Repository navigation
dream(security): #3102 bind ADR-377 caller-identity verification into live authorizeMcpTool chokepoint (evaluated, ACCEPT-with-caveats) - #3103
Conversation
…er row Co-Authored-By: RuFlo <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_015bfVASxkNXKpZbMSLhVU1L
|
CI red on this PR — root-caused, not this PR's: 6 checks failed ( Root
No code fix exists to port — the fix is an Generated by Claude Code |
638362a to
efbf308
Compare
…live authorizeMcpTool chokepoint (evaluated, ACCEPT-with-caveats) authorizeMcpTool() trusted the plain, unsigned CLAUDE_FLOW_PRINCIPAL_ID env var as caller identity with zero verification (OWASP ASI07-class gap). ADR-377 Phase 3 already implemented Ed25519 issueInvocationToken/ verifyInvocationToken but never wired it into a live dispatch path. Binds the two: DualModeOrchestrator mints a per-worker signed InvocationToken at spawn (worker-lifetime TTL, wildcard tool scope -- disclosed reduction from the primitive's original per-call design); authorizeMcpTool verifies it before trusting identity, failing closed on missing/forged/expired tokens. Off by default (CLAUDE_FLOW_MCP_CALLER_AUTH). 6 new deterministic Vitest scenarios plus an independent adversarial critique (fresh session, verdict CONFIRMED-SAFE-WITH-CAVEATS) are recorded in docs/dream-cycle/dream-gist-2026-08-26.md. Darwin skipped (scope mismatch -- binary crypto gate, no tunable parameter space). Co-Authored-By: RuFlo <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_015bfVASxkNXKpZbMSLhVU1L
…scape hatches CLAUDE_FLOW_MCP_CALLER_PUBKEY and CLAUDE_FLOW_MCP_INVOCATION_TOKEN (DualModeOrchestrator's per-worker Ed25519 credential pair, read by resolveMcpCallerIdentity()) weren't registered in audit-env-var-precedence.mjs's KNOWN_ESCAPE_HATCHES, so the audit flagged CLAUDE_FLOW_MCP_CALLER_PUBKEY as a real violation (exit 1). Same no-CLI-flag reasoning as the existing CLAUDE_FLOW_PRINCIPAL_ID entry: these are credentials minted into a spawned worker's own environment, not a value any caller should be able to select via a flag. Verified: audit-env-var-precedence.mjs now exits 0. policy-runtime.test.ts still 18/18. Co-Authored-By: RuFlo <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01RYP825phHMjPRmmjkcAWpq
…ow/security dep The CI Test Suite job runs plain `npm ci` at repo root (no v3 pnpm install step), so it resolves workspace deps strictly from package-lock.json — not from v3/pnpm-lock.yaml, which this PR had already updated. codex/package.json's new @claude-flow/security dependency was missing from package-lock.json's codex entry, so CI's npm ci never linked it and every dual-mode test importing orchestrator.ts failed with "Cannot find package '@claude-flow/security'". Co-Authored-By: RuFlo <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01RYP825phHMjPRmmjkcAWpq
0e4b756 to
d492ea8
Compare
Complements d492ea8 (codex's package-lock.json entry) rather than duplicating it: that commit fixed `npm ci` not *linking* @claude-flow/security for codex at all ("Cannot find package"); this fixes the fact that even where it IS linked (codex now, @claude-flow/cli already), nothing builds its dist/ before tests run, so importers hit ERR_MODULE_NOT_FOUND on dist/index.js instead. This was already a known, pre-existing gap for @claude-flow/cli: v3/@claude-flow/cli/__tests__/policy-runtime.test.ts has carried a scripts/ci-test-baseline.txt entry for exactly this reason since before this PR existed. dual-mode.test.ts and dual-mode-stdin-2947.test.ts (added by the caller-identity candidate on this branch) newly trip the same gap in @claude-flow/codex, confirmed by diffing main's own Test Suite ratchet failures (3, pre-existing, unrelated oauth/publisher plugin tests) against this branch's (5: the same 3 plus these 2). Adding new files to ci-test-baseline.txt to paper over this is explicitly forbidden by that file's own header ("Never add entries to make a regression green"), and gutting the tests to avoid the import would remove real coverage of the orchestrator/security integration point. Real fix: build @claude-flow/security before "Run all tests", using its own existing `npm run build` (tsc && verify-oauth-exports.mjs). Verified locally end-to-end by removing both dist/ and the stale tsconfig.tsbuildinfo (simulating a fresh CI checkout, since both are gitignored) -- the build step produces a full dist/, both new codex test files pass, and (bonus, explicitly allowed by the baseline file's own "remove entries when fixed" rule) the pre-existing policy-runtime.test.ts baseline entry is now consistently green too, so it's removed here. Co-Authored-By: RuFlo <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_015bfVASxkNXKpZbMSLhVU1L
|
The remaining 3 (
Generated by Claude Code |
1. Hypothesis
Frozen before evaluation began; not modified after seeing results.
2. Candidate
v3/@claude-flow/security/src/mcp-caller-identity.ts(ADR-377 Phase 3, merged 2026-07-30) already implements Ed25519issueInvocationToken/verifyInvocationToken— fully tested, but a repo-wide grep confirmed zero production call sites. Last night's research recommended wiring it intoAgentAuthorizationPropagator.checkToolCall— tonight's deep research corrected that mid-pipeline:checkToolCallalso has zero production callers. The actual live chokepoint (used by both stdio and HTTP MCP transports) isauthorizeMcpTool(), whoseidentity.idwasprocess.env.CLAUDE_FLOW_PRINCIPAL_ID ?? 'legacy-cli'— verified nowhere downstream inAgenticPolicyEngine.evaluate().DualModeOrchestratorsets this value for every spawned worker subprocess, a process running arbitrary LLM-driven tool calls.Diff (5 source files + 2 test files, ~345 lines net):
mcp-caller-identity.ts— addedpublicKeyFromHex,encodeTokenEnvelope,decodeTokenEnvelope(purely additive).security/src/index.ts— exported the three new functions.orchestrator.ts—workerEnvironment()mints and setsCLAUDE_FLOW_MCP_INVOCATION_TOKEN/CLAUDE_FLOW_MCP_CALLER_PUBKEYwhen caller-auth is enabled, via a lazily-generated keypair cached once per orchestrator instance (private key never leaves the process).policy-runtime.ts— newresolveMcpCallerIdentity()wired intoauthorizeMcpTool's identity construction; disabled branch is a verbatim copy of the two lines it replaced.codex/package.json— added@claude-flow/securityas an intra-monorepo workspace dependency (not a new third-party package).3. Evaluation Receipt
evaluated: accepted, with disclosed caveats. Real evaluator: Vitest, zero LLM calls. 6 new deterministic scenarios (disabled-baseline-spoofing-succeeds; enabled+no-token→reject; enabled+forged-signature→reject; enabled+expired→reject; enabled+legitimate-token→accept via verified
callerId; orchestrator env-var-gating+round-trip) — all pass post-fix, all correctly fail/error pre-fix viagit stashsource isolation. Full receipt:docs/dream-cycle/dream-gist-2026-08-26.md.4. Baseline Comparison
@claude-flow/securityfull@claude-flow/codexfulldual-mode.test.ts)@claude-flow/clipolicy-runtime.test.ts@claude-flow/clifull suite, diff applied@claude-flow/clifull suite,git stash(clean HEAD)Pre-existing WASM-module-resolution/Docker-integration failures, proven identical with and without this diff by direct stash-and-diff comparison (both by the implementer and independently re-derived by the critic below).
tsc --noEmitclean forsecurity/codex;cli's ~467 pre-existing errors (missing workspace type declarations) are unrelated, none referencepolicy-runtime.ts.5. Darwin Lineage
Skipped — scope mismatch, confirmed via
npx ruvector harness darwin --help: real interface evolves continuous/categorical genome parameters against an LLM-scored bench corpus. A binary Ed25519 signature-verification gate has no such tunable parameter space. Same class of skip as 5 of the last 6 dream-cycle nights.6. Flywheel Evidence
No signed
@metaharness/flywheelbundle — deterministic Vitest evidence, not an LLM-task corpus the replay/verify tooling targets (library-only, no standalone CLI), same as every accepted night since 08-18. Evidence retained as the committed tests + issue #3102 + the gist.7. Reward Hack Check
Independent adversarial critic (fresh session, no authoring context) confirmed: no existing test weakened (diff purely additive in both test files); no gold-value tuning; no cherry-picking; zero LLM/API cost. Critic independently verified the Ed25519 SPKI DER prefix against real
node:cryptooutput byte-for-byte, probed cross-worker token confusion (rejected), probed NUL-byte field-injection intocallerId(rejected — verification always uses structured JSON fields, never re-parses a flat delimited string), and traced every fail-closed branch line-by-line — none falls through to the unverified env var once the flag is on.8. Security Review
This IS the security-sensitive candidate. Critic confirmed: the private key never leaves the
DualModeOrchestratorprocess (only public-key hex and the signed token envelope are exported to workerenv); the existing sensitive-env-var strip loop correctly excludes the two new vars before re-setting them fresh per worker; JSON→base64url envelope encoding is injection-proof by construction.Disclosed, not hidden, residual risk: the per-worker token is a worker-lifetime capability credential (not the originally-designed 30s/single-tool proof) — a leaked copy remains valid for that worker's remaining lifetime. Narrows "any process can freely impersonate any identity" to "only credential theft from an already-compromised worker grants impersonation" — a real reduction, not a complete solution. Two other call sites (
harness-flywheel-runtime.ts:101,metaharness.ts:146,173) still trust the bare env var unconditionally — out of tonight's scope, flagged as follow-up. A pre-existing, unrelated quirk (4 literal NUL bytes as field separators insigningPayload(), present atHEAD) was probed for exploitability by the critic (none found) and correctly left unfixed (out of frozen scope).Verdict: CONFIRMED-SAFE-WITH-CAVEATS (full adversarial review in the gist).
9. Regression Analysis
Zero regressions: off-by-default behavior is provably byte-identical (the disabled branch of
resolveMcpCallerIdentityis a verbatim copy of the two lines it replaced); all pre-existing test assertions across every touched file are unchanged (only additive newdescribe/itblocks); the ~19-file pre-existing CLI failure set is proven identical with/without this diff via stash-and-diff comparison, not assertion.10. ADR
None created — this activates already-decided ADR-377 Phase 3 architecture (implemented, merged, never wired) rather than introducing a new architectural decision. Matches repo convention for scoped correctness/reachability fixes (08-18 hybridSearch, 08-19 MessageBus, 08-24 weightedConsensus, 08-25 PQ distance dispatch).
11. Research Gist
docs/dream-cycle/dream-gist-2026-08-26.md(committed on this branch; no gist-creation MCP tool available in this session, consistent with every dream-cycle night since 2026-08-14). Full SOTA research: 5-role parallel fan-out, ledger check with verified 8-night fates, intelligence/swarm scan findings (CP-WBFT AAAI-2026 reference for the still-openConsensusVote.confidencegap;FederationHub.vote()stale-vote bug andTopologyManager.rebalanceHybrid()adjacency bug both re-confirmed still open), and competitor analysis (LangGraph/AutoGen/CrewAI/OpenAI Agents SDK/MCP spec itself — none verify inter-agent caller identity, a genuine field-wide open gap per two independently-closed-not-planned RFCs).12. Issue
Closes #3102 (full research: 5-role parallel fan-out, ledger check incl. verified fates for the last 8 nights, intelligence/swarm scan findings, competitor comparison).
13. Witness
e21aa352fdc80fd2d3cc4e83404a76a18d118b96a590238de9502914cdcca27a2ceb9426b2cda590c0c4fd4be84cc03f5deefb52fd944eaaea6d669f9c62355bd95c339f97313faa4b999510b454002e85714ee2Verifier procedure: fetch
docs/dream-cycle/dream-gist-2026-08-26.mdfrom this branch, strip the witness table's filled values back to placeholders, SHA-256 it, concatenate with the session commit above, SHA-256 again — result must equal the witness stamp.14. Merge Policy
Human review required. Do not self-merge. Do not autonomously promote Flywheel state.
Generated by Claude Code