Commit Graph
723 Commits
Author SHA1 Message Date
Lucas Berger cb3ae9081f docs(10): fold backlog 999.5 self-service onboarding into CONTEXT (D-07), reconcile with ROADMAP 2026-06-13 13:17:22 -04:00
luckberg efa42801b5 Merge pull request 'ci: skip Docker publish for .gitea/.planning-only pushes' (#16) from quick/260613-fp9-publish-paths-ignore into main
Reviewed-on: #16
2026-06-13 12:17:51 -04:00
Lucas Berger 15fa89d483 docs(quick-260613-fp9): document ci.yml heavy-job paths-filter fix
CI / changes (pull_request) Successful in 3s
CI / fast-checks (pull_request) Successful in 1m36s
CI / api (pull_request) Has been skipped
CI / harness (pull_request) Has been skipped
CI / security (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 2s
2026-06-13 12:13:58 -04:00
Lucas Berger ec85fe026a ci(260613-fp9): fix changes paths-filter so doc/.gitea/.planning PRs skip heavy jobs
dorny/paths-filter combines patterns with Array.some, and picomatch compiles
'!.gitea/**' as 'any path not under .gitea'. The '- !.gitea/**' line (from
quick task 260613-dmw) therefore matched every non-.gitea file — including
.planning/** and *.md — so code=true for doc-only PRs and the heavy api/harness
jobs ran anyway. It also never excluded .gitea (the **/*.yml glob already
matched workflow files), regressing the Phase 15 doc-only skip.

Switch to predicate-quantifier 'every' with negation-only globs: a file counts
as code only if it is outside .gitea/, outside .planning/, and not Markdown.
Verified with picomatch against representative file sets.
2026-06-13 12:13:58 -04:00
Lucas Berger 4d4ffad059 docs(quick-260613-fp9): .gitea/.planning pushes should not trigger a docker image publish
CI / changes (pull_request) Successful in 2s
CI / fast-checks (pull_request) Successful in 1m24s
CI / api (pull_request) Successful in 1m0s
CI / harness (pull_request) Successful in 3m53s
CI / security (pull_request) Successful in 39s
CI / gate (pull_request) Successful in 1s
2026-06-13 11:22:16 -04:00
Lucas Berger cd5a88c8a2 ci(260613-fp9): skip Docker publish for .gitea/.planning-only pushes
Add paths-ignore ['.gitea/**', '.planning/**'] to publish.yml's push
trigger. Doc/CI-only pushes to main (planning docs push straight to main;
workflow edits merge via PR) produce a byte-identical image (.dockerignore
already excludes .planning), so the build + :latest re-push was wasted.
paths-ignore skips only when every changed file matches; mixed code+docs
pushes still publish.
2026-06-13 11:22:05 -04:00
Lucas Berger 7478c0fa50 docs(15): mark Gitea branch-protection checkpoint resolved (verified on main)
Publish / publish (push) Successful in 9s
2026-06-13 11:14:20 -04:00
Lucas Berger 0a28a438a8 docs(16): confirm live-CI human-verify items (gitleaks scan + boot-smoke) from PR #15 run #51/#52
Publish / publish (push) Successful in 8s
2026-06-13 10:26:38 -04:00
luckberg 06238a9781 Merge pull request 'Phase 16: CI dependency audit & security checks' (#15) from gsd/phase-16-ci-dependency-audit-and-security-checks into main
Publish / publish (push) Successful in 1m36s
Reviewed-on: #15
2026-06-13 10:00:59 -04:00
Lucas Berger 9be3b7ba37 docs(quick-260613-dmw): exclude .gitea/** from CI heavy-job paths-filter
CI / changes (pull_request) Successful in 2s
CI / fast-checks (pull_request) Successful in 1m23s
CI / api (pull_request) Successful in 1m1s
CI / harness (pull_request) Successful in 3m51s
CI / security (pull_request) Successful in 39s
CI / gate (pull_request) Successful in 2s
2026-06-13 09:53:25 -04:00
Lucas Berger 2d329a97b2 ci(260613-dmw): exclude .gitea/** from heavy-job paths-filter (treat CI edits like docs)
Workflow-only edits shouldn't pay for the multi-minute MariaDB/integration/harness
suite. A negation '- !.gitea/**' (ordered after the yml/yaml globs) makes CI-config-
only PRs resolve code=false so api/harness skip like docs. fast-checks (format:check
still validates the workflow YAML) + gate continue to gate them. PRs that also touch
app code or lockfile still resolve code=true and run the full suite.
2026-06-13 09:51:41 -04:00
Lucas Berger 8154ba6f35 style(16): apply prettier formatting to satisfy CI format:check
CI / changes (pull_request) Successful in 2s
CI / fast-checks (pull_request) Successful in 1m23s
CI / api (pull_request) Successful in 1m0s
CI / harness (pull_request) Successful in 3m54s
CI / security (pull_request) Successful in 40s
CI / gate (pull_request) Successful in 1s
2026-06-13 09:29:02 -04:00
Lucas Berger c72e013a7b docs(phase-16): complete phase execution
CI / changes (pull_request) Successful in 3s
CI / fast-checks (pull_request) Failing after 57s
CI / api (pull_request) Successful in 58s
CI / harness (pull_request) Successful in 3m59s
CI / security (pull_request) Successful in 40s
CI / gate (pull_request) Failing after 1s
2026-06-13 09:00:12 -04:00
Lucas Berger e0ec4a40a5 docs(16): re-review clean + code review fix report 2026-06-13 08:52:12 -04:00
Lucas Berger bb1e97556d fix(16): fail closed on unparseable audit-waiver expiry date 2026-06-13 08:51:53 -04:00
Lucas Berger 3e609b2550 fix(16): WR-04 relabel/clarify outdated advisory tier (no false promise)
The AUDIT-ADVISORY tier cross-checks pnpm outdated (direct deps) against
advisory module_names (mostly transitive), so it almost never fires.
Relabel to OUTDATED-WITH-ADVISORY and document the limitation in the
header, header text, and inline comment; point to check-audit.mjs as the
authoritative advisory gate. No risky full-tree rewrite.
2026-06-13 08:43:54 -04:00
Lucas Berger 3daa351d70 fix(16): WR-02/WR-05 robust boot-smoke and active .dockerignore checks
WR-02: capture docker run exit directly (not the piped head exit) so a
chatty booting image can't SIGPIPE to 141 and false-PASS; require the
FATAL guard marker in output as a positive assertion.
WR-05: strip comment lines and use anchored fixed-string (grep -qF)
matching so a commented-out rule can't satisfy the hygiene check and
patterns aren't treated as regexes.
2026-06-13 08:43:16 -04:00
Lucas Berger 26a6b2e53f fix(16): WR-01/WR-03 bind PR context via env; symmetric HEAD_SHA fallback
Bind github.base_ref/base.sha/head.sha through env: and reference quoted
shell vars (no inline ${{ }} in run:) to close the script-injection vector.
Add a HEAD_SHA rev-parse fallback mirroring BASE_SHA and echo the final
${BASE_SHA}..${HEAD_SHA} range before gitleaks.
2026-06-13 08:42:40 -04:00
Lucas Berger 4bb205fe0f fix(16): CR-01 enforce audit-waiver expiry; IN-01 realpath isMain
Add isWaived() predicate: a waiver with a past 'expires' date is treated
as absent so the High/Critical advisory re-blocks. Applied in both
selectBlocking and partitionAdvisories. Add expired-waiver unit tests.
isMain now compares fully-resolved real paths (mirrors index.ts).
2026-06-13 08:42:08 -04:00
Lucas Berger 5dd84a2861 docs(16): add code review report 2026-06-13 08:35:28 -04:00
Lucas Berger 9a108a3618 docs(16-06): complete image hygiene CI assertions plan 2026-06-13 08:28:37 -04:00
Lucas Berger 27046dbf92 ci(16-06): add static image-hygiene assertion + boot-smoke before push
- Static assertions step: verifies .dockerignore exists, covers all forbidden
  patterns (.env, node_modules, apps/api/scripts, .git, .planning, apps/api/tests,
  apps/pwa/e2e), and --target production is still pinned in publish.yml (T-16-19)
- Boot-smoke step: runs freshly-built sha_tag image with NODE_ENV=production +
  DEV_AUTH_BYPASS=true; fails if image starts (EXIT==0) or times out (EXIT==124),
  passes on any other non-zero exit — proves D-08 guard fires in shipped image (T-16-18)
- Both steps ordered after build and before push; a hygiene failure blocks publish (T-16-20)
2026-06-13 08:27:13 -04:00
Lucas Berger bc00f3e815 ci(16-06): split publish build and push into separate steps
- Separate 'Build and push' into 'Build production image' (build only) and 'Push image' steps
- Creates insertion point between build and push for hygiene assertions (Task 2)
- Preserves --target production, Dockerfile path, both tags, and root context
- WR-04 immutable-tag-first push ordering preserved in push step
2026-06-13 08:26:32 -04:00
Lucas Berger 6e5086ce5e docs(16-05): complete security job + gate wiring plan 2026-06-13 08:25:00 -04:00
Lucas Berger f0f7d8a9f4 ci(16-05): wire security job into gate aggregator
- gate needs: expanded to include security
- individual needs.security.result check added (requires success, not success-or-skipped)
- security not folded into api/harness for loop — gitleaks always runs so it cannot be skipped
- follows Gitea #31007 individual needs.X.result pattern
2026-06-13 08:23:20 -04:00
Lucas Berger 61b758648f ci(16-05): add security job (gitleaks always; audit/outdated code-gated)
- New security job parallel to fast-checks, needs: [changes], if: pull_request
- actions/checkout@v4 with fetch-depth: 0 (Pitfall 3 — base.sha must be local)
- Probe PR base/head SHA step with git merge-base fallback (A2 / OQ-1)
- Install gitleaks v8.30.1 from GitHub releases (pinned, no actions/cache)
- Secret scan step (no if:) — blocking, uses .gitleaks.toml + baseline
- setup-node/pnpm/audit/outdated steps each gated by needs.changes.outputs.code
2026-06-13 08:22:52 -04:00
Lucas Berger 946dafe5fb docs(16-04): complete gitleaks+dockerignore plan — add SUMMARY, advance state 2026-06-13 08:16:49 -04:00
Lucas Berger bc83495946 chore(16-04): commit gitleaks full-history baseline
- 613 commits scanned, ~23 MB of git history
- Zero findings after allowlisting crypto.test.ts AES test fixture
- Human-verified: single baseline finding (TEST_KEY) was a Vitest fixture, not a real credential
- Baseline is empty JSON array [] — PR-diff scans in 16-05 start from clean known state
2026-06-13 08:14:54 -04:00
Lucas Berger fba22b445b chore(16-04): allowlist crypto.test.ts synthetic AES test key in gitleaks config
- Add 4th [[allowlists]] block for apps/api/tests/broker/crypto.test.ts
- Human-verified: TEST_KEY in Vitest beforeAll is a synthetic AES-256-GCM fixture, not a real credential
- Original 3 allowlists (VAPID fixture, .env.example, .env.spike) intact
2026-06-13 08:14:26 -04:00
Lucas Berger 5819247a01 chore(16-04): add .dockerignore (secrets/dev/bulk, preserve builder inputs)
- Exclude .env/.env.* with !.env.example un-ignore so template survives
- Exclude apps/api/scripts/seed-credential.mjs (defense-in-depth)
- Exclude .git, dist/, node_modules/, tests/, Playwright artifacts
- Exclude .planning/, docs/, editor/OS, CI config, SQL dumps
- Do NOT exclude apps/api/src or apps/pwa/src (builder stage needs them)
- Add NOTE comment on migration .sql files and multi-stage COPY semantics
2026-06-13 05:28:29 -04:00
Lucas Berger 2f1592cc45 chore(16-04): add gitleaks config with fixture + env allowlists
- Add .gitleaks.toml inheriting default ruleset via [extend] useDefault = true
- Allowlist apps/api/tests/fixtures/vapid.ts (test-only VAPID keypair)
- Allowlist .env.example (intentional placeholder template)
- Allowlist apps/api/.env.spike (dev/spike values)
2026-06-13 05:27:54 -04:00
Lucas Berger ab42794c1c docs(16-03): complete eslint-plugin-security plan — add SUMMARY, advance state to plan 4 2026-06-13 05:26:15 -04:00
Lucas Berger 59e49ec3da chore(16-03): triage eslint-plugin-security findings to green
- Disable detect-object-injection globally in eslint.config.js: all hits were
  numeric loop array indices (ranks[i]) — not user-controlled keys; zod guards
  real API input boundaries; justification comment added (T-16-09)
- Add inline eslint-disable for detect-non-literal-fs-filename at 2 sites:
  - apps/api/src/index.ts: realpathSync(process.argv[1]) — runtime entry path, not user input
  - apps/api/tests/broker/expand.test.ts: readFileSync of test fixture path — test-controlled
- pnpm lint exits 0 across both apps with --max-warnings 0
- 14 of 15 security rules remain active at error; no blanket file disables
2026-06-13 05:24:02 -04:00
Lucas Berger 826a23a16c chore(16-03): add eslint-plugin-security to root flat config (D-03)
- Install eslint-plugin-security@3.0.1 to root devDependencies (pinned exact version; 3.0.1 chosen over 4.0.1 for bake time per RESEARCH)
- Add pluginSecurity import to eslint.config.js
- Insert security config block (files: apps/**/*.{ts,tsx}) with configs.recommended spread, placed before prettierConfig which stays last
- ESLint version unchanged at 9.39.4 (D-13-ESLint-PIN)
2026-06-13 05:22:32 -04:00
Lucas Berger 069fe53523 docs(16-02): complete dependency-audit-gate plan — add SUMMARY, advance state to plan 3 2026-06-13 05:20:43 -04:00
Lucas Berger baf2e3ad1b feat(16-02): add check-outdated.mjs advisory-only tiered report
- Classifies outdated packages into four tiers: AUDIT-ADVISORY, MAJOR-BEHIND-INTENTIONAL,
  MAJOR-BEHIND-UNPINNED, ROUTINE-DRIFT
- Reads outdated-pins.json for intentional pin reasons (eslint, @eslint/js, zod, @types/node)
- Cross-checks pnpm audit --json to flag pinned versions with active advisories
- Always exits 0 — never gates the build (D-06)
2026-06-13 05:19:06 -04:00
Lucas Berger 6eb51078e3 feat(16-02): add check-audit.mjs blocking wrapper + unit tests
- Exports selectBlocking() and partitionAdvisories() as pure functions for unit testing
- Main body guarded by import.meta.url check (only runs when invoked directly)
- Uses pnpm audit --json (no --audit-level — Pitfall 1 honored)
- Exits 1 on unwaived High/Critical; exits 0 with advisory report on moderate/low
- All 5 unit tests pass (node --test)
2026-06-13 05:18:19 -04:00
Lucas Berger 7ac8b19a76 test(16-02): add failing tests for check-audit.mjs filter logic
- Tests four behavioral cases: unwaived High → blocking, waived High → not blocking,
  moderate/low only → not blocking, no advisories → not blocking
- Uses node:test + node:assert (no extra dependencies)
- Fails at RED: check-audit.mjs does not yet exist
2026-06-13 05:17:36 -04:00
Lucas Berger 0f101bdd39 chore(16-02): seed audit allowlist (esbuild GHSA waiver) + outdated pin reasons
- audit-allowlist.json: GHSA-gv7w-rqvm-qjhr waiver (esbuild dev transitive via drizzle-kit/vitest/vite; not in production runtime)
- outdated-pins.json: intentional pin reasons for eslint, @eslint/js, zod, @types/node
2026-06-13 05:17:13 -04:00
Lucas Berger b67d757f87 docs(16-01): complete boot-guard plan — add SUMMARY, advance state to plan 2 2026-06-13 05:15:35 -04:00
Lucas Berger 5b4f32a649 chore(16-01): bake ENV NODE_ENV=production into production Dockerfile stage
- Add ENV NODE_ENV=production in production stage after WORKDIR /app/apps/api
- Engages the devBypass.ts hard guard so DEV_AUTH_BYPASS can never inject
  DEV_USER in the shipped image, even if accidentally set (D-07)
- No other stage (base/builder/dev/pwa-builder) is affected
2026-06-13 05:14:28 -04:00
Lucas Berger c2ffd1c1b2 feat(16-01): add boot-time refuse-to-boot guard for dev-bypass in production
- Create apps/api/src/lib/bootGuards.ts with assertNotDevBypassInProduction()
- Guard exits non-zero when NODE_ENV=production AND DEV_AUTH_BYPASS=true (D-08)
- Wire import + call as first statement in isMainModule() block in index.ts
- 3/3 unit tests pass, typecheck green
2026-06-13 05:14:08 -04:00
Lucas Berger 8414e891b3 test(16-01): add failing tests for boot-time dev-bypass guard
- Three test cases: prod+bypass=exit(1), dev+bypass=no-exit, prod+unset=no-exit
- Fails with Cannot find module (src/lib/bootGuards.ts absent) — RED confirmed
2026-06-13 05:12:33 -04:00
Lucas Berger 0d6366cd56 docs(16): create phase plan 2026-06-12 23:25:48 -04:00
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
Lucas Berger bfc93584d7 docs(16): add validation strategy 2026-06-12 23:08:05 -04:00
Lucas Berger 4929a79eee docs(16): research phase — gitleaks, eslint-plugin-security, pnpm audit/outdated, image hygiene 2026-06-12 23:06:20 -04:00
Lucas Berger 7d30e0bfab docs(state): record phase 16 context session 2026-06-12 22:50:57 -04:00
Lucas Berger 75b82f472c docs(16): capture phase context 2026-06-12 22:50:52 -04:00
Lucas Berger 2d50c7b515 docs(roadmap): add Phase 16 (CI dep audit, security & image hygiene); fold in + remove backlog 999.17
Publish / publish (push) Successful in 6s
2026-06-12 22:08:29 -04:00