Files
familysync/.planning/phases/16-ci-dependency-audit-and-security-checks/16-VALIDATION.md
T
Lucas Berger e039c85a22 docs(16): create phase plan — 6 plans, 2 waves (dep audit, security checks, image hygiene)
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.
2026-06-12 23:23:18 -04:00

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 --test for the audit wrapper, or pnpm lint for the eslint fold)
  • After every plan wave: Run the full suite command
  • Before /gsd-verify-work: Full API suite green + pnpm lint green + (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 for assertNotDevBypassInProduction() (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 with GHSA-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, never vitest watch)
  • Feedback latency < 60s
  • nyquist_compliant: true set in frontmatter

Approval: approved 2026-06-13