- smoke-plugin-registry-signature.mjs: follow the verifier rename to
registrySignatureFailure and require the fail-closed call site
`if (failure) return this.fallbackToBuiltIn(`, keeping full #1922 coverage.
- verifyEd25519Signature verifies in RFC 8032 strict mode, and
isPlaceholderSigningKey rejects every small-order or off-curve key, not
only the all-zero one.
- An unverified registry claims no official plugins and no verified
authors; MCP transfer_plugin-* results carry the registry status.
- Tests no longer assume the shipped trust anchor stays a placeholder.
- Document the strict-mode asymmetry for unlisted packages in --help.
The live plugin registry CID is unsigned, so discovery always fell back to
the built-in plugin list, whose entries all claim trustLevel "official".
`plugins install` passed that claim on as registry trust, which runs npm
lifecycle scripts and skips the permission gate (#3557) for any listed
plugin, with nothing actually verified.
- Discovery reports `verified` + `unverifiedReason`; only a registry whose
signature verifies against the pinned key keeps its trust labels. The
built-in fallback and a registry fetched with requireVerification off are
reported with every entry as 'unverified'.
- A placeholder pinned key (missing, malformed, all-zero) is never used to
verify: the all-zero key is small-order and accepts a forged signature.
- CLAUDE_FLOW_STRICT_PLUGINS=true (ADR-145): no fallback to the built-in
list, and `plugins install` refuses unless --allow-unverified is passed.
- `plugins install` passes registry trust, permissions and checksum only
for a verified registry entry; `plugins list`/`info` show registry status.
- PluginIntegrityVerifier (@claude-flow/security) skips placeholder anchors,
such as the all-zero one in trust-anchors.json, and verifies in RFC 8032
strict mode, which rejects small-order keys.
verify never re-anchors receipts that have no anchor (anchor-missing); appending refuses too.
Adds a hash-chained second anchor log in the project dir plus a mirror of its newest entry under
~/.config/ruflo/policy-trust, an explicit interactive --establish-anchor repair that is logged,
and migration of older state.json anchors. Threat model in ADR-475.
Co-Authored-By: RuFlo <ruv@ruv.net>
security 3.0.1 carries the #3568 policy-ledger truncation fix (bundled into
the train and published standalone). codex's security pin relaxes to ^3.0.0
so the workspace keeps linking the local security package; codex source is
unchanged.
Co-Authored-By: RuFlo <ruv@ruv.net>
The ADR-324 decision ledger is a hash chain, which rejects a modified or
deleted-middle receipt but not a deleted tail: a prefix of a valid chain is
itself a valid chain. Deleting the most recent receipts, or all of them, from
.claude-flow/policy/state.json still verified as valid: true.
- PolicyState gains ledgerHead/ledgerLength, updated in the same state write
as every receipt append.
- verifyLedger fails with policy-ledger-truncated (or
policy-ledger-anchor-mismatch) when the chain disagrees with its anchor, and
answers an empty ledger distinctly (state: 'empty').
- appendReceipt refuses to extend a chain that no longer matches its anchor,
so a new decision cannot re-anchor a truncated ledger and erase the evidence.
- Ledgers written before the anchor existed verify as valid and are anchored
once (anchor: 'established-now'); truncation before that point is not
detectable and is not claimed to be.
- verifyPolicyLedger is now a locked read instead of a write transaction, whose
own post-operation check turned every failure into a generic
policy-ledger-verification-failed. `policy verify` exits 1 when invalid.
Co-Authored-By: RuFlo <ruv@ruv.net>
Every @claude-flow/* leaf still on X.Y.Z-alpha.N moves to its stable X.Y.Z
(3.0.0, cli-core 3.7.0, the two plugins 1.0.0), matching the public train's
move to stable at 3.7.0. @claude-flow/embeddings is deliberately excluded:
its registry `latest` (3.0.0-alpha.45) was built from a branch not on main,
and publishing from main would drop 139 exported names.
Validated before publish:
- API surface: each package's main build compared against its published
`latest` tarball (files, exports/bin keys, exported names). All 18 are
equal-or-superset; no export removed.
- Every package builds; ~7.8k tests across 17 suites pass (cli-core has no
suite of its own and is exercised through the CLI's).
Packaging fixes made along the way:
- publishConfig.tag v3alpha/alpha removed so stable versions land on
`latest` rather than only the legacy channel tag.
- `files` allowlists (dist + README) on the 7 packages that had none. Their
published tarballs shipped local runtime state: .claude-flow daemon logs
and pids, settings.json, .swarm/memory.db, headless-worker prompt logs.
A secret scan of every affected published version found no credentials.
mcp shrinks from 335 files to 70, and no longer ships the .claude/
helpers copy that #3411 concerned.
- plugins/providers/testing pointed every `types` condition at a
nonexistent `.d.js` file; now `.d.ts`, so consumer type resolution works.
- Removed three subpath exports whose targets were never built (plugins
./examples/*, performance ./benchmarks/*); they could only throw
ERR_MODULE_NOT_FOUND.
- Intra-leaf ranges normalised to ^<stable>, including a `file:../security`
and a `workspace:*` devDependency that npm publish would ship verbatim.
Security: harden attention toSQL against injection (plugins)
The 13 attention SQL builders interpolated caller-supplied vectors and
config values into SQL that AttentionExecutor executes. TypeScript types do
not exist at runtime, so a string element could break out of the quoted
vector literal. Every interpolated value is now proven finite-numeric or
boolean. A differential run of 195 legitimate inputs across all 13
mechanisms produced byte-identical SQL; the new test fails 11/13 on the
original code (which emitted DROP TABLE for 5 payloads) and passes 13/13.
Co-Authored-By: RuFlo <ruv@ruv.net>
* dream(security): #3290 fix hive-mind_consensus Sybil-vote authentication gap
hive-mind_consensus's vote action recorded proposal.votes[voterId] for
any caller-supplied voterId string, with no check against state.workers
(the roster hive-mind_join populates). Since calculateRequiredVotes()
derives the quorum threshold from state.workers.length, a single caller
could cross any strategy's threshold (raft/bft/quorum) by voting under
fabricated identities -- a Sybil attack on consensus, not merely a
double-vote. The existing double-vote guard and Byzantine-flip detector
only catch one identity contradicting its own prior vote, not many
distinct forged identities each voting once.
Fixed with a fail-closed roster-membership check before a vote is
recorded. Stash-isolated: 2/3 new tests fail against real baseline
(forged votes silently accepted; a BFT proposal reaches "approved" from
3 forged identities against 3 real workers), 3/3 pass against candidate.
Full @claude-flow/cli suite: 2863/3036 passing, 46 pre-existing/unrelated
failures (workspace-resolution gaps), zero "hive" failures. Independent
adversarial critique: CONFIRMED-WITH-CAVEATS -- hive-mind_join/leave
remain fully unauthenticated, so this closes forged-vote Sybil but not
forged-join Sybil (disclosed follow-up).
Closes#3290 (full research: 5-role parallel fan-out, ledger check,
intelligence/swarm scan findings, competitor comparison, adversarial
critique).
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01DXHWKVWLjLv6wHkx6bBDF1
* dream(security): update ledger for #3290/#3291 (2026-09-11)
Records tonight's hive-mind_consensus Sybil-vote fix, verified GitHub
fates for the trailing nights (#3139/#3152/#3160 still open/draft;
unmerged performance night (#3265/#3266).
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01DXHWKVWLjLv6wHkx6bBDF1
* dream(security): capability-bind hive-mind join/leave/vote, fix CI cold-install
Addresses ruvnet's review REJECT on PR #3291 at b275fdd0:
1. hive-mind_join/hive-mind_leave were themselves unauthenticated, so the
roster-membership check alone (the original fix) only closed forged-vote
Sybil, not forged-join Sybil -- an attacker could just legitimately join
under fake names. hive-mind_init now mints a 32-byte hiveToken; join,
leave, and vote all require it (constant-time comparison), fail-closed:
a denied call makes zero state change (no roster write, no vote write),
verified by reloading state.json fresh off disk after each denial
(standing in for a restart/reopen). The CLI's own hive-mind subcommands
are unaffected -- a new getHiveTokenForCli() reads the token directly
off the same-machine state file (never exposed via hive-mind_status,
which is MCP-reachable) and the CLI wires it through automatically.
2. The required "Test Suite" CI workflow failed twice on the exact head:
@claude-flow/security (a root npm-workspace member) has no
prepare/postinstall script, only prepublishOnly (which npm ci never
runs), so a fresh checkout's dist/ never gets built, causing
ERR_MODULE_NOT_FOUND in doctor.js -> policy-runtime.ts. Root-caused via
a real root `npm ci` (confirmed dist/index.js is missing without a
prepare script, present with one) and cross-checked against PR #3266's
identical first-run failure. Added "prepare": "npm run build" --
standard npm lifecycle, fixes every future checkout, not just this PR.
Full @claude-flow/cli suite re-run after both fixes: 28 failed / 2884
passed (was 46/2863) -- the ~18-test improvement is exactly the
@claude-flow/security-dependent tests the prepare script now fixes; the
remainder are pre-existing, unrelated @claude-flow/neural/@claude-flow/mcp
workspace-resolution gaps. Zero "hive" failures. 6/6 new/updated tests
pass against the candidate, 6/6 fail appropriately against a
stash-isolated baseline. tsc --noEmit clean on every touched file.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01DXHWKVWLjLv6wHkx6bBDF1
---------
Co-authored-by: Claude <noreply@anthropic.com>
* dream(security): #TBD bind ADR-377 caller-identity verification into 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
* fix(ci): register ADR-377 Phase 3 caller-identity env vars as known escape 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
* fix(ci): regenerate root package-lock.json for codex's new @claude-flow/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
* fix(ci): build @claude-flow/security before Test Suite runs it
Complements d492ea835 (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
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(security): compare candidate and prefix in the same canonical form (#3010)
`validate()` resolved the candidate with `fs.realpath` but the allowed
prefixes were only `path.resolve()`d, so the two sides were compared in
different forms and nothing under a prefix reached through a symlink could
ever match. On macOS that is every temp directory — `os.tmpdir()` is
`/var/folders/...` and `/var` is a symlink to `/private/var` — so
`ruflo proxy install` rejected the binary it had just signature-verified.
Linux is unaffected because `/tmp` is real, which is why CI stayed green.
Keep both forms of every prefix and compare against the one matching how
the candidate was resolved. Canonicalizing the prefixes outright would have
broken `validateSync()` / `isWithinAllowed()`, which are documented as
lexical-only, in the mirror direction.
When the candidate's leaf does not exist, canonicalize its longest existing
ancestor instead of falling back to the lexical path — otherwise
`allowNonExistent` (the default) would regress under a symlinked prefix.
That also closes a hole this exposed: `<allowed>/escape/not-written-yet`,
where `escape` symlinks outside the prefix, was previously accepted because
`realpath` threw ENOENT on the missing leaf.
Escapes stay blocked, and the regression tests construct the symlink
explicitly rather than relying on the macOS quirk, so they hold on every
platform. 3 of the 6 fail without this change.
Co-Authored-By: RuFlo <ruv@ruv.net>
* fix(security): keep root prefixes usable and leave strict results untouched
Three defects found by an adversarial review of the previous commit.
`allowNonExistent: false` was still canonicalizing the candidate on ENOENT.
The call already fails, so the only effect was rewriting the `resolvedPath`
and `matchedPrefix` reported back — a silent shape change for any caller
reading those off a rejected result. Canonicalize only when non-existent
paths are actually allowed.
A root prefix (`/`, `C:\`, a UNC share root) already ends in a separator, so
anchoring the boundary as `prefix + path.sep` searched for `//` and rejected
every descendant. Pre-existing at all three comparison sites — this change
only widened how a prefix can end up being a root, since one can now
canonicalize to it. Extracted `isWithinPrefix`, which keeps the boundary
anchored so `/srv/app-secrets` is still outside `/srv/app`.
The guarded symlink tests returned early instead of skipping, so they
reported as passed on a host that cannot create links, and the flag was
never reset between cases. They now skip properly and reset per case.
Also documents that prefixes are canonicalized once, at construction: the
allowlist keeps denoting what it named when configured, rather than handing
whoever can rewrite a link the ability to redirect it later.
Co-Authored-By: RuFlo <ruv@ruv.net>
---------
Co-authored-by: RuFlo <ruv@ruv.net>
PathValidator.validate() canonicalizes the candidate path through
fs.realpath (when resolveSymlinks is on, the default), but the constructor
only ever path.resolve()d the allowed prefixes — never realpath'd them. On
any platform where a prefix is itself reached through a symlink (e.g.
macOS os.tmpdir() under /var, itself a symlink to /private/var), the
realpath'd candidate can never match the non-realpath'd prefix, and every
path under that prefix — including the prefix's own contents — is
rejected as "outside allowed directories".
This broke `ruflo proxy install` on macOS: the download and Ed25519
signature verification both succeed, then the defense-in-depth path check
in proxy/install.ts fails on the extracted binary, which lives under a
workDir created via os.tmpdir().
Fix: realpath the prefixes at construction time too, with the same
ENOENT-tolerant fallback to path.resolve() that validate() already uses
for candidates (a prefix directory that doesn't exist yet degrades
gracefully instead of throwing).
Implements all 3 phases of ADR-377 (ruvnet/ruflo#2516, tracked in #2873):
AgentDB's retrieval and write paths had zero certified defenses against
memory poisoning — SMSR (arXiv:2606.12703) shows 93-100% undefended attack
success. All three phases are off by default; enabling each is opt-in via
env flag until independently benchmarked, per the ADR's own gate criteria.
Phase 1 — AgentDbRetrievalGuard (@claude-flow/memory)
- New `agentdb-retrieval-guard.ts`, wired into `AgentDBBackend.search()`
(covers `query()`'s semantic path too, since it calls `search()`
internally). Reuses `@claude-flow/security`'s `ToolOutputGuardrail`
(ADR-131) for the actual OWASP LLM01/LLM08 pattern scan rather than
duplicating it — `@claude-flow/memory` now depends on
`@claude-flow/security` (no cycle: security has zero workspace deps).
- Size gate + guardrail scan per retrieved chunk. Flag-only by default;
`CLAUDE_FLOW_RETRIEVAL_GUARD_STRICT=true` drops unsafe/oversized chunks
instead of just annotating them. Both flags off by default
(`CLAUDE_FLOW_RETRIEVAL_GUARD=true` to enable scanning at all).
Phase 2 — MemoryPoisonForensics (@claude-flow/hooks)
- New `workers/memory-poison-forensics.ts`: rolling-window z-score anomaly
detection on write-interval and content-length features per
(agentId, namespace), reproducing the Forensic Trajectory Signatures
(arXiv:2606.30566) shape (behavioral features on write *sequences*, not
content). Opt-in `HookEvent.PostToolUse` handler for memory-write tools;
`CLAUDE_FLOW_POISON_FORENSICS=0` disables recording.
- NOT wired into any of this repo's three separate "worker" dispatch
systems (hooks' own workers/index.ts codebase-analyzers,
@claude-flow/swarm's worker-dispatch.ts 12-trigger table, or the CLI's
scheduled daemon) — @claude-flow/swarm is a deliberately
zero-workspace-dependency package and adding hooks there would break
that property; picking the wrong one of three ambiguous dispatch tables
seemed worse than leaving that integration as an explicit follow-up.
See the file header for detail.
Phase 3 — McpCallerIdentity (@claude-flow/security)
- New `mcp-caller-identity.ts`: per-invocation Ed25519-signed capability
tokens (callerId + toolName + nonce + 30s TTL by default), native
node:crypto sign/verify following the same pattern already used by
`@claude-flow/browser`'s witness-signer.ts (reimplemented rather than
cross-imported — a security-primitives module depending on the browser
package would be a backwards layering dependency).
- Standalone issue/verify API; deliberately NOT wired into
`AgentAuthorizationPropagator.checkToolCall` (ADR-144) or any tool
dispatcher — this repo has at least 3 distinct `ToolRegistry`
implementations and guessing which is authoritative risked silently
wiring the wrong path. `CLAUDE_FLOW_MCP_CALLER_AUTH` defaults to
disabled, matching the ADR's own text: key distribution needs a
follow-on ADR before this ships enabled.
Deviations from the ADR's guessed file paths/class names (documented
inline where relevant): the ADR was written against aspirational paths
(`agentdb/retrieval-guard.ts`, a `BackgroundWorker` interface,
`SafeExecutor`) that don't match the real repo layout — implementation
targets the actual `AgentDBBackend`, the actual worker systems, and the
actual `AgentAuthorizationPropagator`/tool-registries respectively.
Tests: 31 new tests across the three packages (9 + 11 + 11), all passing;
full existing suites re-run clean (memory 451, hooks 145, security 570 —
zero regressions).
Adds the live Ruflo capability brain, governed federation and Cognitum product planes, source-bound concurrent harness contracts, ADRs 325-329, and the v3.32.28 guarded release train.
* feat(memory): typed provenance in AgentDB (ADR-323)
Corrects and implements the 2026-07-28 dream-cycle research proposal
(PR #2804, closed as superseded) for typed memory provenance in
AgentDB, addressing MemIR/MemSyco-Bench's "provenance-role collapse"
and memory-induced sycophancy findings.
The dream-cycle PR's proposed schema change targeted the wrong table
(`vector_indexes`, which is per-namespace HNSW index metadata, not
per-entry) and its filename collided with the already-merged
`ADR-322-metaharness-flywheel-integration.md` (#2817) — both corrected
here as ADR-323 against `memory_entries`, the real per-record table.
- memory-initializer.ts: `provenance_type` column (user_claim |
agent_output | system_observation | tool_result | unknown, default
unknown) on `memory_entries`, migrated for existing DBs via
`ensureSchemaColumns()`. `storeEntry()`/`searchEntries()` accept and
validate `provenanceType`/`provenanceFilter`.
- memory-bridge.ts: same wiring for the native better-sqlite3 AgentDB
v3 path (`bridgeStoreEntry`/`bridgeSearchEntries`).
- commands/memory.ts: `memory store --provenance <type>`, `memory
search --provenance-filter <types>` (comma-separated, OR semantics).
- mcp-tools/memory-tools.ts + cli-core/mcp-tools/memory-defs.ts:
`memory_store`'s `provenance_type` param, `memory_search`'s
`provenance_filter` param (kept in sync across both tool-def sets).
Found and fixed during implementation: an initial design skipped the
RaBitQ/HNSW acceleration paths entirely whenever a provenance filter
was requested (neither carries provenance_type on their candidates).
This reproducibly crashed the CLI process on exit (a libuv assertion
on Windows) after printing correct results. Fixed by never skipping
those paths — RaBitQ now fetches provenance_type in its existing
per-candidate query, HNSW does one batched (namespace, key) lookup
afterward — eliminating the crash across repeated runs. See ADR-323's
"Implementation note" for detail.
7 new end-to-end tests (adr-323-memory-provenance.test.ts), including
a repeated-run regression guard for the crash. Full existing memory
suite re-verified with no regressions (one pre-existing, environment-
specific #2558 test failure confirmed identical against unmodified
main — better-sqlite3's native bridge is unavailable in this dev
environment, unrelated to this change).
Closes the follow-up from dream-cycle PR #2804 (closed as superseded).
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(memory): enforce provenance across retrieval paths
* feat: add agentic policy engine
* fix: align Codex MCP integration
* fix: keep MetaHarness dependencies optional
* fix: declare MetaHarness as optional peers
* fix: sync workspace lockfile
* fix: document policy tool selection guidance
* test: cover governed in-process flywheel
* ci: make MetaHarness absence drill deterministic
Follow-up from dream-cycle issue #2783 / PR #2784. Implements the v2 that the already-shipped v1 (commits 381b7ebcc/581cd2bf3) explicitly deferred: SimHash-based cross-tool fragment detection for MCP composition, and a ChannelGuard reusing the real InputValidator instead of a reinvented catalog. 29 new tests, 134/134 existing hooks tests passing, 0 regressions, clippy clean.
* fix(security): ADR-165 Phase 1 — close all critical CVEs, refresh registry, add CI gate
Closes ADR-165 Phase 1. Both workspaces pass `npm audit --audit-level=critical`.
## Before (2026-06-29)
| Workspace | Critical | High | Moderate | Total |
|-----------|----------|------|----------|-------|
| Root | **1** | 6 | 31 | 38 |
| v3 | **4** | 33 | 57 | 97 |
## After
| Workspace | Critical | High | Moderate | Total |
|-----------|----------|------|----------|-------|
| Root | **0** | 0 | 31 | 31 |
| v3 | **0** | 27 | 58 | 88 |
All 5 critical advisories closed (vitest GHSA-5xrq, handlebars prototype-pollution,
protobufjs constructor-pollution, plus 2 more in v3). 12 of 39 high advisories
closed via overrides; the remaining 27 v3 highs are deeper transitive chains
that would require breaking-change major bumps — flagged for ADR-165 Phase 2.
## Changes
### Root workspace overrides (package.json)
- `vitest`: `^1.0.0` → `^3.2.6` (closes critical GHSA-5xrq CVSS 9.8)
- `hono`: `>=4.11.4` → `>=4.12.25` (closes 6 hono advisories)
- `undici`: `>=7.18.0` → `>=8.5.0` (closes 7 undici advisories — bumps the
stale pin that ADR-165 flagged as still-in-vulnerable-range)
- `vite`: `>=6.4.6` → `>=8.0.16` (closes 2 vite advisories)
- NEW `@grpc/grpc-js`: `>=1.14.4`
- NEW `form-data`: `>=4.0.6`
- NEW `http-proxy-middleware`: `>=3.0.7`
- `@hono/node-server`: `>=1.19.10` → `>=1.19.14`
### v3 workspace overrides (v3/package.json + pnpm.overrides)
- `vitest`: `^4.0.16` → `^4.1.0` (and `@vitest/coverage-v8` matched)
- Top-level + pnpm overrides: `handlebars: >=4.7.9`, `protobufjs: >=8.6.0`
- 27 sub-package `package.json` updated to `vitest ^4.1.0`
### CVE registry refresh (v3/@claude-flow/security/src/CVE-REMEDIATION.ts)
Rewritten from 5 stale Jan-2026 entries to 16 total: 15 fixed, 1 open
(ADR165-OPEN-01 — PII pipeline wiring per ADR-164 §6.1 not yet on
dispatch-layer). `SECURITY_SUMMARY` is now computed dynamically from the
registry; `validateRemediation()` correctly returns `allFixed: false`
with 1 issue. ADR-165 §5 flagged this as THE Phase 2 priority — now closed.
### New CI gate
`.github/workflows/cve-audit.yml`:
- `audit-root` — `npm audit --audit-level=critical` in root, blocking
- `audit-v3` — same in v3, blocking
- `audit-high-report` — warn-only summary on high-severity drift
- Triggers: PR, push to main, daily cron
### ADR-165 §9 evidence ledger addendum
Captures AFTER-remediation `npm audit --json` metadata for both workspaces.
The diff between BEFORE (already in §9) and AFTER makes Phase 1 verifiable.
Also CORRECTED one prior surprise in §9: the `validate-input.ts` in
`mcp-tools/` is NOT a 9-line shim. It's 269 lines of real implementation.
(That was an inaccurate findings note from the initial audit.)
### §7.3 research items resolved
- `protobufjs` v3 chain confirmed gone after override pins
- `handlebars` v3 chain confirmed not user-reachable on the workflow path
(toolchain-only; override sufficient)
- `validate-input.ts` confirmed real implementation, not a shim (registry
entry corrected)
- CORS surface check: no `cors()` / `app.use(cors` call sites — the hono
CORS-wildcard advisory is not exploitable on the current server
- ADR-164 §6.1 PII pipeline NOT wired in current agentbbs dispatch —
recorded as ADR165-OPEN-01 (Phase 3 work)
## Gates (all PASS)
- `npm audit --audit-level=critical` root: exit 0 (was non-zero)
- `npm audit --audit-level=critical` v3: exit 0 (was non-zero)
- vitest suites: 105/109 pass (4 skip-conditional — same as before)
- ADR-112 audit: 352/352 tools with guidance
- business-pods smoke: 11/11 PASS
- agentbbs smoke: 8/8 PASS
- agenticow smoke: 8/8 PASS
- cli build: clean (tsc)
## Phase 2+ deferred
The 27 v3 high-severity advisories that remain are transitive chains with
no patched-in-range version. ADR-165 §6 Phase 2 picks these up after the
`cve-watch.yml` automation in §6 lands (NEW PR — not in scope here).
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01WbBa4nccx5aGhXphoHxcni
* chore(lock): regen v3/pnpm-lock.yaml after override updates (PR #2508 CI fix)
PR #2508 added handlebars + protobufjs to v3 overrides + bumped vitest pin,
but v3/pnpm-lock.yaml wasn't regenerated. CI surfaced
ERR_PNPM_LOCKFILE_CONFIG_MISMATCH on every install step.
Same pattern as the previous lockfile-drift fixes on PR #2500 and #2503.
* fix(ci): cve-audit.yml jq-only + regen witness manifests for #1609 marker drift
Two PR #2508 CI failures fixed:
1. Static-regression-guard tripped on cve-audit.yml line 52: the static
YAML strict-parser sees `if high > 0:` inside the `run: |` block as
a YAML block-mapping key indicator. Refactored all 3 inline-python
blocks to use jq (preinstalled on ubuntu-latest) — no embedded
`if x > 0:` lines, so the YAML guard passes.
2. Witness marker drift smoke flagged fix-marker #1609 — the Phase 1
vitest bump (4.0.16 → 4.1.0) drifted the cited marker string in
v3/@claude-flow/aidefence/package.json. Updated `marker` field in
all 3 platform manifests + `desc` to reference ADR-165 Phase 1, then
re-signed via plugins/ruflo-core/scripts/witness/regen.mjs.
Co-Authored-By: RuFlo <ruv@ruv.net>
@claude-flow/security 3.0.0-alpha.8 → 3.0.0-alpha.10 (alpha.9 was already
on npm). Adds @noble/ed25519 to dependencies so its TS build succeeds
standalone — closes the TS2307 break that has been red on main since
2026-06-02 (#2311, #2275).
@claude-flow/cli, claude-flow, ruflo 3.10.37 → 3.10.38. CLI also pins
@claude-flow/security to ^3.0.0-alpha.10 so users picking up the wrapper
get the noble dep automatically.
All three published with latest+alpha+v3alpha aligned at 3.10.38.
Co-Authored-By: RuFlo <ruv@ruv.net>
Three HIGH-severity verification-cluster fixes shipping together.
#2311 — `@claude-flow/security` build fails with TS2307: `Cannot find module
'@noble/ed25519'`. The package imports it in integrity-verifier.ts:260 but
never declared it as a dep — the root override didn't propagate (lesson
from #2112). Declare it directly. Unblocks the `ToolOutputGuardrail smoke
(ADR-131)` job and the broader `V3 CI/CD Pipeline failing on main` cluster
documented in #2275.
#2274 — `plugins/ruflo-core/scripts/witness/verify.mjs:175` unconditionally
assigned `ed.etc.sha512Sync`. On `@noble/ed25519` v2 some patch versions
freeze `etc`, throwing `TypeError: Cannot add property sha512Sync, object
is not extensible` and aborting witness verification on every run (6+
documented occurrences). Guard with `if (!ed.etc.sha512Sync)` and a
try/catch fallback — sha512Sync is already wired internally on v2, so the
shim is only needed on v1. Verified against macOS, Linux, and Windows
manifests on this checkout: all three return `Ed25519 signature valid: yes`
with regressed=0 missing=0.
#2312 — `scripts/smoke-trajectory-graph-edges.mjs` TEST 2 OOMs at ~4 GB on
GitHub-runner default heap, killing the `graph trajectory hooks smoke
(ADR-130 P3)` job (SIGABRT exit 134). The allocation lives deep inside
`intelligence.recordTrajectory` → `@ruvector/ruvllm SonaCoordinator` on
first call; the runtime cause is tracked separately. Bump
`NODE_OPTIONS=--max-old-space-size=6144` on the CI step so the job
completes while the leak is investigated.
Co-Authored-By: RuFlo <ruv@ruv.net>
Three independent P1 components, each scoped to a single PR-sized change.
All three are off by default — flips become default in v4.0 — so existing
pipelines keep their exact behaviour.
ADR-144 P1 — AgentAuthorizationPropagator (#2248)
v3/@claude-flow/security/src/authorization/propagator.ts (new)
• AuthScope envelope (principal, granted tools/servers, delegation depth, expiry)
• wrapOutbound: monotonically-reducing scope, depth-decrement, expiry check
• checkToolCall: typed decisions (tool-not-in-scope / server-not-in-scope /
scope-expired / delegation-depth-exhausted) — never throws, telemetry-friendly
• verifyServerAuth: fail-closed on missing/empty credentials (P1 permissive
accept for non-empty — P4 wires real validator)
• Provenance buffer ring-bounded for P5 telemetry sink
• makeLegacyPermissiveScope shim for legacy-mode migrations
18 unit tests covering every invariant; all pass.
ADR-145 P1 — PluginIntegrityVerifier (#2254)
v3/@claude-flow/security/src/plugins/integrity-verifier.ts (new)
v3/@claude-flow/cli/src/plugins/trust/trust-anchors.json (new placeholder)
• Canonical JSON serialisation (deterministic key order) + SHA-256 manifest hash
• Ed25519 detached signature verification via @noble/ed25519 (probe-and-fall-
back, mirrors verify.mjs #1880 pattern so untrusted environments skip
rather than throw)
• Trust-anchor allowlist with exact / wildcard scope matching + expiry
• Structured VerificationStatus (pass / signature-missing / signature-invalid
/ manifest-hash-mismatch / unknown-signer / signer-expired)
• Stage-2 semantic-intent scan (SCH defence) lands in P2
13 unit tests including the round-trip sign→verify and tamper-flip cases.
ADR-146 P2 — guardrail call site in MCP dispatch (#2149 follow-up)
v3/@claude-flow/cli/src/mcp-client.ts — patch callMCPTool
• Lazy-resolves @claude-flow/security so cold-import cost doesn't hit
every CLI invocation; falls back to no-op if module unavailable
• Walks the result object one level deep (matches the flat-record shape
of every existing tool — deeper traversal would change the p99 latency
contract)
• Rejected fields replaced with typed marker: '<rejected-by-guardrail
tool="X" category=Y>' so callers can surface the rejection rather than
silently dropping
• Off by default. CLAUDE_FLOW_STRICT_GUARDRAIL=true turns it on; precedence
documented inline so the env-var audit passes without an escape-hatch entry.
4 wiring tests (legacy passthrough, strict-mode reject of known injection,
strict-mode passthrough on safe content, non-object results pass through).
Security index exports updated for all three. Builds clean (tsc with no
errors). All 4 audits + 2 regression smokes pass locally.
Co-Authored-By: RuFlo <ruv@ruv.net>
* feat(security): #2149 ADR-131 P1 — ToolOutputGuardrail + OWASP 2026 mapping
Closes the OWASP ASI01 (Agent Goal Hijacking) gap identified in the
2026-05-26 dream cycle. Adds a pure, synchronous, pattern-based screener
for content crossing the agent's content boundary (MCP tool results,
memory reads, external API responses) before it enters reasoning.
Design (full rationale in ADR-131):
- Eight built-in pattern categories: instruction-override, embedded-system
(ChatML / Llama [INST] / <system>), exfiltration, role-hijack, jailbreak,
hidden-unicode (bidi + zero-width), tool-spoofing, truncation
- Default policy: critical→reject, high→redact, medium→flag, low→allow
- Pure-function shape — safe to invoke in every tool result / memory read
- 32KB content scans in 0.1ms locally (target: <1ms p99)
Scope:
- P1 (this PR): class + tests + exports + ADR + OWASP matrix + CI smoke
- P2-P5 (follow-on): MCP dispatch, memory read, swarm Raft payload,
per-tool policy + telemetry — tracked in ADR-131
Validation:
- 24/24 vitest tests pass (9ms)
- 11/11 smoke checks against built dist/index.js
- Tested against the four canonical ASI01 attack shapes
- CI guard `tool-output-guardrail-smoke` wired into v3-ci.yml
OWASP Top 10 for Agentic Applications 2026 control matrix added:
v3/docs/security/owasp-agents-2026-mapping.md — flags ASI01/06/09/10 as
highest-priority remaining gaps.
Co-Authored-By: RuFlo <ruv@ruv.net>
* fix(plugins/ruflo-core/hooks): #2155 — wrap 3 unwrapped .sh hooks in /bin/bash -c
Three hooks (PreToolUse:Bash, PreToolUse:Write|Edit|MultiEdit, Stop)
invoked scripts/ruflo-hook.sh directly without an outer shell wrapper.
On Windows, Claude Code's hook executor spawned the .sh path, Node read
the shebang, attempted to exec /bin/bash (absent on Windows), and exited
with code 126 — surfacing as PostToolUse:Bash/PreToolUse:Bash/Stop hook
errors in every user session.
Wrapped each invocation in /bin/bash -c '... || true' to match the
existing pattern of the four other hooks in the same file. The || true
fallback now executes inside the bash subshell rather than relying on
the .sh body being readable. Validated as still working on POSIX.
Co-Authored-By: RuFlo <ruv@ruv.net>
This commit closes the chain started in ca0a6fa5c (standalone use recipes
landed in repo but never reached npm because no version bumps shipped).
## Changes
### Module version bumps (alpha.43)
- @claude-flow/memory → 3.0.0-alpha.17 (republish standalone use README)
- @claude-flow/embeddings → 3.0.0-alpha.18 (republish standalone use README)
- @claude-flow/security → 3.0.0-alpha.8 (republish standalone use README)
- @claude-flow/neural → 3.0.0-alpha.9 (pin @ruvector/sona to 0.1.5,
republish standalone use README)
- @claude-flow/cli → 3.7.0-alpha.43 (repin to new modules above)
- claude-flow → 3.7.0-alpha.43 (umbrella)
- ruflo → 3.7.0-alpha.43 (umbrella)
### Umbrella bump (alpha.44) — npm download badges
- @claude-flow/cli → 3.7.0-alpha.44
- claude-flow → 3.7.0-alpha.44
- ruflo → 3.7.0-alpha.44
### Neural sona pin (root fix for #2022)
@claude-flow/neural alpha.8 had `"@ruvector/sona": "latest"`, and
@ruvector/sona@0.1.6 (current latest on npm) shipped as an empty publish
— README + package.json only, no index.js or native bins. So every
fresh `npm install @claude-flow/neural@alpha.8` broke at import time
with `Cannot find package '.../node_modules/@ruvector/sona/index.js'`.
Alpha.9 pins to the exact known-good 0.1.5 (which has index.js + 7
platform-specific .node bins). Will revert to a range once
@ruvector/sona@0.1.7+ ships with content (tracked in #2022).
### npm download badges across every published package README
Added `shields.io/npm/dm/<pkg>` (monthly downloads) badges to:
- claims, cli-core, plugins (had no npm badges at all)
- plugin-agent-federation, plugin-iot-cognitum (no npm badges)
- browser, codex, mcp (had version badge, missing downloads)
- umbrella root README (now badges both `ruflo` + `claude-flow`)
The ruflo/README.md and v3/@claude-flow/cli/README.md auto-copy from
root via prepublishOnly, so they inherit the new badges on publish.
Packages that already carried downloads badges (aidefence, deployment,
embeddings, guidance, hooks, integration, memory, neural, performance,
providers, security, shared, swarm, testing) are unchanged.
## Live functional check on the published artifacts
- Fresh `npm install @claude-flow/neural@3.0.0-alpha.9`:
sona resolves to 0.1.5; `import('@claude-flow/neural')` returns
A2CAlgorithm, BalancedMode, BaseModeImplementation, BatchMode,
CuriosityModule, ... (was broken before alpha.9).
- Standalone use sections from ca0a6fa5c now visible on npmjs.com for
memory@alpha.17, embeddings@alpha.18, security@alpha.8, neural@alpha.9.
- npm dm/ badges resolve and render the current weekly download count.
Co-Authored-By: RuFlo <ruv@ruv.net>
Adds a 'Standalone use (without the Ruflo CLI)' section to each
module's README right after Installation, with copy-pasteable
recipes verified against the published artifacts (fresh `npm install`
clean room, no patched dist, exit 0 for every example).
Recipes:
@claude-flow/memory (@3.0.0-alpha.16)
1. HNSWIndex — add 3 vectors, search top-2, no other deps
2. ControllerRegistry + injected AgentDB — proves the v3
governance layer works on a caller-owned AgentDB instance
(the agentdb injection field added in #2019)
@claude-flow/embeddings
1. MockEmbeddingService + cosineSimilarity — embed + compare
without network calls
2. Provider swap snippet showing OpenAI / Transformers / etc.
keep the same API surface
@claude-flow/security (@3.0.0-alpha.7)
1. InputValidator.validate(EmailSchema) +
new PasswordHasher({ rounds: 12 }) +
createCredentialGenerator().generateApiKey() — three of the
OWASP top 10 in ~15 lines
2. createDevelopmentExecutor — git through the allowlist gate;
createProjectPathValidator — refuses `../` traversal with
errors[] surfaced
Bugs uncovered by running the recipes against live npm:
- `embed()` returns { embedding, tokens, ... }, not a raw Float32Array
(recipe destructures `.embedding`).
- createPasswordHasher(rounds: number) accepts a NUMBER, not a
config object (passing `{ rounds: 12 }` made config.rounds nest
into `{ rounds: 12 }` and bcryptjs threw 'Illegal arguments:
string, object'). Switched recipe to `new PasswordHasher(...)`.
- generateCredentials() does NOT generate API keys — that lives
on `CredentialGenerator.generateApiKey(prefix)`. Recipe now
uses createCredentialGenerator().generateApiKey().
- PathValidator result is { isValid, errors[], ... }, not
{ valid, error }. Recipe matches the real shape.
Not included — @claude-flow/neural standalone recipe:
@claude-flow/neural@3.0.0-alpha.8 currently fails on fresh
install because @ruvector/sona@0.1.6 is an empty publish (only
README + package.json — no index.js, no native bins).
@ruvector/sona@0.1.5 had full content (index.js + 7 platform
bins), so this is a regression in the latest sona release that
blocks the entire neural package from loading.
Rather than ship a broken recipe, the neural README gains a
'Known issue' callout pointing at @ruvector/sona@0.1.6. Fix
path: republish @ruvector/sona with content or pin
@claude-flow/neural to @ruvector/sona@^0.1.5 in its next bump.
Co-Authored-By: RuFlo <ruv@ruv.net>
* fix(security): drop bcrypt → bcryptjs to eliminate tar CVE chain (#1608)
`@claude-flow/security` was pulling 6 HIGH-severity CVEs through this
transitive chain:
@claude-flow/security → bcrypt@5.1.1 → @mapbox/node-pre-gyp@1.0.11
→ tar@<=7.5.10 (vulnerable)
CVEs (all tar <=7.5.10):
- GHSA-34x7-hfp2-rc4v Hardlink path traversal → arbitrary file create
- GHSA-8qq5-rm4j-mr97 Symlink poisoning → arbitrary file overwrite
- GHSA-83g3-92jg-28cx Hardlink target escape → arbitrary read/write
- GHSA-qffp-2rhf-9h96 Drive-relative hardlink path traversal
- GHSA-9ppj-qmqm-q256 Drive-relative symlink path traversal
- GHSA-r6q2-hw4h-h46w Race condition in node-tar path reservations
Downstream consumers of `@claude-flow/security` were forced to add a
`tar` `overrides` block to their own `package.json` to clear
`npm audit`. The umbrella `claude-flow` package already does this, but
direct consumers of `@claude-flow/security` (or anything that depends
on it transitively) couldn't rely on that.
Switch from `bcrypt` (native, requires @mapbox/node-pre-gyp build chain)
to `bcryptjs@^3.0.3` (pure JS, zero native deps, zero tar dependency):
- Same hash format (`$2a$`, `$2b$`, `$2y$`) — existing hashes verify
unchanged.
- Same API surface for the calls this module makes (`bcrypt.hash`,
`bcrypt.compare`); the `import * as bcrypt from 'bcryptjs'` form
keeps the rest of the module's call sites byte-identical.
- Drops the `@types/bcrypt` devDep — bcryptjs ships its own types.
Bumps `@claude-flow/security` 3.0.0-alpha.6 → 3.0.0-alpha.7. Republish
required for downstream consumers to pick up the fix; the source change
itself doesn't move until that publish lands.
Closes#1608.
Co-Authored-By: RuFlo <ruv@ruv.net>
* chore(lockfile): regenerate v3/pnpm-lock.yaml for bcryptjs swap
Companion to the bcrypt → bcryptjs swap in
@claude-flow/security/package.json. CI runs with `--frozen-lockfile`
so the manifest change couldn't land without a matching lockfile.
Most of the diff is incidental patch-version drift of
@opentelemetry/api 1.9.0 → 1.9.1 across vitest dev-deps; the
load-bearing change is the @claude-flow/security entry switching
from `bcrypt` to `bcryptjs` and dropping `@types/bcrypt`.
Co-Authored-By: RuFlo <ruv@ruv.net>
1. SQL injection: Convert all 9 string-interpolated queries in
memory-initializer.ts to parameterized queries (db.prepare/bind)
Closes#1030
2. Preinstall hook: Remove no-op preinstall script and package.json entry
that triggered supply chain concern
3. SECURITY.md: Add vulnerability disclosure policy with supported
versions, reporting process, and safe harbor statement
4. SafeExecutor: Remove npx from default command allowlist to prevent
arbitrary package execution via safe executor
5. Cleanup command: Add `cleanup` command for removing project artifacts
(.claude/, .swarm/, data/, etc.) with --dry-run default and --force
to execute. Addresses uninstall documentation gap
Published as v3.5.40. All fixes verified in Docker.
Closes#1375
Automatic checkpoint created by Claude Code
- Branch: v3
- Timestamp: 2026-01-07T19:04:42Z
- Changes: 19 file(s)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Updated standard-checkpoint-hooks.sh to accept both JSON and plain paths
- Fixed pre-edit and post-edit functions to detect input format
- Fixed v3 CLI hook flags (-f -> --file, -c -> --command, etc.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>