Skip to content

Releases: AGGIB/Stroq

v0.10.1 - Attack corpus out of the JS bundle

Choose a tag to compare

@AGGIB AGGIB released this 09 Sep 17:05

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

Choose a tag to compare

@AGGIB AGGIB released this 08 Sep 07:59

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.unscannable and default rule deny-secret-unscannable: an egress-shaped action whose input exceeds 2 MiB is denied, not partially scanned. Custom policy.yaml files should carry the rule; stroq attack fails without it.
  • stroq attack gains scenario 13, 13-padded-secret-exfil: 13 scenarios, 9 blocked, 4 asked, 0 passed through.
  • The MCP proxy refuses tools/call arguments 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.tgz

stroq-cli-0.10.0.tgz sha256: e19868334d72ee301f2b9e1058847649bb19bcf0166fe1a6860bcd9453172d6d

stroq attack: 13 scenarios, 9 blocked, 4 asked, 0 passed through.

v0.9.0 - MCP stdio proxy

Choose a tag to compare

@AGGIB AGGIB released this 07 Sep 22:06

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…>). Every tools/call is judged before it is forwarded: a deny or a policy ask comes back as an isError tool 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 (including inputSchema), resources/read and prompts/get results 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 batched tools/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-code or --config <path>, --unwrap, --dry-run): rewrites each stdio mcpServers entry to start through the proxy, preserves every other key, skips HTTP entries, replaces its own wrapper on re-run, refuses a file whose mcpServers is not an object.
  • stroq doctor gains an mcp proxy line per existing client config, with stale-wrapper detection after an upgrade.
  • Self-tamper protection for claude_desktop_config.json and mcp_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-desktop

stroq-cli-0.9.0.tgz sha256: 8761a5c2365e14c6467eaf9dcdd05bda770d70ac617463b7edf2023528746480

stroq attack: 12 scenarios, 8 blocked, 4 asked, 0 passed through.

v0.8.0 - Windsurf adapter

Choose a tag to compare

@AGGIB AGGIB released this 06 Sep 20:54

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, a windsurf hooks line in stroq 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 policy ask is rendered as a block that names the rule (Windsurf hooks cannot prompt; the audit keeps the real ask); 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 from mcp_result; MCP calls are classified as mcp__<server>__<tool> with the arguments in front of the secret egress guard. The policy cwd is the workspace root, never a payload's cwd. Internal errors on pre_run_command, pre_write_code and pre_mcp_tool_use fail closed (exit 2).
  • Installer: merges Stroq's entries into .windsurf/hooks.json (or ~/.codeium/windsurf/hooks.json with --user), preserving your own hooks; idempotent; --dry-run; show_output: true so 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_line is 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 windsurf

stroq-cli-0.8.0.tgz sha256: e26b4159a5e4d58fb2a4b26bd220e253e5ea65f912a6be72e81059657f1875a2

stroq attack: 12 scenarios, 8 blocked, 4 asked, 0 passed through.

v0.7.0 - OpenClaw adapter

Choose a tag to compare

@AGGIB AGGIB released this 06 Sep 16:49

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 openclaw copies 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 when openclaw is on PATH, otherwise they are printed). Restart the Gateway afterwards. The plugin registers before_tool_call (priority 100, every tool) and after_tool_call, and calls stroq hook openclaw pre|post as 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 ask becomes OpenClaw's requireApproval (allow-once or deny, answered with /approve <id> …; a timeout blocks); a deny becomes a terminal block with Stroq 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 ignores params.cwd for policy, so an agent cannot point the secret index at an empty directory.
  • Tool mapping. exec/terminal and the shell aliases → Bash; read/write/edit/apply_patch; web_fetch, web_search/x_search; only ask_user, progress_card, heartbeat_respond, get_goal pass through; everything else (message, browser, tts, image_generate, tool_search, tool_describe, process, code_execution, any MCP tool) is mcp__openclaw__<name>: scanned on post, guarded on pre, fail-closed. Names are matched case-insensitively.
  • Every argument spelling is read and the worst decision wins; caller-supplied file_paths/urls lists 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/ are config.self, and so are openclaw plugins disable|remove|uninstall … and openclaw config set plugins.… — a tainted session cannot switch the plugin off.
  • Resilient install. A recorded stroq.json entry that no longer exists (a pruned npx cache) is skipped with a warning and the plugin falls back to stroq on PATH; init warns when the entry lives in the npx cache; stroq doctor reports the plugin installed only when every file and the manifest id are present.
  • Documented limits: ask needs an approval route (UI or chat channel); MCP server names are unknown to the plugin; process/code_execution are side-effect tools, not shells; unreadable read params are allowed; the plugin's config is read once at registration; plugins that run before Stroq may rewrite params; the plugin needs plugins.entries.stroq.enabled = true (and plugins.allow when 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.cwd honoured as the working directory (plugin and CLI); stroqBin word-splitting; disabling the plugin not treated as self-tampering; a stale stroq.json entry locking the agent out; terminal not 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

Choose a tag to compare

@AGGIB AGGIB released this 06 Sep 11:05

Stroq 0.6.0 adds a fourth agent: GitHub Copilot CLI.

Copilot CLI support

  • stroq init --agent copilot writes .github/hooks/stroq.json (or ~/.copilot/hooks/stroq.json with --user, honouring COPILOT_HOME; --dry-run previews) and registers stroq hook copilot pre / stroq hook copilot post on preToolUse and postToolUse. The file is Stroq's own — Copilot loads every *.json in the hooks directory independently — so no sibling file is ever touched; a stroq.json Stroq 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.
  • ask is a real prompt. Copilot's preToolUse accepts a top-level {"permissionDecision": "allow" | "deny" | "ask", "permissionDecisionReason"}, so a policy ask (git reset --hard, an external push, an unknown npx) prompts the user in the interactive CLI: Stroq asks before this action (<rule>): …. The cloud coding agent turns ask into deny.
  • Fail-closed the way Copilot honours it. An internal error, unparsable stdin, a failed stdin read or a missing/unknown phase on pre exits with code 2, the reason on stderr and an empty stdout — Copilot denies on exit 2 regardless of stdout. post and low-impact pre tools answer errors with silence. A Copilot hook timeout always fails open, so the installed timeoutSec is Copilot's own 30 s and the README recommends a global install.
  • Native tools are mapped: bash/powershell and the aliases shell, 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/task pass through; any other name is an MCP call mcp__copilot__<tool> (Copilot's hooks never report the server).
  • Every argument spelling is read, and only Stroq's own lists are judged. toolArgs as an object or a JSON string; commands from command/cmd/input/script/raw; file paths from path/file_path/raw and URLs from url/uri/href/raw, each distinct candidate judged and the worst decision wins; a caller-supplied urls/file_paths list is dropped rather than trusted; every fan-out is bounded (copilot-too-many-targets beyond 64 files or URLs); a high-impact call whose toolArgs was non-empty but yielded no command, path or URL is denied as copilot-unreadable-input.
  • Self-protection. .github/hooks/*, .github/copilot/settings(.local).json, .copilot/hooks/*, .copilot/settings.json and .copilot/config.json are config.self for every agent, so a tainted session cannot rewrite Stroq's hook file or switch hooks off with disableAllHooks.
  • stroq doctor gains a copilot hooks line (a hook file without version: 1, which Copilot drops, is reported not installed); a runnable demo (examples/demo/run-copilot-demo.sh) asserts every decision, including the real ask, and runs in CI.
  • Documented limits: the secret guard scans a web_fetch call's url and prompt only; find .github -name stroq.json -delete is 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_fetch losing its URL for any shape but {url: string}; shell/sh spellings bypassing the shell rule set; a caller-supplied urls/file_paths list 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

Choose a tag to compare

@AGGIB AGGIB released this 05 Sep 16:38

Stroq 0.5.0 adds a third agent: OpenAI Codex CLI.

Codex support

  • stroq init --agent codex writes .codex/hooks.json (or ~/.codex/hooks.json with --user; --dry-run previews) and registers stroq hook codex on PreToolUse (Bash|exec_command|shell|local_shell|apply_patch|ApplyPatch|mcp__.*) and PostToolUse (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 policy ask (destructive commands, external pushes, unknown-package npx from tool output) is rendered as a deny whose reason begins Stroq would ask before this action (<rule>): … and names ~/.stroq/policy.yaml as the place to relax it. The audit keeps the real ask.
  • Fail-closed the only way Codex honours it. An internal error, unparsable stdin or a failed stdin read on a high-impact PreToolUse exits with code 2 and the reason on stderr, which Codex treats as a block without parsing stdout. Codex has no failClosed knob, so if the hook command itself cannot start Codex continues — install globally (npm install -g @stroq/cli).
  • apply_patch is 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 plus patch/arguments. A high-impact call whose tool_input was non-empty but yielded no command or path is denied as codex-unreadable-input, with a reason that names only the keys it saw.
  • Self-protection. .codex/hooks.json and .codex/config.toml are now config.self for 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 a hooks value that is not an object, and re-runs idempotently.
  • stroq doctor gains a codex hooks line; a runnable demo (examples/demo/run-codex-demo.sh) asserts every decision and runs in CI.
  • Documented limits: ask is 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-shaped stroq attack scenarios 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.json header past 200 KB; Bash commands under keys other than command and apply_patch bodies 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 turned git commit -m 'rm -rf /' into a destructive command; and a hooks.json shape ambiguity that could make doctor report 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

Choose a tag to compare

@AGGIB AGGIB released this 05 Sep 12:33

Stroq 0.4.0 adds a second agent and a second install path.

Cursor support

  • stroq init --agent cursor writes .cursor/hooks.json (or ~/.cursor/hooks.json with --user; --dry-run previews) and registers stroq hook cursor on six events: beforeShellExecution and beforeMCPExecution are blocking and installed with failClosed: true, answered as {"permission":"deny"|"ask","user_message","agent_message"}; beforeReadFile scans 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); afterShellExecution and afterMCPExecution scan, taint and record provenance (a suspect MCP result adds additional_context); afterFileEdit is audit-only because Cursor has no beforeFileEdit.
  • 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 /tmp does not shed it); MCP tool names are sanitised so no __ can be synthesised inside a segment and a pre-shaped mcp__… name cannot override the reported server; non-object tool inputs stay visible to the secret guard.
  • stroq doctor gains a cursor hooks line; 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/Stroq then /plugin install stroq@stroq installs the same hooks as stroq init without touching .claude/settings.json. The hook wrapper prefers a globally installed stroq and falls back to npx -y @stroq/cli@<pinned>; if neither can start, a PreToolUse event 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 attack badge, 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

Choose a tag to compare

@AGGIB AGGIB released this 04 Sep 23:57

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 class secret.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 doctor reports the index and fails when a source could not be read, .env* files were dropped, or the index was corrupt; stroq why explains 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.yaml replaces the default policy, so add deny-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 $schema token fetch, an env | curl exfiltration, a hooks-removal write, the rm -rf ~ and drizzle-kit push --force incidents, 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 prints blocked / asked / passed per scenario with totals. Exit code 1 when any scenario misbehaves, so a weakened policy fails CI; --json for badges; --only <id> for one scenario. Each scenario cites the public incident it models.
  • Classifier coverage the suite exposed: rm -r of 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 delete are shell.destructive; gh repo create … --push is git.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 attack with 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

Choose a tag to compare

@AGGIB AGGIB released this 04 Sep 19:08

Added

  • Provenance. PostToolUse now 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; PreToolUse attributes proposed actions to those traces and adds two action classes, origin.untrusted and origin.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 via classifierContext. Packages the project already depends on are never counted for shell commands.
    • Upgrade note: a custom ~/.stroq/policy.yaml replaces the default policy wholesale, so provenance is enforced only if it contains rules for origin.suspect and origin.untrusted — copy deny-origin-suspect and ask-origin-untrusted from policies/default.yaml (keeping them ahead of the ask-* rules).
  • 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 producing origin.* decisions.
  • Demo: a Sentry-style poisoned MCP result (examples/demo/events/4-post-mcp-sentry.json) followed by the npx it suggests.

Fixed

  • CI's rules-bundle check no longer depends on machine speed. Previously, CI regenerated packages/core/src/rules.bundle.json and 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 as pnpm build:rules --check --advisory-perf, and available locally as pnpm check:rules) now re-verifies rule compilation and the benign-corpus scan against the already-committed rules/atr-disabled.json and byte-compares an in-memory rebuild against the committed bundle, without measuring performance at all. --advisory-perf still times every rule and prints a WARNING for 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.ts is now a thin CLI over scripts/lib/rules-pipeline.ts, a set of pure functions (load, compile, benign-corpus gate, timing gate, assemble, compare) covered directly by packages/core/test/rules/rules-pipeline.test.ts.
  • npm package published as @stroq/cli; the unscoped name stroq is refused by the registry's similarity check. The CLI binary is unaffected — it's still invoked as stroq (npm install -g @stroq/cli, then stroq init; or npx @stroq/cli init for one-off use).

Install or upgrade: npx @stroq/cli@0.2.0 init (or npm install -g @stroq/cli@0.2.0).