Skip to content

dream(security-adversarial): mcp-scan missed Bash(python *) — live in vertical:ai (evaluated) - #210

Draft
ruvnet wants to merge 1 commit into
mainfrom
dream/2026-08-18-security-adversarial
Draft

dream(security-adversarial): mcp-scan missed Bash(python *) — live in vertical:ai (evaluated)#210
ruvnet wants to merge 1 commit into
mainfrom
dream/2026-08-18-security-adversarial

Conversation

@ruvnet

@ruvnet ruvnet commented Aug 18, 2026

Copy link
Copy Markdown
Owner

MetaHarness Dream Cycle, 2026-08-18. Deep surface: security-adversarial (slot 3). Full detail, receipts, candidate-selection reasoning, adversarial-critique writeup, and sbom/policy scan findings: #209.

Hypothesis

Given scanMcp's host-permission-check loop, which flags only exact wildcard strings (*, mcp__*, mcp__*__*) and a regex over a fixed binary list (rm|curl|wget|sudo|chmod|ssh), when the regex's binary alternation is extended to include arbitrary-code interpreters (python, python3, node, ruby, perl, bash, sh) and a new exact-match branch flags fully unscoped Bash/Bash(*) as a separate HIGH finding, then a real metaharness --template vertical:ai scaffold's shipped Bash(python *) allow-rule should be flagged risky-bash-allow (MEDIUM) where it previously scanned clean, subject to: (a) zero regression in the package's 455-test baseline, (b) no existing test modified, (c) the new checks must not false-positive on narrower, legitimately-scoped Bash(...) rules, (d) diff materially under 300 lines.

Candidate

packages/create-agent-harness/src/mcp-scan.ts's host-permission-check loop gained: (1) an exact-match branch for Bash/Bash(*) → new unrestricted-bash-allow HIGH finding; (2) the existing risky-bash-allow regex's binary alternation extended with python3?|node|ruby|perl|bash|sh. 2 source files (mcp-scan.ts +9/-2), 1 test file (+38 lines, 5 new tests). 44 lines net — well under the 300-line target.

Evaluation Receipt

  • Real-world repro (not synthetic): ran the actual CLI — metaharness --template vertical:ai --host claude-code — producing a real .claude/settings.json containing "Bash(python *)". harness mcp-scan against that output: before fix, 2 findings (no-policy HIGH, unpinned-deps LOW), python grant invisible; after fix, 3 findings, adding [MED] Risky shell allow-rule: Bash(python *). Byte-identical CLI invocation, only the scanner code differed.
  • Deterministic before/after stash repro: with only src/mcp-scan.ts reverted (tests kept), the 5 new tests fail exactly as predicted; the 10 pre-existing tests are unaffected.
  • Fix restored: packages/create-agent-harness 457 passed, 2 skipped (baseline 455/2, 0 regressions), tsc --noEmit clean, full monorepo build clean (no wasm/NAPI degradation tonight).
  • False-positive check: a dedicated test confirms Bash(*) doesn't double-fire the narrower risky-bash-allow id; scoped rules (Bash(npx foo*), Bash(git *)) remain unflagged.

Baseline Comparison

@metaharness/agntcy (the one dependent package that structurally reads mcp-scan's Finding[] shape, per its own from-mcp-scan.ts docstring) re-run clean (1/1) — its badge-derivation logic only maps allow-shell/allow-network/allow-file-write ids to badges and deliberately treats other ids (including the two new ones) as risk findings, not badges, matching existing design intent.

Darwin Lineage

Not run — this is a static-analysis coverage fix to a CLI scanner (fixed regex/exact-match check), not a tunable routing/topology/prompt/tool/tier parameter; no Darwin mutation surface applies (ADR-071 allowlist), same reasoning as the 2026-08-16/17 entries.

Flywheel Evidence

Not applicable in the "ran a flywheel evolution" sense — this candidate hardens @metaharness/create-agent-harness's own static scanner. Durable evidence is the real-CLI + deterministic repro above, committed to docs/dream-cycle/2026-08-18-gist.md.

Reward Hack Check

Purely additive diff — no existing test, fixture, or gold data touched (confirmed independently by the adversarial critic re-reading the diff hunks). No threshold changed, no metric substituted. mcp-scan remains pure/read-only, unchanged tonight.

Security Review

This is the security-review surface tonight. Also checked and clear: no other .claude/settings.json.tmpl template ships an unscoped shell/interpreter allow-rule (vertical_ai was the only offender); redact.ts/secrets.ts untouched; no new dependency or credential/network surface added.

Regression Analysis

Zero regression: 457/457 packages/create-agent-harness tests pass (0 failures, 2 pre-existing skips unaffected), tsc and full repo build clean, dependent-package test unaffected (1/1).

ADR

Appended a dated 2026-08-18 section to ADR-022 (the ADR that documents mcp-scan's checks) rather than minting a new ADR number, matching the 2026-08-13/14/17 precedent for a coverage/completion addition to an existing architectural decision.

Research Gist

No gist-creation tool is available in this session's toolset (matching 2026-08-13 through 08-17) — committed instead: docs/dream-cycle/2026-08-18-gist.md (GIST=LOCAL).

Issue

#209 (full findings, candidate-selection reasoning, adversarial-critique writeup including a self-caught overstatement correction, sbom/policy scan results, competitor review, ledger check/backfill).

Witness

session_commit : 5453c8c990824b54e05f289774e5a8b2cea0a32e
report_sha256  : 007959c64d46f68cfb273142e1b37a175ffd91f53c0c015c1f3be1bfd8517883
witness        : 97587f9cdd8fee182c05ae0f1a47946738195e04ae0126495119f5354c49dc18

Verifier procedure is in the gist's Witness section and was independently self-verified (round-trip reproduced programmatically) before this PR was opened.

Merge Policy

Human review required. Do not self-merge. Do not autonomously promote Flywheel state.

Recommendation: ACCEPT-WITH-CAVEATS — small, dual-repro'd (real CLI + deterministic unit) fix to a genuinely live gap in the repo's own security scanner. One disclosed correction along the way: the initial draft overstated the Bash(*)/bare-Bash half as traceable to a live generator bug at host-config.ts:45; an independent critic traced the real call graph, found that path unreachable through any shipped generator today, and the framing was corrected before this PR opened — that half ships as disclosed defense-in-depth, not as a second live-vulnerability claim. Two comparable-class gaps in sibling tools are disclosed as open next steps, not silently fixed under scope pressure: (1) threat-model never consults mcp-scan's findings, so the same harness can be HIGH on one tool and "clean" on the other; (2) harness sbom has no Cargo/Rust awareness despite --with-wasm legitimately shipping Rust deps in scaffolded harnesses.


Generated by Claude Code

…e) + unrestricted Bash(*) (evaluated)

harness mcp-scan's risky-bash-allow regex omitted arbitrary-code
interpreters (python/node/ruby/perl/bash/sh) - the vertical:ai
template's own settings.json.tmpl ships Bash(python *), unflagged by
any check until now. Also adds a new unrestricted-bash-allow HIGH
finding for bare Bash/Bash(*), disclosed as defense-in-depth rather
than a live generator bug after an independent critic traced the call
graph and found no shipped generator currently writes that exact
string to .claude/settings.json.

Evidence: real (non-synthetic) `metaharness --template vertical:ai`
scaffold repro plus a deterministic before/after stash repro; 15/15
mcp-scan tests, 457/457 full package suite, 0 regressions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R1diHYv256aiVNdEEUMfjG
ruvnet pushed a commit that referenced this pull request Sep 1, 2026
…edger conflict

Main advanced again (5b4ebd3 -> 1ded6f2): #212 (github-actions
shell/YAML injection fix, another backlog item flagged in issue #257)
merged, plus a published-smoke CI gating fix (#263). Real conflict
again in docs/dream-cycle/LEDGER.md: main's real #212 row replaced
this branch's stale "OPEN (backfilled)" placeholder for the same PR.

Resolved by taking main's real #212 row (with its actual receipt/
witness) and updating every row that referenced #212 as still-open
to note its 2026-09-01 merge, narrowing tonight's own row's
still-OPEN list accordingly (now just #207 and #210).

Re-validated: full monorepo build clean; packages/flywheel 72/72; all
7 real committed ReplayBundle/proof-bundle files still verify PASS;
flywheel + host-github-actions + create-agent-harness + web-ui —
558/560 (2 pre-existing skips) green, 0 failures. CI's earlier
packages/avo/rvf-resume.test.ts flake also confirmed non-recurring on
the previous head — not re-checked here as it's unrelated to this
merge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012NE5wk64pND2VpBZmGGkQq
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