Skip to content

Security & pentest remediation status

Last updated: 2026-07-15 (A1 human TestFlight confirm on AWS)
Scope: LastVet API (api.last.vet) + iOS veteran client + provider secure-link landing
Environment note: Production is AWS ECS + RDS (us-west-2), DNS-only at api.last.vet, running against VA sandbox data until VA production Lighthouse access is granted.

Summary

Area Staging Production 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 SM 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 main
Secure-link iframe sandbox + HTML blob guard Provider portal
Signed ROI AES-256-GCM at rest roiArtifactStorage
Health record RLS (042) Prod migrate applied 2026-07-09
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 (PR #30, prod verified 2026-07-11)
iOS Bearer JWT client TestFlight device pass Gate 0.5 B6 (E2E_RUN_LOG_B6_TESTFLIGHT.md)
Postgres RLS batch 1 (046) lastvet_app + DATABASE_APP_URL on staging + prod (2026-07-11); batch 2 tables deferred
SMS OTP for secure links ⏸️ ⏸️ Deferred — email OTP path (docs/planning/gate-0.5/DECISIONS.md)
42 CFR Part 2 counsel-locked copy 🟡 🟡 Parameterized; HL lock pending (Gate 0.5 D10)

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 database migrate policy (B7)

  1. Prod never auto-migrates — guard is APP_ENV=production in scripts/aws-start.sh (ported from Railway's RAILWAY_ENVIRONMENT_NAME check).
  2. scripts/migrate.js on prod runs only via explicit one-off — e.g. infra/scripts/run-ecs-db-setup.sh (ECS task command override).
  3. ensureCoreSchema() runs inline DDL on every boot via src/index.tsmigrationDb; it does not read migrations/*.sql. Skipped on prod when APP_ENV=production (2026-07-16).
  4. Idempotent Skipping … (already applied) lines appear only when migrate.js is invoked; they are not prod service-boot behavior.
  5. Database access posture (RLS, owner pool, consent vs clinical split): docs/security/SECURITY_POSTURE_DATABASE_ACCESS.md — expands this note; B7 remains closed.
  6. Migration pre-flight: migrate.js --list-pending + --expect on prod via run-ecs-db-setup.sh (2026-07-16).

Gate 0.5 cross-reference

Gate item Status
A1 E2E proof on AWS ✅ Scripted + human TestFlight 2026-07-15 (E2E_RUN_LOG_2026-07-15.md)
A2 Bearer harness + CloudWatch OTP e2e-roi-gate0-full.sh pass 2026-07-15
A3 Facilities ✅ 11 SD facilities, 2026-07-15
B5 042/043 prod on RDS ✅ Cutover 001048
B6 TestFlight ✅ Device pass logged; confirm AWS API URLs
B7 This doc
C8 VA API matrix removed from onboarding ✅ 2026-07-11
C9 SMS defer

Full checklist: docs/runbooks/gate-0.5/GATE_0.5_STATUS.md

Open / follow-up

  • [x] A1 — Human iOS full-loop E2E (B6 TestFlight log)
  • [x] A2e2e-roi-gate0.sh Bearer JWT harness
  • [x] B6 — TestFlight device smoke
  • [x] 046 prodlastvet_app cutover + 046048 on prod (docs/api/TRACK3_PHI_RLS_PLAN.md)
  • [ ] RLS batch 2docs/api/RLS_BATCH2_PLAN.md (consent first, then credentials/tokens)
  • [ ] D10 — 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"}'