chore: root commit of OWSAP security testing/tightening
All checks were successful
Deploy / deploy (push) Successful in 1m42s
Security Tests / security-non-db (push) Successful in 20s
Security Tests / security-db (push) Successful in 22s

This commit is contained in:
2026-03-01 20:46:47 -06:00
parent 1645896e54
commit 079b8b9492
25 changed files with 1131 additions and 107 deletions

View File

@@ -0,0 +1,62 @@
# A01: Broken Access Control
Last updated: March 1, 2026
## Findings
1. Cross-account delete risk in `/account/confirm-delete`.
2. `AUTH_DISABLED` mode allows header-based impersonation (`x-user-id`) and must be tightly controlled.
## Fixes implemented
1. `/account/confirm-delete` is now session-bound:
- Lookup uses `req.userId`.
- Request `email` must match session user's email.
- Mismatch returns `403`.
2. Insecure auth guard added:
- `AUTH_DISABLED=true` now requires `ALLOW_INSECURE_AUTH_FOR_DEV=true` unless `NODE_ENV=test`.
3. `/admin/rollover` hardened:
- Still requires `AUTH_DISABLED=true`.
- Now also requires request IP to be internal/private.
## Test coverage
1. `api/tests/access-control.account-delete.test.ts`
- Verifies cross-account deletion attempt is denied (`403`).
- Verifies victim account is not deleted.
2. `api/tests/auth.routes.test.ts`
- Verifies protected route rejects unauthenticated access.
- Verifies spoofed `x-user-id` is rejected when auth is enabled.
- Verifies login/session/logout behavior with CSRF handling.
- Verifies login lockout behavior.
3. `api/tests/access-control.admin-rollover.test.ts`
- Verifies unauthenticated access to `/admin/rollover` is denied when `AUTH_DISABLED=false`.
- Verifies authenticated access still receives `403` when `AUTH_DISABLED=false`.
- Verifies `/admin/rollover` rejects non-internal client IP when `AUTH_DISABLED=true`.
- Verifies `/admin/rollover` allows internal client IP when `AUTH_DISABLED=true`.
## Run commands
From `api/`:
```bash
npm test -- tests/auth.routes.test.ts tests/access-control.account-delete.test.ts tests/access-control.admin-rollover.test.ts
```
## Expected results
1. All three test files pass.
2. No access granted to protected routes without valid auth cookie/JWT.
3. Spoofed `x-user-id` does not bypass auth when `AUTH_DISABLED=false`.
4. Cross-account delete attempt fails with `403`.
5. `/admin/rollover` remains inaccessible from public/non-internal clients.
## Production configuration requirements
1. `AUTH_DISABLED=false`
2. `ALLOW_INSECURE_AUTH_FOR_DEV=false`
3. Strong `JWT_SECRET` and `COOKIE_SECRET`

View File

@@ -0,0 +1,89 @@
# A02: Security Misconfiguration
Last updated: March 1, 2026
## Findings addressed
1. SMTP transport debug logging enabled in all environments.
2. Production CORS had a fail-open branch when configured origins were empty.
3. Missing explicit anti-framing headers at edge.
4. Docker compose exposed Postgres/API on all host interfaces.
## Fixes implemented
1. SMTP transport hardening in API:
- `requireTLS` now respects config (`SMTP_REQUIRE_TLS`).
- SMTP debug/logger are disabled in production (`!isProd` only).
2. CORS production behavior tightened:
- Removed fail-open branch for empty configured origins.
- Production now allows only explicitly configured origins.
3. Edge header hardening:
- Added `X-Frame-Options: DENY`.
- Added `Content-Security-Policy: frame-ancestors 'none'`.
4. Compose exposure reduction:
- Bound Postgres and API ports to localhost only.
## Files changed
1. `api/src/server.ts`
2. `Caddyfile.prod`
3. `Caddyfile.dev`
4. `deploy/nginx/skymoneybudget.com.conf`
5. `docker-compose.yml`
## Verification
### Automated security regression tests
Command (A01 regression):
```bash
cd api
npm test -- auth.routes.test.ts access-control.account-delete.test.ts
```
Verified output (provided from host run):
- Test Files: `2 passed (2)`
- Tests: `6 passed (6)`
- Start time: `16:39:35`
Command (A02 dedicated suite):
```bash
cd api
npx vitest --run -c vitest.security.config.ts
```
Verified output:
- Test Files: `1 passed (1)`
- Tests: `5 passed (5)`
- Suite: `tests/security-misconfiguration.test.ts`
Coverage in dedicated suite:
1. Production CORS allowlist enforcement (allowed origin accepted, denied origin does not receive allow headers).
2. SMTP production mailer options disable debug/logger.
3. Runtime CORS preflight headers validated for allowed origins (`allow-origin`, `allow-credentials`, `vary`).
4. Edge config files contain anti-framing headers (`X-Frame-Options`, `frame-ancestors` CSP).
5. `docker-compose.yml` binds Postgres/API ports to localhost only.
### Expected operational checks after deploy
1. Unauthenticated `GET /dashboard` returns `401`.
2. Spoofed `x-user-id` does not bypass auth when `AUTH_DISABLED=false`.
3. `/admin/rollover` remains inaccessible from public network.
4. Response headers include anti-framing policy.
## Residual notes
1. Keep production env pinned to:
- `AUTH_DISABLED=false`
- `ALLOW_INSECURE_AUTH_FOR_DEV=false`
2. Keep CORS origins explicitly configured in production:
- `CORS_ORIGIN` and/or `CORS_ORIGINS`.

View File

@@ -0,0 +1,77 @@
# A03: Software Supply Chain Failures
Last updated: March 1, 2026
## Findings addressed
1. Production dependency vulnerabilities were present in both API and web lockfiles.
2. Deploy pipeline had no explicit dependency vulnerability gate.
## Fixes implemented
1. Dependency remediation:
- Ran `npm audit fix` in `api` and `web`.
- Revalidated production dependencies are clean with `npm audit --omit=dev`.
2. Pipeline hardening:
- Added supply-chain check step in deploy workflow:
- `npm ci` + `npm audit --omit=dev --audit-level=high` for API and web.
- Updated checkout action from broad major tag to explicit release tag `v4.2.2`.
## Files changed
1. `.gitea/workflows/deploy.yml`
2. `api/package-lock.json`
3. `web/package-lock.json`
4. `api/tests/software-supply-chain-failures.test.ts`
5. `api/vitest.security.config.ts`
## Verification
### Production dependency vulnerability scans
Command:
```bash
cd api
npm audit --omit=dev --audit-level=high
cd ../web
npm audit --omit=dev --audit-level=high
```
Verified output:
- `found 0 vulnerabilities` (api)
- `found 0 vulnerabilities` (web)
### Workflow policy verification (automated)
Command:
```bash
cd api
npx vitest run -c vitest.security.config.ts tests/software-supply-chain-failures.test.ts
```
Verified output:
- Test Files: `1 passed (1)`
- Tests: `2 passed (2)`
Coverage in policy suite:
1. Deploy workflow includes dependency gate step for API and web.
2. Workflow requires `npm ci` and `npm audit --omit=dev --audit-level=high` for both projects.
3. `actions/checkout` remains pinned to an explicit release tag.
## Residual risks (not yet fully eliminated)
1. Base image tags are still mutable (`node:20-bookworm-slim`, `postgres:15`) and not digest-pinned.
2. `actions/checkout` is pinned to a release tag, not a full commit SHA.
3. No artifact signing/attestation verification (e.g., cosign/SLSA) in current deploy pipeline.
## Recommended next hardening steps
1. Pin container images by immutable digest in `Dockerfile`/`docker-compose.yml`.
2. Pin workflow actions to full commit SHAs.
3. Add SBOM generation and signature/attestation verification before deploy.

View File

@@ -0,0 +1,71 @@
# A04: Cryptographic Failures
Last updated: March 1, 2026
## Findings addressed
1. Production origin could be configured as non-HTTPS.
2. JWT configuration did not explicitly constrain issuer/audience/algorithm.
3. Missing explicit env contract for JWT issuer/audience values.
## Fixes implemented
1. Production HTTPS origin enforcement:
- Added production guard requiring `APP_ORIGIN` to start with `https://`.
2. JWT hardening in Fastify:
- Explicit signing algorithm set to `HS256`.
- JWT signing includes configured `iss` and `aud`.
- JWT verification enforces:
- allowed algorithm (`HS256`)
- allowed issuer
- allowed audience
3. Env schema expanded:
- Added `JWT_ISSUER` and `JWT_AUDIENCE` with secure defaults:
- `skymoney-api`
- `skymoney-web`
## Files changed
1. `api/src/env.ts`
2. `api/src/server.ts`
3. `.env.example`
4. `api/tests/cryptographic-failures.test.ts`
5. `api/tests/cryptographic-failures.runtime.test.ts`
6. `api/vitest.security.config.ts`
## Verification
Commands:
```bash
cd api
npx vitest run -c vitest.security.config.ts tests/cryptographic-failures.test.ts tests/cryptographic-failures.runtime.test.ts
```
Verified output:
- Test Files: `2 passed (2)`
- Tests: `7 passed (7)`
Dedicated A04 checks in `cryptographic-failures.test.ts`:
1. Production env rejects non-HTTPS `APP_ORIGIN`.
2. JWT issuer/audience defaults resolve as expected.
3. Fastify JWT plugin is registered with explicit algorithm + issuer + audience constraints.
Runtime adversarial checks in `cryptographic-failures.runtime.test.ts`:
1. Token with invalid issuer is rejected (`401`).
2. Token with invalid audience is rejected (`401`).
3. Unsigned token (`alg=none`) is rejected (`401`).
4. Token with valid signature + expected issuer/audience is accepted on protected auth refresh route.
## Required production env values
1. `APP_ORIGIN=https://...`
2. `JWT_SECRET` strong 32+ chars
3. `COOKIE_SECRET` strong 32+ chars
4. `JWT_ISSUER=skymoney-api` (or your approved value)
5. `JWT_AUDIENCE=skymoney-web` (or your approved value)

View File

@@ -0,0 +1,50 @@
# A05: Injection
Last updated: March 1, 2026
## Findings addressed
1. API route used unsafe Prisma raw SQL helper (`$queryRawUnsafe`) for `/health/db`.
2. Restore script accepted unvalidated external inputs that could be abused for command/SQL injection scenarios during operational use.
## Fixes implemented
1. Replaced unsafe raw SQL helper:
- `app.prisma.$queryRawUnsafe("SELECT now() as now")`
- replaced with tagged, parameter-safe:
- `app.prisma.$queryRaw\`SELECT now() as now\``
2. Hardened `scripts/restore.sh` input handling:
- Added required file existence check for `BACKUP_FILE`.
- Added strict identifier validation for `RESTORE_DB` (`^[A-Za-z0-9_]+$`).
## Files changed
1. `api/src/server.ts`
2. `scripts/restore.sh`
3. `api/tests/injection-safety.test.ts`
4. `api/vitest.security.config.ts`
## Verification
Command:
```bash
cd api
npx vitest run -c vitest.security.config.ts tests/injection-safety.test.ts
```
Verified output:
- Test Files: `1 passed (1)`
- Tests: `2 passed (2)`
Dedicated A05 checks in `injection-safety.test.ts`:
1. Verifies no usage of `$queryRawUnsafe` / `$executeRawUnsafe` across API source files.
2. Executes `scripts/restore.sh` with adversarial `RESTORE_DB` input and verifies restore is rejected before DB commands execute.
## Residual notes
1. Main API query paths use Prisma query builder + zod input schemas (reduces SQL injection risk).
2. Operational scripts should remain restricted to trusted operators and environments.

View File

@@ -0,0 +1,54 @@
# A06: Insecure Design
Last updated: March 1, 2026
## Findings addressed
1. Sensitive email-token workflows did not enforce a cooldown between repeated code requests.
2. Verification and account-deletion flows needed tighter, route-specific throttling to reduce brute-force and abuse risk.
## Fixes implemented
1. Added explicit email-token cooldown guard in API:
- New helper `assertEmailTokenCooldown(userId, type, cooldownMs)`.
- Throws structured `429` error with code `EMAIL_TOKEN_COOLDOWN`.
- Sets `Retry-After` header when cooldown is active.
2. Applied cooldown checks to both token issuance paths:
- `/auth/verify/resend` for signup verification codes.
- `/account/delete-request` for account deletion confirmation codes.
3. Split and applied stricter rate-limit profiles for sensitive auth/account routes:
- `authRateLimit` on `/auth/register` and `/auth/login`.
- `codeVerificationRateLimit` on `/auth/verify` and `/account/confirm-delete`.
- `codeIssueRateLimit` on `/auth/verify/resend` and `/account/delete-request`.
## Files changed
1. `api/src/server.ts`
2. `api/tests/insecure-design.test.ts`
3. `api/vitest.security.config.ts`
## Verification
Command:
```bash
cd api
npx vitest --run -c vitest.security.config.ts
```
Verified output:
- Test Files: `4 passed (4)`
- Tests: `10 passed (10)`
Dedicated A06 checks in `insecure-design.test.ts`:
1. Runtime verification resend endpoint enforces cooldown (`/auth/register` issues token, then immediate `/auth/verify/resend` is blocked with `429` + `Retry-After`).
2. Runtime verification delete-request endpoint enforces cooldown (`/account/delete-request` second attempt returns `429` + `Retry-After`).
3. Runtime verification repeated invalid `/auth/verify` requests trigger route throttling (`429`).
## Residual notes
1. A06 runtime tests require PostgreSQL availability for user/token persistence paths.

View File

@@ -0,0 +1,70 @@
# A07: Identification and Authentication Failures
Last updated: March 1, 2026
## Findings addressed
1. No explicit account lockout after repeated failed login attempts (brute-force risk).
2. Password policy for registration and password updates was too weak (length-only).
## Fixes implemented
1. Added login lockout controls:
- Tracks failed login attempts per normalized email in server memory.
- Locks login for a configurable window after threshold failures.
- Returns `429` with code `LOGIN_LOCKED` and `Retry-After` header during lockout.
2. Added strong password policy:
- Minimum length `12`.
- Requires lowercase, uppercase, number, and symbol.
- Applied to:
- `/auth/register` password.
- `/me/password` new password.
3. Added auth hardening configuration:
- `AUTH_MAX_FAILED_ATTEMPTS` (default: `5`)
- `AUTH_LOCKOUT_WINDOW_MS` (default: `900000`, 15 minutes)
## Files changed
1. `api/src/server.ts`
2. `api/src/env.ts`
3. `.env.example`
4. `api/tests/auth.routes.test.ts`
5. `api/tests/identification-auth-failures.test.ts`
6. `api/vitest.security.config.ts`
## Verification
Dedicated security suite command (executed):
```bash
cd api
npx vitest --run -c vitest.security.config.ts
```
Verified output:
- Test Files: `5 passed (5)`
- Tests: `12 passed (12)`
Dedicated A07 checks in `identification-auth-failures.test.ts`:
1. Runtime checks weak password rejection for registration and `/me/password` update flow.
2. Runtime checks lockout threshold/window behavior with configured `AUTH_MAX_FAILED_ATTEMPTS` and verifies `LOGIN_LOCKED` response + `Retry-After`.
Runtime auth flow checks added in `auth.routes.test.ts`:
1. Rejects weak passwords on registration.
2. Locks login after repeated failed attempts.
Run this in an environment with PostgreSQL running to verify runtime behavior:
```bash
cd api
npm test -- tests/auth.routes.test.ts tests/identification-auth-failures.test.ts
```
## Residual notes
1. Current lockout state is in-memory per API instance; for horizontally scaled production, move lockout tracking to a shared store (Redis/DB) for consistent enforcement across instances.

View File

@@ -0,0 +1,49 @@
# A08: Software and Data Integrity Failures
Last updated: March 1, 2026
## Findings addressed
1. Backup/restore workflow did not verify backup artifact integrity before restoring.
2. Restores could proceed with tampered/corrupted dump files, risking silent data corruption.
## Fixes implemented
1. Added checksum artifact generation during backups:
- `scripts/backup.sh` now generates a SHA-256 checksum file next to each dump (`.sha256`).
2. Added checksum verification before restore:
- `scripts/restore.sh` now requires `${BACKUP_FILE}.sha256`.
- Validates checksum format (64 hex chars).
- Computes runtime SHA-256 of backup file and blocks restore on mismatch.
## Files changed
1. `scripts/backup.sh`
2. `scripts/restore.sh`
3. `api/tests/software-data-integrity-failures.test.ts`
4. `api/vitest.security.config.ts`
## Verification
Command:
```bash
cd api
npx vitest run -c vitest.security.config.ts tests/software-data-integrity-failures.test.ts
```
Verified output:
- Test Files: `1 passed (1)`
- Tests: `2 passed (2)`
Dedicated A08 checks in `software-data-integrity-failures.test.ts`:
1. Executes `scripts/backup.sh` with stubbed `pg_dump` and verifies dump + `.sha256` artifact generation.
2. Executes `scripts/restore.sh` with tampered checksum and verifies restore is blocked before DB commands are invoked.
## Residual notes
1. This secures backup artifact integrity in operational scripts.
2. For CI/CD artifact integrity hardening, next step is attestation/signature verification for deployed build artifacts.

View File

@@ -0,0 +1,59 @@
# A09: Security Logging and Monitoring Failures
Last updated: March 1, 2026
## Findings addressed
1. Security-sensitive auth/account outcomes were not consistently logged as structured audit events.
2. Incident triage required better request correlation for failed auth/CSRF and account-deletion attempts.
## Fixes implemented
1. Added centralized structured security logging helper in API:
- `logSecurityEvent(req, event, outcome, details)`
- Includes request correlation fields (`requestId`, `ip`, `userAgent`).
2. Added audit logging for critical security events:
- `auth.unauthenticated_request` (JWT auth failure)
- `csrf.validation` (CSRF check failure)
- `auth.register` success/blocked
- `auth.login` success/failure/blocked (including lockout cases)
- `auth.logout` success
- `auth.verify` success/failure
- `auth.verify_resend` success/failure/blocked
- `account.delete_request` success/failure/blocked
- `account.confirm_delete` success/failure/blocked
3. Reduced sensitive data exposure in logs:
- Added email fingerprinting (`sha256` prefix) for event context instead of plain-text credentials.
## Files changed
1. `api/src/server.ts`
2. `api/tests/security-logging-monitoring-failures.test.ts`
3. `api/vitest.security.config.ts`
## Verification
Command:
```bash
cd api
npx vitest run -c vitest.security.config.ts tests/security-logging-monitoring-failures.test.ts
```
Verified output:
- Test Files: `1 passed (1)`
- Tests: `2 passed (2)`
Dedicated A09 checks in `security-logging-monitoring-failures.test.ts`:
1. Runtime check emits structured `auth.unauthenticated_request` security event for protected-route access failures.
2. Runtime check emits structured `csrf.validation` security event for CSRF failures.
3. Validates correlation fields (`requestId`, `ip`, `outcome`) are present in emitted security events.
## Residual notes
1. Event logs are currently emitted through app logs; ensure production log shipping/alerting (e.g., SIEM rules on repeated `auth.login` failure/blocked events).
2. Next step for A09 maturity is alert thresholds and automated incident notifications.

View File

@@ -0,0 +1,50 @@
# A10: Server-Side Request Forgery (SSRF)
Last updated: March 1, 2026
## Findings addressed
1. Production `APP_ORIGIN` previously enforced HTTPS but did not explicitly block localhost/private-network targets.
2. SSRF posture needed explicit verification that API runtime code does not introduce generic outbound HTTP clients for user-influenced targets.
## Fixes implemented
1. Hardened production `APP_ORIGIN` validation in env parsing:
- Requires valid URL format.
- Rejects localhost/private-network hosts:
- `localhost`, `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `::1`, `0.0.0.0`, `.local`.
2. Added dedicated A10 verification tests:
- Rejects private/loopback `APP_ORIGIN` in production mode.
- Asserts API server source (`api/src/server.ts`) does not use generic outbound HTTP request clients (`fetch`, `axios`, `http.request`, `https.request`).
## Files changed
1. `api/src/env.ts`
2. `api/tests/server-side-request-forgery.test.ts`
3. `api/vitest.security.config.ts`
## Verification
Command:
```bash
cd api
npx vitest run -c vitest.security.config.ts tests/server-side-request-forgery.test.ts
```
Verified output:
- Test Files: `1 passed (1)`
- Tests: `3 passed (3)`
Dedicated A10 checks in `server-side-request-forgery.test.ts`:
1. Asserts production env parsing rejects multiple private/localhost `APP_ORIGIN` variants.
2. Asserts production env parsing accepts public HTTPS `APP_ORIGIN`.
3. Asserts API source code has no generic outbound HTTP client usage (`fetch`, `axios`, `http.request`, `https.request`) outside test scripts.
## Residual notes
1. Current API architecture has minimal outbound HTTP surface (primarily SMTP transport).
2. If future features add URL fetch/proxy/webhook integrations, enforce strict destination allowlists and network egress controls at implementation time.

View File

@@ -0,0 +1,40 @@
# OWASP Test Results
Last updated: March 2, 2026
This directory is the source of truth for SkyMoney OWASP validation work.
## Purpose
- Track implemented security tests and hardening changes.
- Define exact pre-deploy and post-deploy verification steps.
- Keep release evidence (commands, outputs, timestamps, pass/fail).
## Files
- `A01-Broken-Access-Control.md`: Findings, fixes, and verification for OWASP A01.
- `A02-Security-Misconfiguration.md`: Findings, fixes, and dedicated verification suite for OWASP A02.
- `A03-Software-Supply-Chain-Failures.md`: Dependency and pipeline supply-chain findings/fixes/verification.
- `A04-Cryptographic-Failures.md`: Crypto/session token hardening findings/fixes/verification.
- `A05-Injection.md`: Injection sink remediation and script input hardening verification.
- `A06-Insecure-Design.md`: Abuse-resistance design hardening (cooldowns + tighter route throttling).
- `A07-Identification-and-Authentication-Failures.md`: Login lockout and strong-password policy hardening.
- `A08-Software-and-Data-Integrity-Failures.md`: Backup/restore checksum integrity controls.
- `A09-Security-Logging-and-Monitoring-Failures.md`: Structured security event auditing for auth/account flows.
- `A10-Server-Side-Request-Forgery.md`: SSRF hardening and outbound-request surface validation.
- `post-deployment-verification-checklist.md`: Production smoke checks after each deploy.
- `evidence-log-template.md`: Copy/paste template for recording each verification run.
- `residual-risk-backlog.md`: Open non-blocking hardening items tracked release-to-release.
## Current status
1. A01 complete: implemented and tested.
2. A02 complete: implemented and tested.
3. A03 complete (initial hardening): implemented and tested.
4. A04 complete: implemented and tested.
5. A05 complete: implemented and tested.
6. A06 complete: implemented and tested.
7. A07 complete: implemented and tested.
8. A08 complete: implemented and tested.
9. A09 complete: implemented and tested.
10. A10 complete: implemented and tested.

View File

@@ -0,0 +1,70 @@
# OWASP Verification Evidence Log Template
## Run metadata
- Date:
- Environment: `local` | `staging` | `production`
- App/API version (git SHA):
- Operator:
## Environment flags
- `NODE_ENV`:
- `AUTH_DISABLED`:
- `ALLOW_INSECURE_AUTH_FOR_DEV`:
## Commands executed
1.
```bash
# command
```
Output summary:
2.
```bash
# command
```
Output summary:
3.
```bash
# command
```
Output summary:
## Results
- A01 protected route unauthenticated check: `pass` | `fail`
- A01 spoofed header check: `pass` | `fail`
- A01 admin rollover exposure check: `pass` | `fail`
- A01 automated suite (`auth` + `account-delete` + `admin-rollover`): `pass` | `fail`
- A02 dedicated suite (`security-misconfiguration`): `pass` | `fail`
- A03 dedicated suite (`software-supply-chain-failures`): `pass` | `fail`
- A04 dedicated suites (`cryptographic-failures*`): `pass` | `fail`
- A05 dedicated suite (`injection-safety`): `pass` | `fail`
- A06 dedicated suite (`insecure-design`): `pass` | `fail`
- A07 dedicated suites (`auth.routes` + `identification-auth-failures`): `pass` | `fail`
- A08 dedicated suite (`software-data-integrity-failures`): `pass` | `fail`
- A09 dedicated suite (`security-logging-monitoring-failures`): `pass` | `fail`
- A10 dedicated suite (`server-side-request-forgery`): `pass` | `fail`
- Non-DB security suite (`SECURITY_DB_TESTS=0`): `pass` | `fail`
- DB security suite (`SECURITY_DB_TESTS=1`): `pass` | `fail`
## Findings
- New issues observed:
- Regressions observed:
- Follow-up tickets:
## Residual Risk Review
- Reviewed `residual-risk-backlog.md`: `yes` | `no`
- Items accepted for this release:
- Items escalated/blocked:
## Sign-off
- Security reviewer:
- Engineering owner:
- Decision: `approved` | `blocked`

View File

@@ -0,0 +1,107 @@
# Post-Deployment Verification Checklist
Use this after every deploy (staging and production).
## Preconditions
1. Deployment completed successfully.
2. Migrations completed successfully.
3. Correct environment flags:
- `AUTH_DISABLED=false`
- `ALLOW_INSECURE_AUTH_FOR_DEV=false`
4. Test DB preflight (for DB-backed suites):
- `TEST_DATABASE_URL` points to a reachable PostgreSQL instance.
- Example quick check:
```bash
echo "$TEST_DATABASE_URL"
```
Expected:
- single valid URL value
- host/port match the intended test database (for local runs usually `127.0.0.1:5432`)
## A01 smoke checks
Replace `${API_BASE}` with your deployed API base URL.
### 1) Protected route requires auth
```bash
curl -i "${API_BASE}/dashboard"
```
Expected:
- HTTP `401`
- response body includes `UNAUTHENTICATED`
### 2) Spoofed identity header is ignored
```bash
curl -i -H "x-user-id: spoofed-user-id" "${API_BASE}/dashboard"
```
Expected:
- HTTP `401`
### 3) Admin rollover is not publicly callable
```bash
curl -i -X POST "${API_BASE}/admin/rollover" \
-H "Content-Type: application/json" \
-d '{"dryRun":true}'
```
Expected:
- HTTP `403`
## A09 smoke checks
### 4) Security events are emitted for failed auth attempts
Trigger a failed login attempt:
```bash
curl -i -X POST "${API_BASE}/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"nonexistent@example.com","password":"WrongPass123!"}'
```
Expected:
- HTTP `401`
- API logs include a structured `securityEvent` for `auth.login` with `outcome=failure`
- log entry includes `requestId`
## A10 smoke checks
### 5) Production origin configuration is public and non-local
Verify production env/config:
- `APP_ORIGIN` uses public HTTPS host (not localhost/private IP ranges)
Expected:
- API boots successfully with production env validation.
## Automated regression checks
Run in CI against a prod-like environment:
```bash
cd api
npm test -- tests/auth.routes.test.ts tests/access-control.account-delete.test.ts tests/access-control.admin-rollover.test.ts
SECURITY_DB_TESTS=0 npx vitest run -c vitest.security.config.ts
SECURITY_DB_TESTS=1 npx vitest run -c vitest.security.config.ts
```
Expected:
- all tests pass
Note:
- A06/A07 runtime suites require PostgreSQL availability.
- `SECURITY_DB_TESTS=0` runs non-DB security controls only.
- `SECURITY_DB_TESTS=1` includes DB-backed A06/A07 suites.
## Sign-off
1. Record outputs in `evidence-log-template.md`.
2. Review open residual risks in `residual-risk-backlog.md`.
3. Mark release security check as pass/fail.

View File

@@ -0,0 +1,26 @@
# OWASP Residual Risk Backlog
Last updated: March 2, 2026
Use this file to track non-blocking hardening items that remain after automated controls pass.
## Open items
| ID | OWASP | Residual risk | Status |
|---|---|---|---|
| RR-001 | A01 | Add explicit authorization integration tests for all future admin-only routes (deny-by-default coverage expansion). | Open |
| RR-002 | A02 | Add runtime CSP and full security-header verification from deployed edge stack (not only config checks). | Open |
| RR-003 | A03 | Add stronger supply-chain provenance controls (digest pinning, SLSA attestations, artifact signing). | Open |
| RR-004 | A04 | Add key rotation runbook validation and automated stale-key detection checks. | Open |
| RR-005 | A05 | Add static taint analysis or Semgrep policy bundle in CI for command/SQL injection sinks. | Open |
| RR-006 | A06 | Add abuse-case tests for account recovery and verification flows under distributed-IP pressure. | Open |
| RR-007 | A07 | Add MFA/WebAuthn roadmap tests once MFA is implemented; currently password+lockout only. | Open |
| RR-008 | A08 | Add signed backup manifests and restore provenance verification for operational artifacts. | Open |
| RR-009 | A09 | Add alerting pipeline assertions (SIEM/webhook delivery) in pre-prod smoke tests. | Open |
| RR-010 | A10 | Add egress firewall enforcement tests to complement application-layer SSRF guards. | Open |
## Close criteria
1. A concrete control is implemented and validated by an automated test or policy gate.
2. Evidence is attached in `evidence-log-template.md`.
3. Owning team marks item as Closed with date and link to implementation PR.