docs(auth): reflect single-cookie architecture; close A13-3

dev-docs/AUTH_ARCHITECTURE.md (v1.0 → v2.0):
- Title section updated to single-SPA / single-cookie reality
- Client Applications table collapsed to one row
- Cookie Specification table collapsed to one row (crewli_app_token)
- Token Lifecycle / Validation section: Origin-based resolution
  language removed; middleware described as origin-agnostic
- Cross-app isolation paragraph removed (no second app)
- Configuration Reference table marks FRONTEND_PORTAL_URL as legacy,
  pointing at TECH-FRONTEND-URL-CONSOLIDATE
- New §11 "History" preserves the pre-WS-3 dual-cookie context for
  future readers, mentions PR-B2a + PR-B2b roles in the unwind

dev-docs/BACKLOG.md — three new entries:
- TECH-FRONTEND-URL-CONSOLIDATE: refactor email controllers to drop
  per-app URL map (EmailChangeController, PasswordResetController,
  PersonController) — low priority, code-cleanliness only
- TECH-DOCS-APPS-PORTAL-PURGE: sweep apps/portal references from
  briefing/tooling docs (.cursor/, MASTER_PROMPT_*, SETUP, dev-guide,
  CLAUDE_CODE_TOOLING) — single chore(docs) PR, low priority
- OPS — DNS retirement of portal.crewli.app — operational task,
  deferred until traffic monitoring confirms zero usage

dev-docs/SECURITY_AUDIT.md:
- A13-1 narrative actualised: pre-WS-3 dual-cookie context kept as
  history, status flipped to RESOLVED (the localStorage→httpOnly
  migration shipped earlier in the consolidation arc)
- A13-3: status flipped to RESOLVED by WS-3 PR-B2b; description
  rewritten to reflect the new postLoginRedirect.ts validator and
  the 16 spec coverage
- Priority remediation table item 8 strikes through A13-3

Backend test suite: 1487 passed (unchanged from Commit 2 baseline).
Frontend: 223 passed (unchanged from Commit 1 baseline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-06 00:29:26 +02:00
parent a748c9ee7a
commit 812cc17460
3 changed files with 119 additions and 40 deletions

View File

@@ -552,13 +552,11 @@ Audit scope: all files under `api/` and `apps/` (app, portal).
### Frontend Security (A13)
#### [CRITICAL] A13-1: Bearer tokens stored in `localStorage` (apps/app and apps/portal)
#### ~~[CRITICAL] A13-1: Bearer tokens stored in `localStorage` (apps/app and apps/portal)~~ RESOLVED
- **File:** `apps/app/src/stores/useAuthStore.ts`
- **File:** `apps/portal/src/stores/useAuthStore.ts`
- **Description:** Sanctum bearer tokens stored in `localStorage` under `crewli_token` and `crewli_portal_token`. Accessible to any JavaScript on the page.
- **Risk:** Any XSS vulnerability (or supply-chain attack) can steal tokens and impersonate users indefinitely.
- **Fix:** Migrate to `httpOnly; Secure; SameSite=Strict` cookies set by the Laravel backend. Remove `setItem`/`getItem` usage.
- **File:** `apps/app/src/stores/useAuthStore.ts` (single SPA post WS-3)
- **Description:** Pre-WS-3 (April 2026) the SPA layer used per-app cookies (`crewli_app_token`, `crewli_portal_token`) with Origin-based middleware resolution. WS-3 PR-B consolidated the dual SPAs into a single `apps/app` workspace; PR-B2b retired the dual-cookie machinery. The system now issues a single httpOnly `crewli_app_token` cookie. The localStorage-based bearer-token storage that this finding originally flagged was migrated to httpOnly cookies as part of the same consolidation arc.
- **Resolution:** Tokens are httpOnly + Secure + SameSite=Strict, set server-side, never exposed to JavaScript. See `dev-docs/AUTH_ARCHITECTURE.md` for current architecture.
#### ~~[HIGH] A13-2: Admin app cookies lack `httpOnly`, `Secure`, and `SameSite` flags~~ RETIRED
@@ -566,13 +564,12 @@ Audit scope: all files under `api/` and `apps/` (app, portal).
- **Description:** The admin SPA has been retired. Its functionality now lives in `apps/app/` under `/platform/*` routes.
- **Resolution:** Finding no longer applicable — `apps/admin/` has been removed.
#### [HIGH] A13-3: Open redirect vulnerability on post-login redirect (all apps)
#### ~~[HIGH] A13-3: Open redirect vulnerability on post-login redirect (all apps)~~ RESOLVED by WS-3 PR-B2b
- **File:** `apps/portal/src/pages/login.vue:61,74-76`
- **File:** `apps/app/src/pages/login.vue:55`
- **Description:** All login pages accept `?to=` query parameter and redirect to it after login without validating it's a relative path. Portal falls back to `window.location.href` with the raw value.
- **Risk:** Phishing: `https://portal.crewli.app/login?to=https://evil.com/steal`.
- **Fix:** Validate that redirect target starts with `/` before using it.
- **File:** `apps/app/src/utils/postLoginRedirect.ts` (single SPA post WS-3)
- **Description:** Login pages accepted `?to=` query parameter and redirected to it after login without validating it's a relative path.
- **Risk:** Phishing: `https://crewli.app/login?to=https://evil.com/steal`.
- **Resolution:** WS-3 PR-B2a introduced a minimum precaution (`startsWith('/') && !startsWith('//')`); WS-3 PR-B2b replaced it with full validation. The `isSafeRelativePath` helper in `apps/app/src/utils/postLoginRedirect.ts` now rejects empty input, non-`/`-prefixed paths, protocol-relative URLs, backslashes (browsers normalise `\``/`), ASCII control characters (`\x00``\x1F`, `\x7F`), and anything the URL constructor parses to a different origin than a synthetic invalid base. 16 vitest specs pin the contract.
#### ~~[HIGH] A13-4: `v-html` with API-sourced data in admin app (template pages)~~ RETIRED
@@ -656,7 +653,7 @@ The following security measures ARE correctly implemented:
| 5 | A02-2: Set Sanctum token expiration | One line |
| 6 | A02-1: Replace ULID tokens with cryptographic random | Small |
| 7 | A01-1: Implement PortalTokenMiddleware | Medium |
| 8 | A13-3: Fix open redirect on login pages | Small |
| 8 | ~~A13-3: Fix open redirect on login pages~~ ✅ resolved by WS-3 PR-B2b | Small |
### Short-term (within 1 sprint)