Skip to content

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)

  1. iOS exchanges VA patient id + x-veteran-session-exchange → short-lived Bearer JWT.
  2. All veteran API routes use Authorization: Bearer.
  3. 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)

  1. Prod never auto-migrates — APP_ENV=production skips boot-time DDL in src/index.ts (ensureCoreSchema). Legacy scripts/aws-start.sh / Railway entrypoints retain the same guard but do not serve R940 prod traffic.
  2. scripts/migrate.js on R940 prod runs only via explicit operator action — SSH to lastvet-api, source /etc/lastvet-api/lastvet-api-prod.env, LAN MIGRATION_DATABASE_URL (192.168.1.91, lastvet_master). See lastvet-gates/docs/runbooks/r940-prod-deploy.md.
  3. ensureCoreSchema() is not prod boot behavior when APP_ENV=production.
  4. Migration pre-flight: migrate.js --list-pending + --expect <set> before apply; snapshot on lastvet-db required.
  5. 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.sh Bearer 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.md Track 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"}'