Security Policy

Learn about our security practices, supported versions, and how to report vulnerabilities.

Supported Versions

VersionSupported
1.23.x
1.22.x
1.21.x
< 1.21No

Reporting a Vulnerability

Please do not open a public GitHub issue for security vulnerabilities.

Email security reports to: security@traqify.com or reach the maintainer via @oyedokunken.

Include:

  • A clear description of the vulnerability
  • Steps to reproduce
  • Potential impact assessment
  • Any suggested fix (optional)

You will receive an acknowledgement within 48 hours and a full response within 7 days.

Authentication

JWT Access Tokens

15-minute lifetime, HS256 only; signed with JWT_SECRET; carry tokenVersion — incremented on logout and password change to invalidate all outstanding tokens immediately

Refresh Tokens

30-day opaque token stored only as a SHA-256 hash; sent as an httpOnly cookie that scripts cannot read; rotated on every use, and reuse of an old token revokes the session

Google OAuth 2.0

Server-side code exchange; tokens stored in OAuthSession (2 min TTL); browser only receives an opaque one-time code — tokens never appear in the URL

OTP Email Verification

SHA-256 hashed before storage; expires after 10 minutes; invalidated after 5 failed attempts

Password Hashing

bcrypt cost factor 12; changePassword requires current password and enforces full complexity rules (uppercase, lowercase, digit, special character)

Authorization (RBAC)

Four roles with descending privilege: OWNER > MANAGER > AUDITOR > CASHIER

  • Route-level enforcement: authenticate middleware, requireOrg middleware, and four RBAC guards (isOwnerOnly, isOwnerOrManager, isAtLeastAuditor, isAtLeastCashier) applied per route
  • All authenticated routes require a valid JWT; expired tokens and tokens with a stale tokenVersion are rejected with 401
  • Organization scope enforced on every Prisma query; no query trusts the frontend to scope data
  • Staff listing (GET /api/staff) requires AUDITOR or above; CASHIER cannot enumerate org members
  • OWNER protection: the OWNER account cannot be restricted, removed, or have their password reset via staff tools
  • Invite role cap: OWNER role can never be assigned via invitation

Additional Security Measures

CORS

Explicit FRONTEND_URL allowlist; credentials: true for cookie support without wildcard origin

File Uploads

Product images: JPEG/PNG/WebP max 5 MB. Downloadable files: explicit MIME allowlist max 4 MB. Multer memoryStorage — no disk writes

Input Validation

Zod schemas on every request body; all auth routes have full password complexity rules (uppercase, lowercase, digit, special character)

Audit Logging

Every create/update/delete/login/export event logged with actor, entity, IP, user agent, and timestamp; append-only

Startup Validation

Server throws before accepting any traffic if JWT_SECRET, DATABASE_URL, PAYSTACK_SECRET_KEY, or FRONTEND_URL are absent

Paystack Verification

Transaction verified server-side via Paystack API before any order is created; verified amount must match server-calculated total

SSRF Protection

Downloadable product file URLs are validated with URL.hostname.endsWith('.supabase.co') before any server-side fetch

Cron Job Security

GET /api/cron/ping requires Authorization: Bearer <CRON_SECRET>; Vercel injects the header automatically; the endpoint is not publicly exploitable