ADA CASA Assessment Controls & Codebase Mapping (`CASA-22465-11104`)
This document records all 48 verified security controls from the official TAC Security ESOF AppSec ADA CASA Assessment Report (Assessment ID: CASA-22465-11104, Status: 48 / 48 P...
ADA CASA Assessment Controls & Codebase Mapping (CASA-22465-11104)
This document records all 48 verified security controls from the official TAC Security ESOF AppSec ADA CASA Assessment Report (Assessment ID: CASA-22465-11104, Status: 48 / 48 PASS, covering CASA AL1 & AL2 requirements) and maps each control to the exact files, helpers, and architectural invariants that enforce it in this repository.
Mandatory Rule for Engineers & AI Coding Agents: Never remove, weaken, or bypass any control listed below. Automated guardrails in
scripts/verify-security-guardrails.mjsandsrc/lib/__tests__/security-architecture-guardrails.test.tscontinuously verify the structural invariants from this report.
Section 1: Authentication (1.1 – 1.3)
| Control ID | Report Sr. No. | Requirement Summary | Codebase Implementation & Enforcement |
|---|---|---|---|
1.1.1 | #37 | Authentication is resistant to brute force attacks | Delegated to Firebase Authentication / Google Identity Platform (signInWithEmailAndPassword in src/lib/firebase/auth.ts) and verified server-side via Firebase Admin SDK (adminAuth.verifyIdToken in src/lib/server/auth.ts). Customer portal 6-digit OTP verification in /api/portal/auth/verify-code enforces strict attempt limits (attempts >= 5 invalidates code). |
1.1.2 | #26 | System-generated initial passwords or activation codes shall be securely randomly generated and expire after a short period | Password resets are delegated to Firebase Auth (sendPasswordResetEmail / confirmPasswordReset in src/lib/firebase/auth.ts). Portal 6-digit OTP codes use crypto.randomInt(100000, 999999) and expire after 10 minutes. |
1.1.3 | #27 | Passwords shall be stored in a form that is resistant to offline attacks | Delegated to Firebase Authentication (scrypt hashing managed by Google Identity Platform). Application never stores user account passwords in Firestore. |
1.2.1 | #28 | Default credentials shall not be present on publicly exposed interfaces | No default, guest, or shared accounts exist on /login. All accounts require authenticated Firebase identity + verified tenant/subscription status. |
1.3.1 | #29 | Out-of-band verifier shall expire in a reasonable timeframe | Firebase Auth oobCode tokens expire automatically on Google's backend; portal OTP codes expire in 10 minutes. |
1.3.2 | #30 | Out-of-band verifier shall only be used once | Firebase Auth oobCode is single-use (confirmPasswordReset returns auth/invalid-action-code on reuse); portal OTP codes are marked used: true atomically on first successful verification. |
1.3.3 | #31 | Out-of-band verifier shall be securely random | Firebase Auth generates cryptographically random 44+ character oobCode tokens; portal OTP codes use Node crypto.randomInt. |
1.3.4 | #32 | Out-of-band verifier shall be resistant to brute force attacks | High-entropy Firebase oobCode tokens + rate-limited/attempt-capped OTP verification. |
Section 2: Session Management (2.1 – 2.4)
| Control ID | Report Sr. No. | Requirement Summary | Codebase Implementation & Enforcement |
|---|---|---|---|
2.1.1 | #33 | Never reveal passwords, API keys, or session tokens in URL query strings | verifyAuthToken() only extracts tokens from the Authorization: Bearer <token> HTTP header. Passing ?access_token= or ?token= in the URL query string on standard API routes returns 401 Unauthorized. (Only specific <img src> / window.open print routes explicitly verify a short-lived ID token where browser headers cannot be set). |
2.2.1 | #34 | Logout or session expiration shall invalidate all stateful session tokens, including refresh tokens | signOutUser() and signOutPortalUser() call revokeUserSessions(idToken) before calling signOut(auth), invoking adminAuth.revokeRefreshTokens(uid) server-side (see docs/SESSION_LOGOUT_SECURITY.md). |
2.2.2 | #35 | Terminate all other active sessions (including refresh tokens) after a password change or reset | Firebase Authentication invalidates existing sessions/refresh tokens upon confirmPasswordReset / password update. |
2.2.3 | #36 | Non-revocable stateless authentication tokens must expire within 24 hours | Firebase ID tokens (RS256 JWTs) have a hard 1-hour (3600s) lifetime (exp - iat = 3600). |
2.3.1 | #25 | Cookie-based session tokens shall have the Secure attribute set | __session cookies set by Firebase App Hosting / application middleware enforce Secure. |
2.3.2 | #38 | Cookie-based session tokens shall have the HttpOnly attribute set | __session cookies enforce HttpOnly so client-side scripts (document.cookie) cannot read session cookies. |
2.3.3 | #39 | Use session tokens rather than static API secrets and keys | Client-to-server requests authenticate exclusively with short-lived Firebase Bearer ID tokens via fetchWithAuth(). Third-party OAuth integrations use short-lived Nango connect session tokens via /api/nango/session. |
2.3.4 | #40 | Stateless session tokens shall use digital signatures (RS256) and reject tampering, alg:none, and key substitution | Verified via Firebase Admin SDK adminAuth.verifyIdToken(token) in src/lib/server/auth.ts, which enforces Google's RS256 public keys, issuer, audience, and expiration. |
2.4.1 | #41 | Require a full valid login session or re-authentication before sensitive transactions or account modifications | Sensitive account updates require recent authentication (reauthenticateWithCredential() with EmailAuthProvider.credential() when auth/requires-recent-login is raised). |
Section 3: Access Control (3.1 – 3.3)
| Control ID | Report Sr. No. | Requirement Summary | Codebase Implementation & Enforcement |
|---|---|---|---|
3.1.1 | #42 | Enforce least-privilege access control rules on a trusted service layer | All /api/tenants/[tenantId]/** routes call verifyTenantAccess(request, tenantId). All Server Actions accepting a tenantId call verifyTenantAccessForAction(idToken, tenantId). Superadmin endpoints call verifyAdminRequest(request). Firestore rules in firestore.rules enforce canAccessTenant(tenantId) and deny direct client writes (allow write: if false) on sensitive collections. |
3.1.2 | #43 | User/data attributes and policy information used by access controls cannot be manipulated by end users | Staff role and permissions live in tenants/{tenantId}/staff/{staffUid} and staffRegistry/{staffUid}, protected by owner checks (authResult.uid !== tenantId) in src/app/api/staff/route.ts and anti-self-escalation checks (SENSITIVE_SELF_FIELDS + staff_manage permission check) in src/app/api/tenants/[tenantId]/staff/[id]/route.ts (403 Forbidden). |
3.1.3 | #44 | Access controls shall fail securely including when an exception occurs | verifyAuthToken, verifyTenantAccess, and verifyAdminRequest wrap token verification in try/catch and return 401/403 on any missing/malformed header or runtime exception without leaking stack traces. |
3.1.4 | #45 | Protect sensitive resources against Insecure Direct Object Reference (IDOR) across CRUD operations | Every tenant resource lives under tenants/{tenantId}/<subcollection>/{docId} and is queried/mutated only after verifyTenantAccess(request, tenantId) confirms the caller is either the shop owner (uid === tenantId) or a registered staff member (staffRegistry/{uid}.tenantId === tenantId). Cross-tenant IDs return 403 Forbidden. |
3.2.1 | #13 | Securely validate redirect_uri and state parameters during OAuth 2.0 authorization | OAuth flows (brokered via Nango and Google Identity) validate redirect_uri against strict allowlists and bind requests to cryptographic state parameters. |
3.2.2 | #48 | Implement only secure OAuth 2.0 flows (Authorization Code Flow with PKCE) | OAuth integrations use Authorization Code Flow with PKCE (code_challenge / code_verifier with S256). Implicit and ROPC flows are not used. |
3.3.1 | #2 | Application administrative interfaces shall use multi-factor authentication (MFA) | Superadmin accounts require TOTP Multi-Factor Authentication via src/lib/firebase/mfa.ts, src/components/auth/TotpEnrollmentSetup.tsx, and src/components/auth/login-form.tsx (auth/multi-factor-auth-required), plus server-side superadmin checks in src/lib/server/admin-auth.ts. |
Section 4: Cryptography (4.1)
| Control ID | Report Sr. No. | Requirement Summary | Codebase Implementation & Enforcement |
|---|---|---|---|
4.1.1 | #3 | Enforce TLS 1.2+ and secure cipher suites for all connections | Enforced by Firebase App Hosting / Google Front End (TLS 1.2 and TLS 1.3 with AEAD cipher suites TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, and Strict-Transport-Security header in next.config.ts). |
4.1.2 | #4 | Connections to and from the server shall use trusted TLS certificates | Enforced via Google Trust Services (WR2 / GTS Root R1) certificates on Firebase App Hosting and standard Node.js TLS certificate verification on outbound HTTPS calls (rejectUnauthorized is never disabled). |
4.1.3 | #5 | No weak cryptography impacting confidentiality or integrity of confidential data | Tenant credentials at rest (PSRestful subaccount tokens, SMTP passwords) are encrypted with AES-256-GCM in src/lib/utils/encryption.ts using a 256-bit key (CREDENTIAL_ENCRYPTION_KEY), 128-bit authentication tag, and fresh 16-byte crypto.randomBytes(16) IV per encryption (see docs/ENCRYPTION_KEY_MANAGEMENT.md). Secret comparisons use crypto.timingSafeEqual(). |
4.1.4 | #6 | Cryptographic modules fail securely and do not enable Padding Oracle attacks | Authenticated encryption (aes-256-gcm) verifies the 16-byte GCM authentication tag prior to returning plaintext (decipher.setAuthTag(authTag) in src/lib/utils/encryption.ts) and throws a generic error on tampering, preventing padding oracle side-channels. |
Section 5: Input Validation & Output Encoding (5.1 – 5.2)
| Control ID | Report Sr. No. | Requirement Summary | Codebase Implementation & Enforcement |
|---|---|---|---|
5.1.1 | #7 | Protect against HTTP Parameter Pollution (HPP) | URL query parameters are parsed via the standard WHATWG new URL(request.url).searchParams.get(...) API, which deterministically returns a single scalar string rather than an array or concatenated string. |
5.1.2 | #8 | Limit URL redirects and forwards to allowlisted URLs or relative paths | Post-login redirects use getSafeRedirectUrl() to validate the redirect parameter: protocol-relative (//), absolute external (http:, https:), javascript:, data:, and backslash (\) URLs are rejected and default safely to /dashboard. |
5.1.3 | #1 | Context-aware output escaping or sanitization protects against reflected, stored, and DOM-based XSS | React JSX auto-escapes rendered values by default; Content-Security-Policy (CSP) and security headers are configured in next.config.ts. |
5.1.4 | #9 / #10 | Avoid eval() / dynamic code execution and template injection | No user input is passed to eval(), new Function(), or server-side template evaluators. |
5.1.5 | #11 | Prevent Server-Side Request Forgery (SSRF) | All URL-fetching proxy routes (src/app/api/image-proxy/route.ts, src/app/api/gang-sheet/image-proxy/route.ts, and src/app/api/shipping/print-proxy/route.ts) MUST use fetchWithSsrfGuard(). fetchWithSsrfGuard resolves DNS records (dns.lookup(..., { all: true })), blocks RFC 1918 private IPs (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), loopback (127.0.0.0/8, ::1), link-local / cloud metadata (169.254.0.0/16 including 169.254.169.254), and re-validates every redirect hop (redirect: 'manual'). |
5.1.6 | #12 | Protect against XPath or XML injection attacks | No user-supplied XML is evaluated with XPath or external entity expansion (XXE). |
5.1.7 | #14 | Protect against database (NoSQL/Firestore) injection attacks | JSON payloads are validated for primitive types before Firestore queries, and all Firestore paths are scoped under verified tenants/{tenantId}/... references. |
5.1.8 | #15 | Protect against OS command injection | No user-controlled strings are interpolated into shell commands (exec/spawn). Vector rendering in src/lib/server/vector-renderer.ts uses argument arrays with temporary file paths generated server-side. |
5.1.9 | #16 | Protect against Local File Inclusion (LFI) and Remote File Inclusion (RFI) | Dynamic routes treat path parameters strictly as database identifiers or return Next.js 404s; no user input is passed to fs.readFile without strict path confinement. |
5.1.10 | #46 | Enforce anti-CSRF protection on authenticated functionality | All authenticated API mutations require a custom Authorization: Bearer <idToken> header (injected by fetchWithAuth()) and JSON Content-Type: application/json, which browsers block cross-origin without CORS preflight approval. |
5.2.1 | #17 | Protect against malicious file uploads (type restriction, size limit, no direct execution) | src/app/api/upload-public-image/route.ts and src/app/api/gang-sheet/upload/route.ts enforce a 50MB limit, block executable MIME types and extensions (html, js, php, sh, exe, etc.), and set contentDisposition: 'attachment' on SVG/XML uploads so browsers never execute them inline (see docs/FILE_UPLOAD_SECURITY.md). |
Section 6: Configuration, Dependencies & Secrets (6.1 – 6.7)
| Control ID | Report Sr. No. | Requirement Summary | Codebase Implementation & Enforcement |
|---|---|---|---|
6.1.1 | #18 | Only use software components without known exploitable vulnerabilities | Dependencies in package.json are patched against known CVEs (firebase-admin, next, nodemailer, etc.). |
6.2.1 | #19 | Disable debug modes and stack trace exposure in production environments | Next.js production builds return generic JSON/HTML error responses without exposing internal stack traces or source maps. |
6.3.1 | #20 | The Origin header shall not be used for authentication or access control decisions | Authentication and authorization rely exclusively on verified cryptographic tokens (verifyAuthToken / verifyTenantAccess), never on spoofable Origin or Referer headers. |
6.4.1 | #21 | The application shall not be susceptible to subdomain takeovers | Wildcard and custom domains are managed through Firebase App Hosting with active TLS certificates and tenant subdomain routing in src/proxy.ts. |
6.5.1 | #22 | Never log credentials, session tokens, or payment card details (PAN/CVV) | Card payments are tokenized client-side via Stripe Elements (js.stripe.com) or Square Web Payments SDK; raw card numbers never touch the application server. Server logs redact secrets via sanitizeCredentials() and maskCredential(). |
6.6.1 | #23 | Browser storage is securely cleared during logout | signOutUser(), signOutPortalUser(), and TenantProvider clear sessionStorage (effectiveTenantId, tenantData, isStaff, permissions) and sensitive localStorage keys (last_tenant_brand) upon sign-out. |
6.7.1 | #24 | Securely store access tokens, API keys, and server-side secrets | Platform secrets are managed in Google Cloud Secret Manager and injected via apphosting.yaml; tenant OAuth tokens are stored in Nango's vault (only connectionId is in Firestore); tenant API keys/SMTP credentials are encrypted at rest with AES-256-GCM (see docs/SECRETS_ACCESS_CONTROL.md). |
6.7.2 | #47 | Directory browsing shall be disabled | Next.js App Router serves only explicitly defined routes and static assets in public/; directory listing requests return 404 Not Found. |
Have a question about this feature?
Log into your Print Life dashboard anytime and click the Shop Co-Pilot button. You can ask anything from margin math to work center capacity, and the AI wizard will guide you through step-by-step.