Releases: AGGIB/Stroq
Release list
v0.10.1 - Attack corpus out of the JS bundle
Patch release: stroq attack's recorded incident corpus ships as inert JSON instead of being bundled into dist/index.js, and the sourcemap is no longer published. Aimed at giving npm's publish-time scanner a cleaner tarball — every release since 0.3.0 has sat stuck in "Validating" on npmjs.com's Staged Packages review.
No behavior change: stroq attack, DISCLOSURE, and the dual-use declaration are all unchanged.
See CHANGELOG.md for details.
If npm still shows an older version, install the tarball directly:
npm install -g https://github.com/AGGIB/Stroq/releases/download/v0.10.1/stroq-cli-0.10.1.tgz
v0.10.0 - secret scan window
Stroq 0.10.0 — secret scan window
Closes a padding bypass of the secret egress guard that affected every adapter: the guard scanned only the first 256 KiB of a tool input, so an agent could exfiltrate a known secret by padding the arguments of an allowed network command, fetch or MCP call. Found by the MCP proxy review; fixed in core for hooks and proxy alike.
Changed
- The secret egress guard scans the whole tool input in 256 KiB windows (4 KiB overlap) up to 2 MiB, with a per-window candidate cap that never decides what is looked up.
- New action class
secret.unscannableand default ruledeny-secret-unscannable: an egress-shaped action whose input exceeds 2 MiB is denied, not partially scanned. Custompolicy.yamlfiles should carry the rule;stroq attackfails without it. stroq attackgains scenario 13,13-padded-secret-exfil: 13 scenarios, 9 blocked, 4 asked, 0 passed through.- The MCP proxy refuses
tools/callarguments above 2 MiB (was 256 KiB), in front of the engine's own deny.
Limits
Inline payloads above 2 MiB in an egress action are refused; use a file path instead. WebFetch scans url and prompt only. Tool results keep their own 200 000-character scan clip.
Install
npm install -g @stroq/cli@0.10.0 # once npm's review clears; until then:
npm install -g https://github.com/AGGIB/Stroq/releases/download/v0.10.0/stroq-cli-0.10.0.tgzstroq-cli-0.10.0.tgz sha256: e19868334d72ee301f2b9e1058847649bb19bcf0166fe1a6860bcd9453172d6d
stroq attack: 13 scenarios, 9 blocked, 4 asked, 0 passed through.
v0.9.0 - MCP stdio proxy
Stroq 0.9.0 — MCP stdio proxy
stroq mcp -- <server command> puts Stroq in front of any stdio MCP server, for clients that have no hooks (Claude Desktop, any mcpServers-style client) and as a second layer for the ones that do. Supported today: Claude Code, Cursor, Codex, Copilot CLI, Windsurf (native hooks) · OpenClaw (in-process plugin) · any MCP client (stdio proxy).
Added
- MCP stdio proxy (
stroq mcp --server <name> --client <client> --cwd <dir> -- <cmd…>). Everytools/callis judged before it is forwarded: a deny or a policyaskcomes back as anisErrortool result on the same id, naming the rule, and never reaches the server; the arguments are scanned whole by the secret egress guard, up to its 256 KiB window, and refused above it.tools/call,tools/list(includinginputSchema),resources/readandprompts/getresults go through the content scan, taint and provenance, and a suspect tool result is forwarded with one appended warning text item. Everything else is forwarded byte-for-byte, in order, with backpressure in both directions; legacy and 2026-07-28 (stateless, MRTR) servers alike. Fail-closed on malformed, unaddressable, BOM-prefixed-unparseable and batchedtools/calls and on engine errors; oversize server lines streamed through unparsed; signals relayed with a SIGKILL escalation. stroq init --agent mcp(--client claude-desktop|windsurf|cursor|claude-codeor--config <path>,--unwrap,--dry-run): rewrites each stdiomcpServersentry to start through the proxy, preserves every other key, skips HTTP entries, replaces its own wrapper on re-run, refuses a file whosemcpServersis not an object.stroq doctorgains anmcp proxyline per existing client config, with stale-wrapper detection after an upgrade.- Self-tamper protection for
claude_desktop_config.jsonandmcp_config.json. - Demo
examples/demo/run-mcp-demo.sh(fake stdio server, six asserted scenarios) in CI.
Limits
No ask (rendered as a block naming the rule). stdio servers only; HTTP entries are skipped and unprotected; VS Code and Codex config shapes are not rewritten. Results delivered outside the tools/call response (tasks extension, subscriptions, legacy sampling/elicitation payloads) are not scanned. Session taint is per client, not per conversation. Batches containing a tools/call are refused. The project directory is the one init ran in. .mcp.json and .cursor/mcp.json are not self-tamper protected. Tool arguments whose serialised form exceeds 256 KiB, the secret guard's scan window, are refused fail-closed (mcp-proxy-arguments-too-large); a server result above 8 MiB is forwarded unparsed, unscanned and untainted. Wire handling comes from the specification and three proxies, not a recorded client; Windows untested.
Install
npm install -g @stroq/cli@0.9.0 # once npm's review clears; until then:
npm install -g https://github.com/AGGIB/Stroq/releases/download/v0.9.0/stroq-cli-0.9.0.tgz
stroq init --agent mcp --client claude-desktopstroq-cli-0.9.0.tgz sha256: 8761a5c2365e14c6467eaf9dcdd05bda770d70ac617463b7edf2023528746480
stroq attack: 12 scenarios, 8 blocked, 4 asked, 0 passed through.
v0.8.0 - Windsurf adapter
Stroq 0.8.0 — Windsurf adapter
npx @stroq/cli init --agent windsurf puts Stroq on Windsurf's Cascade Hooks. Supported today: Claude Code, Cursor, Codex, Copilot CLI, Windsurf (native hooks) · OpenClaw (in-process plugin).
Added
- Windsurf adapter (
stroq hook windsurf,stroq init --agent windsurf, awindsurf hooksline instroq doctor). Six Cascade Hook events:pre_read_code,post_read_code,pre_write_code,pre_run_command,pre_mcp_tool_use,post_mcp_tool_use. A deny is exit 2 with the reason on stderr; a policyaskis rendered as a block that names the rule (Windsurf hooks cannot prompt; the audit keeps the realask); a suspect scan is exit 2 with the warning, which is how Cascade shows hook text to the agent. Files Cascade reads are scanned by reading them (1 MiB cap); MCP results are scanned frommcp_result; MCP calls are classified asmcp__<server>__<tool>with the arguments in front of the secret egress guard. The policy cwd is the workspace root, never a payload'scwd. Internal errors onpre_run_command,pre_write_codeandpre_mcp_tool_usefail closed (exit 2). - Installer: merges Stroq's entries into
.windsurf/hooks.json(or~/.codeium/windsurf/hooks.jsonwith--user), preserving your own hooks; idempotent;--dry-run;show_output: trueso block reasons show in the Cascade UI. - Self-tamper protection for Windsurf's hook files (workspace, both user files, the Linux and macOS system files) in both the Bash classifier and the Write/Edit path check.
- Demo
examples/demo/run-windsurf-demo.sh(7 events) in CI;command_lineis now a recognised command spelling for every adapter.
Limits
No ask. Command output is not visible to Cascade hooks, so a poisoned command output cannot taint a Windsurf session (files read and MCP results do). No hook timeout setting; keep @stroq/cli installed globally. Any exit other than 0 or 2 is an allow on Windsurf. The wire format comes from Windsurf's documentation and third-party integrations, not a recorded session. Windows powershell entries are written but untested; system-level and JetBrains files are not written by init.
Install
npm install -g @stroq/cli@0.8.0 # once npm's review clears; until then:
npm install -g https://github.com/AGGIB/Stroq/releases/download/v0.8.0/stroq-cli-0.8.0.tgz
stroq init --agent windsurfstroq-cli-0.8.0.tgz sha256: e26b4159a5e4d58fb2a4b26bd220e253e5ea65f912a6be72e81059657f1875a2
stroq attack: 12 scenarios, 8 blocked, 4 asked, 0 passed through.
v0.7.0 - OpenClaw adapter
Stroq 0.7.0 adds a fifth agent: OpenClaw — the first adapter that runs inside the agent's own process.
OpenClaw support
stroq init --agent openclawcopies a small dependency-free JavaScript plugin into~/.stroq/openclaw-plugin/and registers it (openclaw plugins install --link+openclaw plugins enable stroq; the commands run only whenopenclawis on PATH, otherwise they are printed). Restart the Gateway afterwards. The plugin registersbefore_tool_call(priority 100, every tool) andafter_tool_call, and callsstroq hook openclaw pre|postas a child process, so the engine, rules, policy, provenance, secret-egress guard and hash-chained audit are shared with the other four adapters unchanged.- A real approval prompt. A policy
askbecomes OpenClaw'srequireApproval(allow-onceordeny, answered with/approve <id> …; a timeout blocks); adenybecomes a terminalblockwithStroq blocked this action (<rule>): …. - Fail-closed on every tool. A missing binary, a spawn error, a non-zero exit, a 10 s timeout, a non-JSON or unknown reply, a reply over 1 MiB or an aborted run all block; the CLI exits 2 with the reason on stderr on an internal error. A suspect result taints the session and is logged at
warn(observe-only hook; no warning reaches the model in this version). - The trusted working directory always wins. The plugin sends its configured
workspace(or the Gateway's cwd) and the CLI ignoresparams.cwdfor policy, so an agent cannot point the secret index at an empty directory. - Tool mapping.
exec/terminaland the shell aliases → Bash;read/write/edit/apply_patch;web_fetch,web_search/x_search; onlyask_user,progress_card,heartbeat_respond,get_goalpass through; everything else (message,browser,tts,image_generate,tool_search,tool_describe,process,code_execution, any MCP tool) ismcp__openclaw__<name>: scanned onpost, guarded onpre, fail-closed. Names are matched case-insensitively. - Every argument spelling is read and the worst decision wins; caller-supplied
file_paths/urlslists are dropped; more than 64 targets →openclaw-too-many-targets; an unreadable high-impact call →openclaw-unreadable-input. - Self-protection.
.openclaw/openclaw.json,.openclaw/plugins/,.openclaw/extensions/areconfig.self, and so areopenclaw plugins disable|remove|uninstall …andopenclaw config set plugins.…— a tainted session cannot switch the plugin off. - Resilient install. A recorded
stroq.jsonentry that no longer exists (a pruned npx cache) is skipped with a warning and the plugin falls back tostroqon PATH;initwarns when the entry lives in the npx cache;stroq doctorreports the plugin installed only when every file and the manifest id are present. - Documented limits:
askneeds an approval route (UI or chat channel); MCP server names are unknown to the plugin;process/code_executionare side-effect tools, not shells; unreadablereadparams are allowed; the plugin's config is read once at registration; plugins that run before Stroq may rewriteparams; the plugin needsplugins.entries.stroq.enabled = true(andplugins.allowwhen an allowlist is set); the wire shapes are inferred from OpenClaw's documentation and one production plugin.
Also
- The Copilot and OpenClaw adapters share one kind-to-input reader and one unreadable-input guard; the shared post-result scan was split out so Codex and Copilot stay byte-identical.
Verification
- 1345 tests across 77 files (up from 1062 / 66); CI on Node 22 and 24 runs all five demos and the attack suite (
12 scenarios: 8 blocked, 4 asked, 0 passed through). - Per-task reviews, a whole-branch security review and one fix wave, each probing the real engine and the real plugin with adversarial payloads. Found and closed before merge: generative/catalog tools exempt from the scan and the egress guard; case-sensitive tool names;
params.cwdhonoured as the working directory (plugin and CLI);stroqBinword-splitting; disabling the plugin not treated as self-tampering; a stalestroq.jsonentry locking the agent out;terminalnot treated as a shell.
Install: npm install -g @stroq/cli then stroq init (Claude Code), stroq init --agent cursor|codex|copilot|openclaw; or the release tarball attached below while npm's review holds the version.
v0.6.0 - Copilot CLI adapter
Stroq 0.6.0 adds a fourth agent: GitHub Copilot CLI.
Copilot CLI support
stroq init --agent copilotwrites.github/hooks/stroq.json(or~/.copilot/hooks/stroq.jsonwith--user, honouringCOPILOT_HOME;--dry-runpreviews) and registersstroq hook copilot pre/stroq hook copilot postonpreToolUseandpostToolUse. The file is Stroq's own — Copilot loads every*.jsonin the hooks directory independently — so no sibling file is ever touched; astroq.jsonStroq did not write is replaced with a printed notice. The engine, rules, policy, provenance, secret-egress guard and hash-chained audit are shared with the Claude Code, Cursor and Codex adapters unchanged.askis a real prompt. Copilot'spreToolUseaccepts a top-level{"permissionDecision": "allow" | "deny" | "ask", "permissionDecisionReason"}, so a policyask(git reset --hard, an external push, an unknownnpx) prompts the user in the interactive CLI:Stroq asks before this action (<rule>): …. The cloud coding agent turnsaskintodeny.- Fail-closed the way Copilot honours it. An internal error, unparsable stdin, a failed stdin read or a missing/unknown phase on
preexits with code 2, the reason on stderr and an empty stdout — Copilot denies on exit 2 regardless of stdout.postand low-impactpretools answer errors with silence. A Copilot hook timeout always fails open, so the installedtimeoutSecis Copilot's own 30 s and the README recommends a global install. - Native tools are mapped:
bash/powershelland the aliasesshell,sh,zsh,exec_command,local_shell,run_command→ Bash;view→ Read;create→ Write;edit/str_replace_editor→ Edit (command: "view"→ Read; the editor sub-command is never treated as a shell command);apply_patch→ Write through its header lines;web_fetch/web_search;grep/rg/glob;ask_user/taskpass through; any other name is an MCP callmcp__copilot__<tool>(Copilot's hooks never report the server). - Every argument spelling is read, and only Stroq's own lists are judged.
toolArgsas an object or a JSON string; commands fromcommand/cmd/input/script/raw; file paths frompath/file_path/rawand URLs fromurl/uri/href/raw, each distinct candidate judged and the worst decision wins; a caller-suppliedurls/file_pathslist is dropped rather than trusted; every fan-out is bounded (copilot-too-many-targetsbeyond 64 files or URLs); a high-impact call whosetoolArgswas non-empty but yielded no command, path or URL is denied ascopilot-unreadable-input. - Self-protection.
.github/hooks/*,.github/copilot/settings(.local).json,.copilot/hooks/*,.copilot/settings.jsonand.copilot/config.jsonareconfig.selffor every agent, so a tainted session cannot rewrite Stroq's hook file or switch hooks off withdisableAllHooks. stroq doctorgains acopilot hooksline (a hook file withoutversion: 1, which Copilot drops, is reported not installed); a runnable demo (examples/demo/run-copilot-demo.sh) asserts every decision, including the realask, and runs in CI.- Documented limits: the secret guard scans a
web_fetchcall'surlandpromptonly;find .github -name stroq.json -deleteis not self-tampering; hooks may not fire in some subagents and never from plugins; a timeout fails open; the wire format is inferred from GitHub's reference and third-party examples, fixtures are hand-written; Windows untested.
Also
- The Codex adapter shares its guard ordering, per-target fan-out, direct-deny audit entry and result reading with the Copilot adapter through two new modules (
adapters/pre-decision.ts,adapters/tool-result.ts); its behaviour is unchanged and its tests are the acceptance check.
Verification
- 1062 tests across 66 files (up from 875 / 62); CI on Node 22 and 24 runs all four demos and the attack suite (
12 scenarios: 8 blocked, 4 asked, 0 passed through). - The adapter went through per-task reviews, a whole-branch security review and two fix rounds, each probing the real engine with adversarial payloads. Found and closed before merge: a
version-less hook file reported as installed;web_fetchlosing its URL for any shape but{url: string};shell/shspellings bypassing the shell rule set; a caller-suppliedurls/file_pathslist shadowing the real target; an unbounded URL fan-out that could run past Copilot's fail-open timeout.
Install: npm install -g @stroq/cli then stroq init (Claude Code), stroq init --agent cursor, stroq init --agent codex or stroq init --agent copilot; or npx @stroq/cli init.
v0.5.0 - Codex adapter
Stroq 0.5.0 adds a third agent: OpenAI Codex CLI.
Codex support
stroq init --agent codexwrites.codex/hooks.json(or~/.codex/hooks.jsonwith--user;--dry-runpreviews) and registersstroq hook codexonPreToolUse(Bash|exec_command|shell|local_shell|apply_patch|ApplyPatch|mcp__.*) andPostToolUse(Bash|exec_command|shell|local_shell|mcp__.*). The engine, rules, policy, provenance, secret-egress guard and hash-chained audit are shared with the Claude Code and Cursor adapters unchanged.- Codex has no
ask. Every policyask(destructive commands, external pushes, unknown-packagenpxfrom tool output) is rendered as a deny whose reason beginsStroq would ask before this action (<rule>): …and names~/.stroq/policy.yamlas the place to relax it. The audit keeps the realask. - Fail-closed the only way Codex honours it. An internal error, unparsable stdin or a failed stdin read on a high-impact
PreToolUseexits with code 2 and the reason on stderr, which Codex treats as a block without parsing stdout. Codex has nofailClosedknob, so if the hook command itself cannot start Codex continues — install globally (npm install -g @stroq/cli). apply_patchis classified from its patch body. Paths come from the*** Add/Update/Delete File:and*** Move to:header lines; one policy decision per path, the most severe wins, every path is audited; a patch declaring more than 64 files is denied (codex-patch-too-large) because classifying more would run past Codex's hook timeout, which fails open.- Defensive about a wire format that is inferred, not recorded. The command is read from
command/cmd/input/script/raw(string, argv or a one-level nested object;[bash, -c, script]classifies the script alone, other argv is POSIX-quoted before joining), every command field found is judged on its worst decision, and patch text is read from those pluspatch/arguments. A high-impact call whosetool_inputwas non-empty but yielded no command or path is denied ascodex-unreadable-input, with a reason that names only the keys it saw. - Self-protection.
.codex/hooks.jsonand.codex/config.tomlare nowconfig.selffor every agent, so a tainted session cannot rewrite Codex's hook file or the file that can disable hooks. - Installer. Always writes the official nested
{ "hooks": { … } }shape, migrates root-level events from a community-style flat file without dropping anything, preserves foreign groups, events and unknown keys, ignores ahooksvalue that is not an object, and re-runs idempotently. stroq doctorgains acodex hooksline; a runnable demo (examples/demo/run-codex-demo.sh) asserts every decision and runs in CI.- Documented limits:
askis lossy; runtime fail-open if Node cannot start; hosted tools (WebSearch) never reach hooks; project-local hooks need the.codex/layer trusted (or--user); older releases need[features] hooks = true;PermissionRequest,updatedInput, session/compaction events, inline[hooks]TOML installation and Codex-shapedstroq attackscenarios are out of scope; Windows is untested. Recording real Codex payloads as fixtures is the next step.
Also
- README: the downloads badge shows the all-time total (npm's monthly point endpoint lags about a week). SECURITY.md supported versions:
0.5.x.
Verification
- 875 tests across 62 files (up from 744 / 56), coverage ≈ 97 / 89 / 100 / 99 (statements / branches / functions / lines); CI on Node 22 and 24 runs all three demos and the attack suite (
12 scenarios: 8 blocked, 4 asked, 0 passed through). - The adapter went through per-task reviews, a whole-branch security review and two fix rounds. The reviews found and closed: a patch-truncation bypass that hid a
.codex/hooks.jsonheader past 200 KB;Bashcommands under keys other thancommandandapply_patchbodies sent as arrays or nested objects reaching the engine empty and being allowed; a stdin-failure exit code Codex would have treated as fail-open; argv joining that turnedgit commit -m 'rm -rf /'into a destructive command; and ahooks.jsonshape ambiguity that could makedoctorreport a hook Codex never loads. The final re-review probed 38 payload shapes and 6 tool-name spellings against the real engine.
Install: npm install -g @stroq/cli then stroq init (Claude Code), stroq init --agent cursor or stroq init --agent codex; or npx @stroq/cli init.
v0.4.0 - Cursor adapter and Claude Code plugin
Stroq 0.4.0 adds a second agent and a second install path.
Cursor support
stroq init --agent cursorwrites.cursor/hooks.json(or~/.cursor/hooks.jsonwith--user;--dry-runpreviews) and registersstroq hook cursoron six events:beforeShellExecutionandbeforeMCPExecutionare blocking and installed withfailClosed: true, answered as{"permission":"deny"|"ask","user_message","agent_message"};beforeReadFilescans the file body before the agent sees it (a suspect file is allowed with a warning and taints the session; a credential path under taint is denied);afterShellExecutionandafterMCPExecutionscan, taint and record provenance (a suspect MCP result addsadditional_context);afterFileEditis audit-only because Cursor has nobeforeFileEdit.- The engine, rules, policy, provenance, secret-egress guard and audit format are shared with the Claude Code adapter unchanged. Both the official and the community field spellings are accepted.
- Hardened against a hostile MCP server or repository: the workspace root, not the shell's cwd, is the project for the secret index (a
cd /tmpdoes not shed it); MCP tool names are sanitised so no__can be synthesised inside a segment and a pre-shapedmcp__…name cannot override the reported server; non-object tool inputs stay visible to the secret guard. stroq doctorgains acursor hooksline; a runnable demo (examples/demo/run-cursor-demo.sh) runs in CI.- Documented limits: Cursor honours no output after shell commands (the taint still applies to the next action), edits cannot be blocked (recorded in the audit as decisions that were not enforced), multi-root workspaces index only the first root; Tab hooks, generic
preToolUse/postToolUse,beforeSubmitPrompt,updated_input, enterprise/team hook locations and the Cursor CLI are out of scope for now; Windows is untested.
Claude Code plugin and marketplace
- The repository is a plugin marketplace with one plugin:
/plugin marketplace add AGGIB/Stroqthen/plugin install stroq@stroqinstalls the same hooks asstroq initwithout touching.claude/settings.json. The hook wrapper prefers a globally installedstroqand falls back tonpx -y @stroq/cli@<pinned>; if neither can start, aPreToolUseevent exits 2 (block), so a missing runtime never silently disables the firewall.
Also
- Site: current default policy in the YAML figure, shipped-feature tiles, Vercel Web Analytics (first-party, cookieless); README:
stroq attackbadge, seven-scene demo GIF; SECURITY.md supported versions.
Verification
- 744 tests across 56 files, coverage ≈ 97 / 89 / 100 / 99 (statements / branches / functions / lines); CI on Node 22 and 24 runs both demos and the attack suite.
- The Cursor adapter went through a whole-branch security review that found and fixed a tool-name shape (
"__","!", non-ASCII) which would have made an MCP call unclassifiable and let a secret value out through it on Cursor only.
Install: npm install -g @stroq/cli then stroq init (Claude Code) or stroq init --agent cursor; or npx @stroq/cli init.
v0.3.0 - Secret egress guard and stroq attack
Stroq 0.3.0 ships the two remaining parts of the killer-feature research: the secret egress guard and stroq attack.
Secret egress guard (#9)
- Values of known secrets — the project's
.env*files,~/.aws/credentials,~/.npmrc,~/.netrc,~/.docker/config.json, and credential-shaped environment variables (hashed live, never stored) — are indexed as salted hashes in~/.stroq/secrets.json. - An outbound action (network command, web fetch, MCP call, external push, encoded exec) whose arguments contain one of those values is denied by the new first default rule
deny-secret-egress(action classsecret.egress). The reason names the secret and its file, never the value; the value is redacted from the audit summary in raw, decoded and URL-encoded forms. stroq canary [--name <NAME>]prints a decoy secret to plant; any outbound use of it is a certain positive that also marks the session suspect.stroq doctorreports the index and fails when a source could not be read,.env*files were dropped, or the index was corrupt;stroq whyexplains secret-egress denials; WebFetch now fails closed on engine errors.- Hardened after review: candidate extraction is bounded by input bytes (not by a candidate count an attacker could pad past), URL/hostname/
_URL-style values are not indexed, at most 32 project.env*files are read. Limits and the false-positive recovery path are documented in the README. - Upgrade note: a custom
~/.stroq/policy.yamlreplaces the default policy, so adddeny-secret-egress(first) to be protected.
stroq attack (#10)
- Replays twelve recorded, incident-backed attack scenarios — a protestware README, Sentry agentjacking, Comment-and-Control, s1ngularity, RoguePilot's
$schematoken fetch, anenv | curlexfiltration, a hooks-removal write, therm -rf ~anddrizzle-kit push --forceincidents, a ToxicSkills base64 installer, a fetched page asking for~/.ssh/id_rsa, and a parent-directory wipe — through the engine with your policy, in throwaway directories, and printsblocked/asked/passedper scenario with totals. Exit code 1 when any scenario misbehaves, so a weakened policy fails CI;--jsonfor badges;--only <id>for one scenario. Each scenario cites the public incident it models. - Classifier coverage the suite exposed:
rm -rof any~…target,terraform/tofu destroy,terraform apply -destroy,pulumi destroy,drizzle-kit push --force,prisma migrate reset,prisma db push --force-reset/--accept-data-loss,supabase db reset --linked,gh repo deleteareshell.destructive;gh repo create … --pushisgit.push_external.
Verification
- 660+ tests, coverage ≈ 96 / 87 / 99 / 97 (statements / branches / functions / lines), CI on Node 22 and 24 including the demo and the attack suite.
stroq attackwith the default policy:12 scenarios: 8 blocked, 4 asked, 0 passed through — every attack was stopped.
Install: npm install -g @stroq/cli then stroq init, or npx @stroq/cli init.
v0.2.0 - Provenance
Added
- Provenance.
PostToolUsenow records the actionable atoms of every scanned output (URLs/hosts, package specs, pipe-to-shell commands, base64 blobs) in a per-session, redacted, bounded trace;PreToolUseattributes proposed actions to those traces and adds two action classes,origin.untrustedandorigin.suspect, evaluated by two new default rules (ask-origin-untrusted,deny-origin-suspect). Hook reasons and audit entries carry the evidence ("… appeared in the output of mcp__sentry__get_issue (…) 40 s ago"); clean outputs that contain atoms are annotated for Claude Code's auto-mode classifier viaclassifierContext. Packages the project already depends on are never counted for shell commands.- Upgrade note: a custom
~/.stroq/policy.yamlreplaces the default policy wholesale, so provenance is enforced only if it contains rules fororigin.suspectandorigin.untrusted— copydeny-origin-suspectandask-origin-untrustedfrompolicies/default.yaml(keeping them ahead of theask-*rules).
- Upgrade note: a custom
stroq why [--seq <n>]: explains the most recent denied or asked action — rule, provenance evidence, and session taint.stroq untaint --session <id>now also clears the session's provenance trace, so a false positive stops producingorigin.*decisions.- Demo: a Sentry-style poisoned MCP result (
examples/demo/events/4-post-mcp-sentry.json) followed by thenpxit suggests.
Fixed
- CI's rules-bundle check no longer depends on machine speed. Previously, CI regenerated
packages/core/src/rules.bundle.jsonand diffed it against the committed copy; a GitHub runner slower than the maintainer's machine could push a rule over the regex performance gate's threshold, disabling a rule the committed bundle didn't and failing CI with an unrelated-looking diff.scripts/build-rules.ts --check(wired into CI aspnpm build:rules --check --advisory-perf, and available locally aspnpm check:rules) now re-verifies rule compilation and the benign-corpus scan against the already-committedrules/atr-disabled.jsonand byte-compares an in-memory rebuild against the committed bundle, without measuring performance at all.--advisory-perfstill times every rule and prints aWARNINGfor anything over threshold that isn't already disabled, but never fails the build. - The local performance gate's threshold (
pnpm build:rules, run by a maintainer to regenerate the bundle) dropped from 50 ms to 25 ms, leaving margin for machines slower than the one that produced the committed bundle.
Changed
scripts/build-rules.tsis now a thin CLI overscripts/lib/rules-pipeline.ts, a set of pure functions (load, compile, benign-corpus gate, timing gate, assemble, compare) covered directly bypackages/core/test/rules/rules-pipeline.test.ts.- npm package published as
@stroq/cli; the unscoped namestroqis refused by the registry's similarity check. The CLI binary is unaffected — it's still invoked asstroq(npm install -g @stroq/cli, thenstroq init; ornpx @stroq/cli initfor one-off use).
Install or upgrade: npx @stroq/cli@0.2.0 init (or npm install -g @stroq/cli@0.2.0).