Skip to content

security-adversarial: gate CI on npm audit production findings (issue #43) - #46

Merged
ruvnet merged 2 commits into
mainfrom
dream/2026-08-28-security-adversarial
Sep 7, 2026
Merged

security-adversarial: gate CI on npm audit production findings (issue #43)#46
ruvnet merged 2 commits into
mainfrom
dream/2026-08-28-security-adversarial

Conversation

@ruvnet

@ruvnet ruvnet commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Nightly Dream Cycle, 2026-08-28. DEEP=security-adversarial, SCAN=redblue,supply-chain. Full report: docs/dream-cycle/2026-08-28-security-adversarial-report.md. Issue: #45.

Hypothesis

Given this repo's CI pipeline, which currently reports npm audit findings without gating on them, when a deterministic classifier is added that parses npm audit --omit=dev --json output and fails CI only on a high/critical finding within that production-scoped report, then CI should correctly distinguish reachable production risk from dev-toolchain risk — subject to: no existing test modified/weakened, no re-derivation of npm's own dependency-graph reachability computation, zero-false-negative against a synthetic high/critical finding, zero-false-positive against tonight's real dev-only findings. Frozen before implementation.

Candidate

+373/-0 across 6 files (2 new source files, 1 new report, 3 small edits: CLI wiring, CLI tests, CI workflow). One conceptual change: packages/cli/src/auditgate.ts (pure classifyAuditGate()) wired into a new dream-machine audit-gate CLI command and a new dependency-security CI job.

Evaluation Receipt

Real evaluator: npm test (vitest, this repo's own bench entrypoint).

Baseline (parent 7933c359) Candidate
Tests 98 111 (+13, 0 removed/modified)
Result 98 passed 111 passed
Lint clean
Build clean (all 6 packages)

Live end-to-end receipt (real repo data):

$ npm audit --omit=dev --json > /tmp/audit-prod.json
$ node packages/cli/dist/bin.js audit-gate --path /tmp/audit-prod.json
audit-gate: clear — 0 findings at any severity (critical=0 high=0 moderate=0 low=0)
exit code: 0

$ node packages/cli/dist/bin.js audit-gate --path /tmp/audit-full.json   # dev-inclusive, same night
audit-gate: blocked — 2 critical + 1 high finding(s) in this audit scope (critical=2 high=1 moderate=3 low=2)
exit code: 1

The second call proves the gate is not a rubber stamp — CI itself only ever points it at the --omit=dev report.

Baseline

Parent commit 7933c3599abe22df5290f4609d1f93f598feb3de, 98/98 tests passing, clean build.

Darwin Lineage

Not run — DARWIN=not-applicable. Probed live tonight (npx @metaharness/darwin evolve --sandbox mock → real leaderboard, winner g2_v5, delta +0.110, LIVE), but no evolvable population exists for a single, already-minimal pure classifier with one fixed policy boundary — same rationale as 2026-08-13/ADR-0002 and 2026-08-18/PR #19.

Evidence

Reproduced live tonight (grade A, first-hand): npm audit --omit=dev --json on this repo's real lockfile → metadata.vulnerabilities all-zero, metadata.dependencies.prod: 13. npm audit --json (dev-inclusive) → 2 critical, 1 high, 3 moderate, 2 low — matches issue #37's independently-reported findings exactly (GHSA-5xrq-8626-4rwp critical Vitest UI arbitrary file read, GHSA-fx2h-pf6j-xcff high Vite server.fs.deny bypass, plus esbuild/eslint-plugin-kit moderate/low). Full evidence trail with grades in the committed report.

Reward-Hack Check

Self-adversarial (solo session, no separate critic agent available):

  • Does not touch any existing test, gold answer, threshold, or the bench/darwin evaluator entrypoints.
  • Delegates production-reachability classification to npm's own --omit=dev dependency-graph walk rather than re-deriving it — avoiding exactly the false-positive/negative class of bug PR security-adversarial: detect unpinned npx supply-chain risk in evaluator entrypoints #19's independent critic caught in a hand-rolled npx-pinning detector on 2026-08-18.
  • Malformed/partial input (missing severity counts, non-audit JSON, npm error payloads) classifies as malformed (exit 2), never silently clear (exit 0) — dedicated tests for empty object, null/undefined, non-audit shape, and partial-counts shape.
  • CI's npm audit --omit=dev --json ... || true only survives npm audit's own nonzero exit on any finding; the gate command itself determines the job's real (non-rubber-stamped) exit code.
  • Boundary cases (single high with all else 0, single critical with all else 0, moderate/low-only) independently tested to block or clear correctly.

Security Review

No prompt-injection surface (pure JSON parsing, no LLM calls). No credential exposure, no new network I/O. New filesystem I/O is a single local read of a caller-supplied JSON path (io.readFile), consistent with every other CLI command's I/O surface. No change to io.exec's actual shell-exec path.

Regression Analysis

0 pre-existing tests modified or removed. All 98 baseline tests still pass unchanged; 13 new tests added (9 pure-classifier + 4 CLI-dispatch).

ADR

None. This extends ADR-0002's "classify, don't silently trust or silently fix" precedent within the existing architecture (a new CI job gating on an existing classification primitive), rather than introducing a new invariant.

Gist

No gh CLI or gist-creation tool available this session (same as 2026-08-13/2026-08-18). Report committed at docs/dream-cycle/2026-08-28-security-adversarial-report.md. GIST=LOCAL.

Issue

#45

Witness

report_sha256 : 409d5fc4902ebdaec272ee495583a56cd95cc18b2077097c82daa3e4045bdc9e
session_commit: 7933c3599abe22df5290f4609d1f93f598feb3de
witness       : fe3d8a92497e59700481b6ce27ba5fec39804c7f61fa18f7f6e2a3a52d7d3caa

Verify: sha256sum docs/dream-cycle/2026-08-28-security-adversarial-report.md, then printf '%s%s' "<that hash>" "7933c3599abe22df5290f4609d1f93f598feb3de" | sha256sum must equal the witness above. Confirmed tonight via dream-machine witness verify (✓ VALID).

Merge Policy

Draft — human review required. This PR does not carry the automerge-safe label: it adds a new CI job (gate infrastructure), which the repo's own merge policy treats as human-review-only regardless of risk level. The session never merges and never applies that label itself.


Generated by Claude Code

…43)

Nightly Dream Cycle, 2026-08-28. DEEP=security-adversarial, SCAN=redblue,supply-chain.

npm ci reports 8 dependency vulnerabilities (2 critical, 1 high, 3 moderate,
2 low) but CI does not gate on any of them (issue #43). `npm audit --omit=dev`
confirms live tonight that all 8 live in the vitest/vite/esbuild/eslint dev
toolchain — 0 of the 13 production dependencies are implicated.

Adds a deterministic classifyAuditGate() + `dream-machine audit-gate` CLI
command + a new CI job that fails only on a reachable high/critical
*production* finding (npm audit --omit=dev), and reports (never fails) on
dev-only findings. Delegates production-reachability classification to npm's
own dependency-graph walk rather than re-deriving it, avoiding the
false-positive/negative class of bug PR #19's critic caught in a hand-rolled
detector.

npm test: 98 -> 111 (+13, 0 removed/modified). Full report:
docs/dream-cycle/2026-08-28-security-adversarial-report.md
#46)

ACCEPT — audit-gate CI job scoped to npm audit --omit=dev, npm test 98->111, 0 regressions.
@ruvnet
ruvnet force-pushed the dream/2026-08-28-security-adversarial branch from 40eab0d to c4c3907 Compare September 7, 2026 16:40
@ruvnet
ruvnet marked this pull request as ready for review September 7, 2026 16:40
@ruvnet
ruvnet merged commit 1559c74 into main Sep 7, 2026
4 of 9 checks passed
ruvnet added a commit that referenced this pull request Sep 7, 2026
#96)

Landing the long-stale dream-cycle backlog (#21/#29/#33/#35/#42/#46) required
rebasing branches whose conflicts were mostly additive, but a mechanical union
of the conflict hunks cut across syntactic boundaries in several places and
left main red. This repairs all of it:

- ledger/src/index.ts: `pendingFindings` and the closing brace of
  LearningSignalsOptions landed inside daysBetween()'s body; field moved back
  into the interface and the function restored.
- ledger|compile|cli test files: five `it(...)` blocks lost their closing
  `});` at the seam between the two sides' test sets.
- cli/src/index.test.ts: duplicated import block collapsed.
- cli/src/index.ts: `ledger signals` emitted TWO JSON objects (one for
  --merged, one for --pending) instead of one combined result, so
  JSON.parse(out) threw for every signals test; both option sets now feed a
  single learningSignals() call. Duplicated help entry collapsed.
- witness/src/index.ts: #35's `termination` and #42's `trace-replay` each
  export their own `canonicalJson` (TS2308). They are not interchangeable --
  trace-replay's does WeakSet cycle detection and takes `unknown`,
  termination's is typed to JsonValue -- so trace-replay is now re-exported
  explicitly without it rather than silently promoting either.

Verified: typecheck clean, 585/585 vitest, `npm run check` 80/81 (the one
failure is the macOS-only /tmp symlink case in mission.test.mjs, which this
repo's Linux-only CI does not exercise).


Claude-Session: https://claude.ai/code/session_01Ff2xRKvYrqXJhefvcapfE1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants