8.1 KiB
phase, fixed_at, review_path, iteration, findings_in_scope, fixed, skipped, status
| phase | fixed_at | review_path | iteration | findings_in_scope | fixed | skipped | status |
|---|---|---|---|---|---|---|---|
| 16-ci-dependency-audit-and-security-checks | 2026-06-13T00:00:00Z | .planning/phases/16-ci-dependency-audit-and-security-checks/16-REVIEW.md | 1 | 9 | 6 | 3 | partial |
Phase 16: Code Review Fix Report
Fixed at: 2026-06-13 Source review: .planning/phases/16-ci-dependency-audit-and-security-checks/16-REVIEW.md Iteration: 1
Summary:
- Findings in scope: 9 (CR-01; WR-01..WR-05; IN-01..IN-04 — IN-04 carries no fix)
- Fixed: 6 (CR-01, WR-01, WR-02, WR-03, WR-04, WR-05, plus IN-01)
- Skipped: 3 (IN-02, IN-03, IN-04 — all optional/no-action)
IN-01 was applied inside the CR-01 commit (same file, scripts/check-audit.mjs),
so it does not get a standalone commit line below but is counted as fixed.
Validation
All validation run inside the isolated review-fix worktree:
pnpm lint— PASS (apps/api + apps/pwa,--max-warnings 0). Note: the project lint scope does not includescripts/*.mjs, so the edited.mjsscripts are syntax-checked vianode -cinstead (all pass).pnpm typecheck— PASS (apps/api + apps/pwatsc --noEmit, PWA e2e tsconfig).node --test scripts/__tests__/check-audit.test.mjs— 8 pass / 0 fail (includes 3 new expired-waiver / isWaived assertions for CR-01).python3 -c 'yaml.safe_load(...)'for.gitea/workflows/ci.ymland.gitea/workflows/publish.yml— both parse OK after edits.- Working tree clean after all commits (no stray/uncommitted changes).
The API DB-integration tests (tests/routes/lists.test.ts,
tests/lib/listAccess.test.ts, etc.) fail with ER_ACCESS_DENIED in this
environment because no dev MariaDB is reachable. That is pre-existing/environmental
and unrelated to these fixes; no new test failures were introduced.
Fixed Issues
CR-01: Audit-waiver expires field is decorative — expired waivers never re-block
Files modified: scripts/check-audit.mjs, scripts/__tests__/check-audit.test.mjs
Commit: 4bb205f
Applied fix: Added an exported isWaived(adv, allowlist) predicate. An allowlist
entry with no expires, or a future expires, waives the advisory; an entry whose
expires is in the past (<= Date.now()) is treated as absent so the High/Critical
advisory re-blocks. Applied isWaived in BOTH selectBlocking and
partitionAdvisories (replacing the bare !allowlist[...] checks). Added three
unit tests: expired waiver re-blocks via selectBlocking, expired waiver re-blocks
via partitionAdvisories, and a direct isWaived truth-table test (future expiry →
waived, past expiry → not waived, no-expiry → waived, missing → not waived). All 8
tests pass.
WR-01: github.base_ref interpolated into a shell command (script-injection vector)
Files modified: .gitea/workflows/ci.yml
Commit: 26a6b2e
Applied fix: Bound github.event.pull_request.base.sha, head.sha, and
github.base_ref through an env: block (PR_BASE_SHA, PR_HEAD_SHA,
PR_BASE_REF) on the "Probe PR base/head SHA" step. The run: body now references
only the already-quoted shell variables — no ${{ ... }} context interpolation
inside the script. The merge-base fallback uses git rev-parse "origin/$PR_BASE_REF".
WR-03: HEAD_SHA has no fallback while BASE_SHA does — asymmetric defense
Files modified: .gitea/workflows/ci.yml
Commit: 26a6b2e (committed together with WR-01 — same step in the same file)
Applied fix: Added a symmetric head fallback (if [ -z "$HEAD_SHA" ]; then HEAD_SHA=$(git rev-parse HEAD); fi) and an echo "Secret-scan range: ${BASE_SHA}..${HEAD_SHA}" line before the gitleaks invocation so the scanned range
is logged rather than relying on git's A.. → A..HEAD default.
WR-02: Boot-smoke false-PASS if a regressed image emits ≥20 lines before binding
Files modified: .gitea/workflows/publish.yml
Commit: 3daa351
Applied fix: Captured docker run output into OUT=$(...) and read EXIT=$?
from the docker command directly (no | head -20 in the exit-bearing command), so a
chatty-but-booting image can no longer SIGPIPE docker to exit 141 and false-PASS.
head -20 is now applied only to the printed echo "$OUT". Treat both 0 and 124
as FAIL ("did not refuse boot"). Added a positive belt-and-suspenders assertion:
the output must contain DEV_AUTH_BYPASS=true is set in a production environment
(the exact D-08 guard marker from bootGuards.ts), so a refusal for an unrelated
reason cannot masquerade as the guard working.
Logic note: this change alters the PASS/FAIL decision logic of a security smoke test. The shell logic was reviewed against the guard marker string, but the actual container behavior under the forbidden env is not exercisable in this environment (no docker daemon / built image). Recommend a human confirm the smoke step on a real publish run.
WR-05: Static .dockerignore assertions use unquoted-regex grep (false-positive prone)
Files modified: .gitea/workflows/publish.yml
Commit: 3daa351 (committed together with WR-02 — same file)
Applied fix: Switched the per-pattern assertion to comment-stripped, fixed-string
matching: grep -v '^[[:space:]]*#' .dockerignore | grep -qF "$pattern". Patterns
are no longer treated as regexes (.env can't match denv) and a commented-out
rule (# .env was here) no longer satisfies the check. Failure message updated to
"missing active rule".
WR-04: AUDIT-ADVISORY tier in the outdated report is effectively dead code
Files modified: scripts/check-outdated.mjs
Commit: 3e609b2
Applied fix: Took the SAFE relabel option (no risky full-tree rewrite). Renamed
the tier from AUDIT-ADVISORY to OUTDATED-WITH-ADVISORY and documented, in the
file header, the inline classification comment, and the printed header text, that it
only matches outdated direct deps against advisory module_names (most advisories
are on transitive deps, so it rarely fires) and that the authoritative advisory gate
is check-audit.mjs. The report still always exits 0 (advisory-only). The
audit-parse-failure warning message was updated to the new tier name.
Skipped Issues
IN-02: pnpm audit --json is run twice per CI security job
File: scripts/check-audit.mjs:84, scripts/check-outdated.mjs:65
Reason: Skipped — explicitly optional and out of v1 performance scope per REVIEW.md
("Out of v1 performance scope and harmless"). The suggested fix (pipe one audit pass
to both scripts via stdin/arg, or merge the two scripts) is a structural change to
script interfaces and CI invocation with no correctness benefit; applying it here
would be speculative scope creep.
Original issue: Both scripts independently spawn pnpm audit --json, doubling
the audit work in the security job.
IN-03: outdated-pins.json reasons are not cross-checked against the audit allowlist
File: scripts/outdated-pins.json / scripts/audit-allowlist.json
Reason: Skipped — explicitly optional ("Documentation-level coupling only").
Both JSON files are flat maps that the consuming scripts iterate directly:
check-outdated.mjs reads pins[pkgName] as a pin reason, and check-audit.mjs
reads allowlist[github_advisory_id]. Injecting a meta __note/cross-reference key
risks the consumers misreading it as real data (a __note pin would be treated as a
pin reason if a package were ever named __note). The safer choice is to leave the
data files as pure data rather than add inert-but-fragile meta keys. The suggested
lint-step variant is net-new tooling, out of scope for a review fix.
Original issue: Two independent suppression lists with no linkage between a
pinned package and an audit waiver for the same package.
IN-04: expand.test.ts is in scope but unrelated to this CI/security phase
File: apps/api/tests/broker/expand.test.ts
Reason: Skipped — no action required. REVIEW.md states "Fix: None." The
reviewer found no defects; the file appears in the review set only because it was
touched/moved and is orthogonal to this phase.
Original issue: Well-constructed test noted for completeness; no defect.
Fixed: 2026-06-13 Fixer: Claude (gsd-code-fixer) Iteration: 1