Epic 006 — Security, Privacy, and Reliability
Status
Ready
The implementation stories are ready; no application fixes have started.
Purpose and boundaries
Turn a repository-specific security review into a small, testable backlog, prioritizing credential transport and authentication-abuse resistance. This is an engineering review, not a penetration test, certification, legal opinion, or claim of formal OWASP Application Security Verification Standard, PCI DSS, or GDPR compliance.
The login password is intentionally sent in the JSON body of POST /api/auth/login. A request body
is not inherently unsafe: confidentiality in transit depends on HTTPS. Do not encrypt the password
in the browser as a substitute for TLS, and do not change the authentication architecture merely to
avoid a normal credential request.
Only this specification and its documentation navigation are changed by the audit. No application code, configuration, or runtime behavior is changed. Existing acceptance behavior and the single-store, one-operator MVP boundary remain in force.
Audit result
Confirmed findings
| Finding | Severity | Confidence | Evidence and consequence | Backlog |
|---|---|---|---|---|
SEC-F-001 — Hosted transport schemes are not enforced consistently. The config schema permits http: for PUBLIC_BROWSER_ORIGIN and SURFBOARD_API_URL; only hosted APP_PUBLIC_URL explicitly requires HTTPS. The frontend accepts its build/runtime API base without a production-scheme check. The Surfboard transport sends configured authentication headers to the configured URL. A misconfigured hosted origin could expose browser credentials/session traffic; an HTTP provider URL could expose provider credentials. |
Medium; potentially high impact if deployed with HTTP | High for the schema gap; deployment exposure is unverified | backend/src/config.ts, frontend/src/app/api/api-client.ts, backend/src/integrations/surfboard-transport.ts, development guide §6.1 |
SEC-AUTH-001 |
SEC-F-002 — Account-attempt throttle entries are never removed. The auth route registrar creates a process-local Map; account keys are hashed, but expired entries remain in the map for the lifetime of the process. Unique login, recovery, or reset keys can therefore accumulate. The existing per-IP and account throttles are also process-local, as documented in ADR 002. |
Medium; availability risk | High | backend/src/auth-http.ts, backend/src/auth-http-support.ts, backend/src/auth-session-http.ts, backend/src/auth-recovery-http.ts |
SEC-AUTH-002 |
Password-in-request conclusion
The frontend sends login, password-change, and reset credentials in JSON request bodies through the
shared API client. The backend validates those bodies; it does not put the password in a URL.
Automatic Fastify request logging is disabled, and the application log event allowlist excludes
bodies, raw URLs, headers, and account values. Passwords are held in page/component memory while
being entered and are not intentionally written to localStorage or sessionStorage. Recovery
credentials are carried in a URL fragment, removed by the reset page, and are covered by existing
no-storage tests.
Password storage uses the maintained argon2 library with Argon2id at 19 MiB, two iterations, and
parallelism one; the library generates salts. Password change and recovery revoke sessions. The
session is an eight-hour, non-rolling, signed HttpOnly cookie with SameSite=Lax; hosted
configuration enables Secure. Origin checks and CSRF tokens protect browser mutations, and login
uses a generic invalid-credentials response, a dummy-hash verification path, and IP/account
throttles.
These controls do not prove the public transport is encrypted. The staging guide prescribes HTTPS origins, but the repository has no application deployment manifest or verified staging/production response evidence. The login concern therefore remains a deployment/configuration verification item, not a confirmed plaintext exposure.
Existing controls and reviewed areas
- Authentication lifecycle: account creation is through guarded operator tooling, not public registration. Passwords are Argon2id hashes; recovery tokens are random, single-use, expiring, and stored as digests. Session IDs are stored as SHA-256 digests in PostgreSQL; login rotates the session, logout destroys it, and password changes invalidate all sessions.
- Authorization and API: product and order operations require a current backend session,
mutations require exact Origin plus CSRF protection, request objects are strict and bounded, and
application errors are sanitized.
/health, the sample/api/message, CSRF acquisition, login, and password recovery are intentionally public; recovery acknowledgements do not reveal whether an account exists. The Surfboard webhook is public by design but verifies its raw-body HMAC signature before parsing and has no order-changing side effect today. - Single-store authorization boundary: the current MVP intentionally has no role, tenant, or per-user resource ownership model. Authenticated operators share one store's products and orders. This is not reported as broken object-level authorization under the current one-operator/trusted account scope. Do not provision less-trusted users until the product owner confirms the trust model; do not invent roles in this epic without that decision.
- Order/payment integrity: the backend prices from its catalog, not client totals; commercial finalization uses durable operation claims and preserves unknown payment outcomes instead of blindly retrying. Surfboard requests have bounded timeouts/responses, no redirects or automatic retries, and diagnostics redact provider payloads. Local and automated tests use a deterministic fake; Surfboard Demo/Live evidence remains blocked under MVP-TASK-034.
- Secrets and dependencies: provider secrets are server-side configuration with empty committed
examples; the repository has Gitleaks history/source checks and dependency, container, and
supply-chain gates. During this review,
pnpm secrets:checkreported no findings andpnpm security:dependenciesreported no blocking high/critical advisories; these checks do not establish that all external secrets or future disclosures are absent. Existing tests cover auth HTTP protections, real PostgreSQL password/session behavior, recovery, and payment outcomes. The new backlog targets the identified gaps rather than duplicating these controls. - Personal data: user email/display name and preferences, password/session/recovery digests, operator-entered order notes and queue labels, product/order snapshots, timestamps, VAT/totals, and transaction references are stored. No customer account, card data, analytics integration, or payment-card storage was found. Order and history records are durable and no application retention/export/deletion workflow is specified.
Open verification and decisions
These are not confirmed vulnerabilities and do not justify speculative runtime changes:
- Hosted transport and edge behavior: inspect staging and production frontend/API URLs, deployment TLS certificates, HTTPS redirect/rejection behavior, HTTP Strict Transport Security/security headers, frontend API-base configuration, trusted-proxy behavior, and the Secure session cookie. Repository documentation describes expected settings but does not establish the deployed state.
- Throttle topology: confirm whether the hosted backend runs one or multiple instances and what edge protections apply. The current rate-limit state is process-local; do not claim cross-instance enforcement.
- Privacy operations: the business/data controller must establish purposes, retention periods, deletion/correction/export handling, backup retention, hosting regions, third-party processors, and payment-provider data flows. The code does not establish those facts. This audit makes no GDPR compliance or non-compliance determination.
- Identity trust: confirm that every provisioned account is an equally trusted operator before widening account provisioning or adding different user classes.
- Surfboard integration: do not use this epic to bypass the external prerequisites or evidence recorded in ADR 004 and MVP-TASK-034. No live payment, replay, or production test was run.
Implementation stories
| Order | Story | Priority | Implementation task | Depends on | Status |
|---|---|---|---|---|---|
| 1 | SEC-AUTH-001 — Require HTTPS at hosted credential boundaries | P1 — prevents accidental credential/provider-secret transport over HTTP | SEC-TASK-001 | None | In progress |
| 2 | SEC-AUTH-002 — Bound authentication abuse controls | P2 — prevents unbounded throttle-state growth and verifies deployed limits | SEC-TASK-002 | None | In progress |
Scenarios use stable IDs. Future implementation tests must include the exact scenario ID in the test title or native metadata and link passing evidence in this epic's task record. Hosted or business-owned verification must remain explicitly incomplete until its evidence exists.
Audit references
The review used the repository's architecture, development guide, testing strategy, current-state, AI handover, ADR 002, ADR 004, and existing authentication/payment tests as implementation evidence.
External guidance consulted as a proportionate baseline:
- OWASP Application Security Verification Standard — site identifies 5.0.0 as the latest stable version; this epic does not claim verification against it.
- OWASP Web Security Testing Guide — the stable link identifies version 4.2.
- OWASP Authentication Cheat Sheet
- OWASP Password Storage Cheat Sheet
- OWASP Session Management Cheat Sheet
- OWASP REST Security Cheat Sheet
- Regulation (EU) 2016/679 (GDPR), especially the principles, data-subject rights, records, and security obligations. The official EUR-Lex page did not return readable content to the web-fetch tool in this pass; no legal interpretation is asserted.
The OWASP standard and testing guide are references for future targeted verification, not a formal assessment or test-coverage claim.