Security & pentest remediation status¶
Last updated: 2026-09-15 (Gate 0 EARNED on R940; Gate 0.5 exit)
Scope: LastVet API (api.last.vet) + iOS veteran client + provider secure-link landing
Environment note: Production is R940 sovereign VMs — api.last.vet (DNS-only) → lastvet-edge (.89) → lastvet-api VM 107 (.90) → Postgres on lastvet-db VM 108 (.91). Running against VA sandbox data until VA production Lighthouse access is granted. AWS ECS/RDS and Railway are rollback/teardown nets only — not production origin post–2026 R940 cutover.
Summary¶
| Area | Staging | Production (R940) | Notes |
|---|---|---|---|
| Bearer JWT veteran auth | ✅ | ✅ | POST /api/auth/veteran/session + Authorization: Bearer |
Legacy x-user-id header auth |
✅ Off | ✅ Off | ALLOW_LEGACY_HEADER_AUTH=false |
| Session exchange secret configured | ✅ | ✅ | Bitwarden LASTVET_R940_*_VETERAN_SESSION_EXCHANGE_SECRET |
| Session mismatch / IDOR guards | ✅ | ✅ | 403 on veteran session mismatch |
| Malformed JSON → 400 | ✅ | ✅ | invalid_json |
| Provider portal CORS | ✅ | ✅ | dev.provider.last.vet / provider.last.vet |
| HTML escaping (OAuth consent, admin email) | ✅ | ✅ | Deployed on R940 |
| Secure-link iframe sandbox + HTML blob guard | ✅ | ✅ | Provider portal |
| Signed ROI AES-256-GCM at rest | ✅ | ✅ | roiArtifactStorage |
Health record RLS (042) |
✅ | ✅ | Active on lastvet-db |
PHI raw field encryption (043) |
✅ | ✅ | raw_ciphertext via LAST1_TOKEN_ENCRYPTION_KEY |
| Provider login enumeration + IP rate limit | ✅ | ✅ | Unknown email → 401; 30/15min IP limit |
| iOS Bearer JWT client | ✅ | ✅ | TestFlight R940 pass Gate 0.5 B6 (E2E_RUN_LOG_2026-09-15_R940_HUMAN.md) |
Postgres RLS batch 1 (046) |
✅ | ✅ | lastvet_app + DATABASE_APP_URL on lastvet-db; batch 2 tables deferred |
| SMS OTP for secure links | ⏸️ | ⏸️ | Deferred — email OTP path (docs/planning/gate-0.5/DECISIONS.md C9) |
| 42 CFR Part 2 counsel-locked copy | 🟡 | 🟡 | Parameterized; HL lock pending — flagged-and-tracked (Gate 0.5 D10; does not block Gate 0.5 exit) |
| Onboarding VA API matrix | ✅ Off path | ✅ Off path | Dev-only VAAPIMatrixDevView when showsVADiagnostics (DECISIONS.md C8) |
Veteran auth model (current)¶
- iOS exchanges VA
patientid +x-veteran-session-exchange→ short-lived Bearer JWT. - All veteran API routes use
Authorization: Bearer. - Legacy header bootstrap disabled in staging and production.
Secure access (ROI delivery)¶
- Opaque token URLs only — no PHI in link, QR, SMS, or email body.
- Designated-recipient email OTP before artifact view.
- Encrypted signed-ROI PDF at rest; single-use view session for download.
- Revoke from veteran Consent tab disables link immediately.
- Prod invariant: OTPs are never logged (
DECISIONS.md; inbox harness for E2E only).
Prod database migrate policy (B7)¶
- Prod never auto-migrates —
APP_ENV=productionskips boot-time DDL insrc/index.ts(ensureCoreSchema). Legacyscripts/aws-start.sh/ Railway entrypoints retain the same guard but do not serve R940 prod traffic. scripts/migrate.json R940 prod runs only via explicit operator action — SSH tolastvet-api, source/etc/lastvet-api/lastvet-api-prod.env, LANMIGRATION_DATABASE_URL(192.168.1.91,lastvet_master). Seelastvet-gates/docs/runbooks/r940-prod-deploy.md.ensureCoreSchema()is not prod boot behavior whenAPP_ENV=production.- Migration pre-flight:
migrate.js --list-pending+--expect <set>before apply; snapshot on lastvet-db required. - Database access posture (RLS, owner pool, consent vs clinical split):
docs/security/SECURITY_POSTURE_DATABASE_ACCESS.md— R940 section applies; historical AWS ECS notes in that doc are audit trail only.
Gate 0.5 cross-reference (R940)¶
| Gate item | Status | Evidence |
|---|---|---|
| A1 E2E proof on R940 | ✅ | Scripted + human 2026-09-15 (E2E_RUN_LOG_2026-09-15_R940.md, E2E_RUN_LOG_2026-09-15_R940_HUMAN.md) |
| A2 Bearer harness + inbox OTP | ✅ | e2e-roi-gate0-full.sh 2026-09-15 on api.last.vet |
| A3 Facilities | ✅ | 11 SD facilities, 2026-09-15 |
| B5 RLS/PHI on lastvet-db | ✅ | Spot-verify VM 108 |
| B6 TestFlight on R940 hosts | ✅ | E2E_RUN_LOG_2026-09-15_R940_HUMAN.md |
| B7 This doc | ✅ | R940 rewrite 2026-09-15 |
| C8 VA API matrix off onboarding | ✅ | DECISIONS.md 2026-07-11; dev-only matrix |
| C9 SMS defer documented | ✅ | DECISIONS.md 2026-07-09 |
| D10 Part 2 flagged-and-tracked | ✅ | DECISIONS.md; blocks real pilot only |
Full checklist: docs/runbooks/gate-0.5/GATE_0.5_STATUS.md
Open / follow-up¶
- [x] A1 — Scripted + human iOS full-loop E2E on R940
- [x] A2 —
e2e-roi-gate0.shBearer JWT harness + inbox OTP - [x] B6 — TestFlight device pass on R940 API hosts
- [x] B7 — Security status note (this doc, R940 topology)
- [x] C8 / C9 / D10 — Decisions documented; D10 tracked-not-signed
- [ ] RLS batch 2 —
docs/api/RLS_BATCH2_PLAN.md(consent first, then credentials/tokens) - [ ] D10 sign-off — Hogan Lovells Part 2 language lock before any real veteran pilot
- [ ] Shannon retest — Phase 2 closure (
last1-security/docs/PENTEST-PHASE2-STATUS.mdTrack 5)
Verification commands¶
# Session exchange (expect 200 or 404 veteran_not_found — not 503)
curl -s -o /dev/null -w "%{http_code}" -X POST https://api.last.vet/api/auth/veteran/session \
-H "Content-Type: application/json" \
-H "x-veteran-session-exchange: $VETERAN_SESSION_EXCHANGE_SECRET" \
-d '{"vaPatientId":"test"}'
# Legacy headers blocked (expect 401)
curl -s -o /dev/null -w "%{http_code}" https://api.last.vet/api/veteran/me \
-H "x-user-id: test" -H "x-user-type: veteran" -H "x-veteran-id: test"
# Provider login enumeration (expect 401, not 404)
curl -s -X POST https://api.last.vet/api/provider/login \
-H "Content-Type: application/json" \
-d '{"email":"nonexistent@example.com","password":"wrong"}'