SEC-01/02, DEP-01/02, IMG-01/02/03, CI-03. Wave 1: image-hygiene runtime guard (TDD), audit+outdated wrappers (TDD), eslint-plugin-security fold, gitleaks config+baseline+.dockerignore. Wave 2: ci.yml security job + gate wiring, publish.yml hygiene assertions + boot-smoke. esbuild GHSA-gv7w-rqvm-qjhr waivered in 16-02 before the gate goes live.
8.1 KiB
phase, slug, status, nyquist_compliant, wave_0_complete, created
| phase | slug | status | nyquist_compliant | wave_0_complete | created |
|---|---|---|---|---|---|
| 16 | ci-dependency-audit-and-security-checks | draft | true | false | 2026-06-12 |
Phase 16 — Validation Strategy
Per-phase validation contract for feedback sampling during execution.
This phase is mostly CI/Docker/security wiring. Only two artifacts carry unit-testable
pure logic — the boot guard (assertNotDevBypassInProduction()) and the audit-wrapper
filter (check-audit.mjs). Everything else is verified by file-assertion, pnpm lint,
or a CI-run / boot-smoke that is exercised after merge.
Test Infrastructure
| Property | Value |
|---|---|
| Framework | Vitest (apps/api) for the boot guard; node --test for the root-level audit wrapper |
| Config file | apps/api/vitest.config.ts; root scripts use no config (node --test) |
| Quick run command | pnpm --filter @familysync/api test -- --run tests/lib/bootGuards.test.ts |
| Full suite command | pnpm --filter @familysync/api test && node --test scripts/__tests__/check-audit.test.mjs |
| Estimated runtime | ~15 seconds |
Sampling Rate
- After every task commit: Run the relevant quick command (boot guard unit test, or
node --testfor the audit wrapper, orpnpm lintfor the eslint fold) - After every plan wave: Run the full suite command
- Before
/gsd-verify-work: Full API suite green +pnpm lintgreen + (post-merge) publish boot-smoke PASS - Max feedback latency: 60 seconds
Per-Task Verification Map
| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---|---|---|---|---|---|---|---|---|---|
| 16-01-01 | 01 | 1 | IMG-01 | T-16-02 | Failing test pins guard exit(1) on prod+bypass | unit | pnpm --filter @familysync/api test -- --run tests/lib/bootGuards.test.ts |
❌ W0 | ⬜ pending |
| 16-01-02 | 01 | 1 | IMG-01 | T-16-02 | Guard exits 1 on NODE_ENV=production + DEV_AUTH_BYPASS=true; inert otherwise | unit | pnpm --filter @familysync/api test -- --run tests/lib/bootGuards.test.ts |
❌ W0 | ⬜ pending |
| 16-01-03 | 01 | 1 | IMG-01 | T-16-01 | Production image bakes NODE_ENV=production | file-assert | grep -c "ENV NODE_ENV=production" apps/api/Dockerfile (==1, in production stage) |
✅ | ⬜ pending |
| 16-02-01 | 02 | 1 | DEP-01 | T-16-04/T-16-05 | esbuild High advisory waived with reason+reviewer before gate goes live | file-assert | node -e "require('./scripts/audit-allowlist.json')['GHSA-gv7w-rqvm-qjhr']" |
❌ W0 | ⬜ pending |
| 16-02-02 | 02 | 1 | DEP-01 | T-16-04 | Wrapper blocks unwaived High+Critical, honors allowlist | unit | node --test scripts/__tests__/check-audit.test.mjs |
❌ W0 | ⬜ pending |
| 16-02-03 | 02 | 1 | DEP-02 | T-16-06 | Outdated report tiered, pin-aware, always exit 0 | behavior | node scripts/check-outdated.mjs; test $? -eq 0 |
❌ W0 | ⬜ pending |
| 16-03-01 | 03 | 1 | SEC-02 | T-16-08 | eslint-plugin-security registered as blocking errors | file-assert | grep -q pluginSecurity eslint.config.js |
✅ | ⬜ pending |
| 16-03-02 | 03 | 1 | SEC-02 | T-16-08/T-16-09 | Lint green with security rules active; suppressions justified | lint | pnpm lint |
✅ | ⬜ pending |
| 16-04-01 | 04 | 1 | SEC-01 | T-16-12 | gitleaks config inherits default ruleset + fixture/env allowlists | file-assert | grep -q useDefault .gitleaks.toml && grep -q vapid .gitleaks.toml |
✅ | ⬜ pending |
| 16-04-02 | 04 | 1 | IMG-02 | T-16-13/T-16-14 | .dockerignore excludes secrets/dev/bulk, preserves apps/api/src | file-assert | grep -q "apps/api/tests" .dockerignore and apps/api/src NOT excluded |
✅ | ⬜ pending |
| 16-04-03 | 04 | 1 | SEC-01 | T-16-11 | Full-history baseline committed, only known fixtures flagged | human-verify + file-assert | test -f scripts/gitleaks-baseline.json + operator confirms findings |
✅ | ⬜ pending |
| 16-05-01 | 05 | 2 | CI-03/SEC-01/DEP-01/DEP-02 | T-16-15/T-16-16/T-16-17 | security job: gitleaks always, audit/outdated code-gated, base.sha probed | yaml-parse | python3 -c "import yaml;yaml.safe_load(open('.gitea/workflows/ci.yml'))['jobs']['security']" |
✅ | ⬜ pending |
| 16-05-02 | 05 | 2 | CI-03 | T-16-15 | gate requires security success (individual result check) | yaml-parse | grep -q needs.security.result .gitea/workflows/ci.yml |
✅ | ⬜ pending |
| 16-06-01 | 06 | 2 | IMG-03 | T-16-20 | build and push are separate steps (assertion seam) | yaml-parse | python3 build/push split assertion |
✅ | ⬜ pending |
| 16-06-02 | 06 | 2 | IMG-03 | T-16-18/T-16-19/T-16-21 | static + boot-smoke assertions between build and push | yaml-parse | python3 assertions-between-build-and-push assertion |
✅ | ⬜ pending |
Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky
Wave 0 Requirements
These are the test/scaffold assets that do not yet exist and must be created by their owning task as the FIRST step (RED) before implementation:
apps/api/tests/lib/bootGuards.test.ts— unit tests forassertNotDevBypassInProduction()(created by 16-01 Task 1, RED)apps/api/src/lib/bootGuards.ts— exported guard function (created by 16-01 Task 2, GREEN)scripts/__tests__/check-audit.test.mjs— unit tests for the audit-wrapper filter logic (created by 16-02 Task 2)scripts/check-audit.mjs— audit wrapper (16-02 Task 2)scripts/check-outdated.mjs— outdated wrapper (16-02 Task 3)scripts/audit-allowlist.json— seeded withGHSA-gv7w-rqvm-qjhr(16-02 Task 1)scripts/outdated-pins.json— intentional-pin reasons (16-02 Task 1).gitleaks.toml— config + allowlists (16-04 Task 1)scripts/gitleaks-baseline.json— full-history scan output, committed (16-04 Task 3).dockerignore— root-level build-context filter (16-04 Task 2)
The boot-guard unit test (bootGuards.test.ts) is the primary Wave 0 test asset. The audit-wrapper
test is the secondary. All other artifacts are config/wiring verified by file-assertion, lint, or CI-run.
Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|---|---|---|---|
| Full-history gitleaks baseline contains only known test fixtures (no real leaked credential) | SEC-01 | Reading repo history for real secrets is a judgment call; a real finding is a security event needing rotation, not auto-approval | 16-04 Task 3 checkpoint: review baseline findings; approve only if every finding is the VAPID fixture / env template |
A PR with a deliberately-planted fake secret in the diff fails CI / gate via the security job |
SEC-01 | Requires opening a throwaway PR against the live Gitea runner | After merge: push a throwaway branch adding a fake AWS-key-shaped string to a tracked file; open PR; confirm gate fails on the security job; close PR |
| A doc-only PR still runs gitleaks but skips audit/outdated | SEC-01 / DEP-01 | Requires a live runner PR to observe step skip behavior | After merge: open a doc-only PR; confirm the security job runs gitleaks (visible in log) and the audit/outdated steps are skipped |
| The published production image refuses to boot with DEV_AUTH_BYPASS=true (boot-smoke PASS in a real publish run) | IMG-03 | Only runs on push-to-main publish against the built image | After this branch merges, watch the Publish workflow run; confirm the boot-smoke step prints PASS and the image publishes |
| base.sha is available on the Gitea runner (Assumption A2 / OQ-1) | SEC-01 | Gitea event-context parity is not probe-confirmed for this field | The 16-05 probe step echoes base.sha/head.sha in the first PR's security-job log; confirm BASE_SHA resolves (event context or merge-base fallback) |
Validation Sign-Off
- All tasks have
<automated>verify or Wave 0 dependencies - Sampling continuity: no 3 consecutive tasks without automated verify
- Wave 0 covers all MISSING references
- No watch-mode flags (uses
--run/node --test, nevervitestwatch) - Feedback latency < 60s
nyquist_compliant: trueset in frontmatter
Approval: approved 2026-06-13