Gate hive-mind init/join/leave/spawn/propose/vote/broadcast/shutdown/
memory set+delete/optimize-memory for remote (HTTP/WebSocket) callers only;
local stdio, ruflo mcp exec, the CLI and in-process callers keep working
with no extra step. hive-mind_init never returns hiveToken. Operator secret
from RUFLO_HIVE_BOOTSTRAP_SECRET or a 0600 bootstrap.secret created by the
first local init. state.json is written 0600 atomically (dir 0700).
Constant-time compare. RUFLO_HIVE_REQUIRE_AUTH=1 for bridged stdio.
Co-Authored-By: RuFlo <ruv@ruv.net>
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>
- startHttpServer refuses a non-loopback --host unless RUFLO_MCP_HTTP_TOKEN /
--auth-token-file / --auth-token is set, or RUFLO_MCP_ALLOW_UNAUTHENTICATED_HTTP=1.
The gate now runs before any import or bind.
- New optional bearer-token auth wired into the HTTP transport: constant-time
compare, bare 401, no token logging, applied before the body parser; only
GET/HEAD /health stays public and reveals liveness only. Auth enabled with no
tokens now fails closed (previously any Bearer value was accepted).
- Wiring tests through MCPServerManager.start() (mutation-checked) and transport
tests covering bypass attempts; README + mcp start help document the env vars.
Loopback without a token stays unauthenticated by design (documented).
Co-Authored-By: RuFlo <ruv@ruv.net>
- plugin.json declares eventsPersist (default true); version 0.36.0 in plugin.json, version.ts, smoke.sh
- data/append-argv.ts: one dd argv (bs=1M iflag=fullblock) shared by the Events store and the autopilot journal; real-dd 8-writer test fails on the old argv
- history decoders and Events/Timeline displays use #3874's timeOf/countOf/isoOf, so a hostile time or count cannot throw
- cli init: .claude-flow/.gitignore template lists console/
- ADR-474 updated to the reviewed behaviour and the open low items
Co-Authored-By: RuFlo <ruv@ruv.net>
`daemon start --workers <list>` was forwarded to the forked background
child's argv (#1968/#1970) but never read into DaemonConfig, so
WorkerDaemon always fell back to DEFAULT_WORKERS (7 enabled) in both
foreground and background mode. Read --workers into a new
DaemonConfig.enabledWorkers, and apply it in initializeWorkerStates()
after the daemon-state.json restore so a stale saved state can't
re-enable a deselected worker.
Scoped to the --workers selection bug only; the related --sandbox
forwarded-but-unused gap and the daemon status TTL display bug noted
in the same issue are left for a separate follow-up.
Opened by the nightly triage routine — human review required before
merge.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JSiFBZnyyG3nzyT3BuHq76
The statusline collision test was made hermetic by both #3842 and this PR; keep this
branch's version (it also mocks child_process and asserts nothing shells out).
Co-Authored-By: RuFlo <ruv@ruv.net>
$'\x2f' and $'\x72m' name the root and rm without any expansion, and a
\' inside $'...' does not close the string. The scanner now decodes ANSI-C
escapes (octal, \x, \u/\U, \c, the single-letter set, NUL ends the string)
in all four copies, and the parity table gains wrapper, flag-spelling,
benign look-alike and documented-limit rows.
Co-Authored-By: RuFlo <ruv@ruv.net>
Adversarial review: with the mod now standing down for the CLI's own hooks
command, `ruflo mods doctor` still said "no hook-handler.cjs: the mod owns
route and post-edit outright" in such a project. A new `legacy CLI hooks`
finding (warn, fix: `ruflo init upgrade --settings`) names the events the
mod leaves to that command, and the handshake line no longer claims them.
The pattern mirrors legacyCliConfigured in the plugin (the CLI package
cannot import plugin source).
(cherry picked from commit d7ac91f595)
Real-bash oracle (bwrap, read-only root, logging fake rm; positive controls
detected) over a 63,696-command differential between main and this branch:
- a substitution inside double quotes is now dropped from the word, as its
empty output is, so "`true`"-rf reads as -rf; a placeholder is kept only
when a # follows, so bash -c "`true`#x; rm -rf /" is still read as one
word and caught;
- 1,260 commands main denies and this branch allows: 0 really delete
(bash parses each differently than main assumed);
- of 1,500 sampled commands this branch allows with a root operand, the
201 that really delete are all $(...) substitutions (stated follow-up, 189)
or $- expansions inside sh -c (12); main misses every one too.
Three parity rows added (309 pass). All four copies alike; the generator's
String.raw source keeps no backtick in comments.
(cherry picked from commit 4f2ac8ba28)
Third review (by reading; its oracle run was cut short): the unquoted
backtick branch did not mark the word as started, so a # right after an
empty substitution was read as a comment and the rest of the line skipped,
`true`#x; rm -rf / among it; bash keeps # inside the word. main denied this
(the backtick was a word character there). Confirmed: the new parity row
fails on the previous head and passes now.
An unterminated backtick (quoted or not) is now literal text, as main read
it, instead of a substitution that swallows the rest of the input. Five rows
added (306 pass); all four copies alike.
(cherry picked from commit 6f515a37a2)
Second review, against a real-bash oracle (sandboxed, logging fake rm) over
12k generated commands:
- the backtick body dropped every backslash; bash removes one only before
$, a backtick or a backslash (and " inside double quotes) and drops
backslash-newline, so escaped operands and line continuations inside a
substitution slipped through;
- a backtick inside "..." was kept as text, not scanned, so an empty
substitution inside a quoted operand hid the delete;
- a word was rescanned only if it contained "rm", so a nested shell whose
rm was split by a substitution or a backslash was never read.
One substitution helper now serves both contexts, and any word with a
separator or a backtick is rescanned (still bounded at four levels). All
four copies alike. The reviewer's oracle: misses 632 -> 26 (24 are the $(...)
follow-up, 2 real expansions), 0 regressions vs main. Five deny rows and one
allow row added to the parity table (301 pass; on the previous head exactly
the five new deny rows fail).
(cherry picked from commit feb0df1e73)
Adversarial review: making the backtick a command separator reset the rm
state, so an empty substitution among rm's operands hid the delete. Four
commands main denied were allowed (rm -rf `true` /, rm `` -rf /,
rm -rf /tmp/x `:` /, rm -r `:` -f /); the shell drops the empty word, so
each runs as a root delete.
The backtick is no longer a separator. Its body is scanned as its own
command (bounded at four levels, as other rescans), and the enclosing word
and rm state are left alone. All four copies changed alike; the parity
table gains those four rows plus two safe look-alikes (295 pass; on the
previous head exactly the four new deny rows fail).
(cherry picked from commit d4b0b739c3)
The #3698 scanner kept `$` in front of a quote, did not separate on a
backtick, and accepted only the exact `--recursive`/`--force`. So
``echo `rm -rf /` ``, `$'rm' -rf /`, `bash -c $'rm -rf /'` and
`rm --r --f /*` passed the mod and both classic pre-bash helpers. GNU rm
runs each of them as a root delete.
- A backtick separates words and ends a command, as `(` and `)` already do,
and a quoted word that contains one is rescanned.
- A `$` immediately before `'` or `"` is ANSI-C/locale quoting, not part of the word.
- Any unambiguous `--` prefix of --recursive / --force counts as that flag,
as getopt_long does.
The same change is applied to all four copies: the mod, the generator
fallback and both shipped hook-handler.cjs. Fifteen cases are added to the
#3698 parity table (10 deny, 5 safe look-alikes).
(cherry picked from commit ad44701617)
- #3853 withRetry: clamp the final jittered delay to maxDelayMs
- #3855 MonitoringHooks: apply retentionMs on getMetrics/getMetricsSummary reads
- #3841 WorkerQueue: track retry timers and clear them on shutdown
- #3844 statusline collision tests: static import + registered data sources, no live gh/ps/git
Co-Authored-By: RuFlo <ruv@ruv.net>
- bridgeStorePattern no longer turns a bridgeStoreEntry {success:false} into a success receipt
- agentdb_pattern-store checks storeEntry's result in the registry-null fallback and refuses failed writes
- hooks pattern-store falls back to the direct store when the bridge write was refused
- graph writer, native recovery/repair and attestation open SQLite via the better-sqlite3 copy AgentDB resolves, so the idle close cannot strip a live AgentDB handle's WAL/SHM sidecars
Co-Authored-By: RuFlo <ruv@ruv.net>
Switch piped stdout/stderr to blocking writes at CLI.run start so the
process.exit() calls after a command no longer truncate output at the
64 KiB pipe buffer.
Co-Authored-By: RuFlo <ruv@ruv.net>
The hook appends one bounded, masked, fail-open record per surfaced recall to
.claude-flow/data/recall-log.jsonl (digest of the prompt, ids/scores/ranks,
router pick; default on, RUFLO_RECALL_LOG=0 or recallLog.enabled=false to stop).
memory list --format json --embeddings adds each stored vector as int8+scale.
Helper manifest is NOT re-signed here; the integrator signs at release.
Co-Authored-By: RuFlo <ruv@ruv.net>