Repository navigation
v0.25.0
Install: npm i -g @stroq/cli@0.25.0, or npx @stroq/cli@latest init. Every change, with the reasons, is in the changelog.
Added
stroq provechecks that the hook really stops things in your agent. A hook written into an agent's settings says nothing about whether the agent obeys it.stroq proveasks Claude Code itself,claude -p, for up to three short Haiku requests with your own login (subscription usage; an API key is never used, and the check kills the host at once if it sees one): one harmless command the policy allows, one the policy denies (agit configsetting that makes git run a program, written to a file of its own, nothing is installed), and the denied one again with the hook out of the way, to show that the host would have run it. A host isverifiedonly when the allowed one ran, the denied one was stopped in the hook's own words and left no file, and the same command did run without the hook; a model that declines, a limit, a host that stops the control by its own rules, or a missing billing field isinconclusiveand neververifiedorfailed. A third probe (acurlwith a made-up key) is optional: the model itself usually declines it. Everything runs in a temporary folder that is removed, also on Ctrl-C. Measured on Claude Code 2.1.271: verified in exactly three requests.stroq doctorshows the result asproof:and marks it stale when Stroq, the policy or the host version changes. It shows that the hook linestroq initwrites works with that host version; it does not show that your own settings file is loaded, and not that other versions or hosts behave the same. Claude Code only for now;--dry-runshows what it would do,--jsonprints the result,--max-requestsraises the cap (at most 6). An agent cannot run it.stroq vet --from <dir>reads a skill, MCP server or plugin before you add it. It reads the folder without following a link or running anything, and prints its passport: what it asks for or can do (the tools its front matter allows, its hooks, the hosts its shell text and code reach, the environment variables and credential files it reads, the programs it runs, install scripts, dependencies that do not come from a registry, native or obfuscated code, symlinks that leave the folder), each marked[declared]or[observed]with the file and line, sensitive ones first, then the signals and every blind spot (what was not looked at: nothing was run, what it fetches at run time, other languages, prose). The name is the source's where the source names it; a package that calls itself something else gets adeclared-name-differssignal. A reading that fails is named and the command exits 1, so an artifact cannot hide a finding by breaking a reader.--jsonprints the canonical passport (the same bytes for the same files, to commit),--expect <digest>checks thestroq-tree/1digest (not on Windows, where a folder has no executable bit),--cardprints a Markdown snippet for a README,--out <file>writes to a new file. It is a reading of the files, not a security certificate, and it says so.stroq installnow says that Stroq has no install and points toinitandvet, instead of suggestinguninstall. Not yet:--online(npm, GitHub), archives,add/remove.stroq policy checkandstroq policy testshow what an edit of your policy does before you use it.checkreads the file exactly as the hook does and names every fault by its field (a policy that does not load makes the hook fail closed, and it says so), then lints it: a rule that an earlier one always decides first, a protection of the default policy that this one quietly loosens (each of the 20 action classes alone, in a clean and a tainted session:secret.egressgoing from deny to allow is an error), a threshold far above the default's, and a field Stroq does not read (a typo such astaintt:silently leaves a rule matching everything).testreplays the 36 attack scenarios and the 75 scenarios of ordinary work under your policy and under a baseline (the default unless you name one), and prints every step whose decision changed (<scenario>/<step>: deny -> allow (looser)), the attacks that now pass, the ordinary work it now interrupts, and the expectations of your own cases (--cases, YAML or JSON) that it breaks; it exits 1 on any of those, on a result that could not be judged, or past--max-new-interruptions. Offline and in-process;--jsonfor both.stroq hardenmakes the host refuse to read your credential files, and takes it back.stroq hardenshows what it would change and writes nothing;stroq harden applyasks you (on a terminal; elsewhere it needs--yes) and writes it through the reversible edit engine, with a copy of the file;stroq harden undogives the file back and keeps anything you changed since;stroq harden statussays what is still in place. The first recipe addsRead(...)rules for the usual credential files (.env,~/.ssh,~/.aws,~/.config/gcloud,~/.kube,~/.docker/config.json,~/.npmrc,~/.netrc,~/.pgpass,~/.git-credentials,~/.gnupg) topermissions.denyof Claude Code's settings (--scope user, or--scope projectfor.claude/settings.local.json). Measured on Claude Code 2.1.271: the Read tool and a plaincatthrough Bash are refused. It is a filter inside the host and a best effort, not a guarantee: a program that reads the file itself (python -c) is not covered, and onlystroq run --sandboxis. A second recipe writes a marked block into.cursorignorefor Cursor; it could not be tested here and says so. A settings file with comments is not rewritten: the command prints the lines to add by hand. An agent cannot runharden apply,harden undo, orharden --acknowledge-link.
Changed
.cursorignoreis protected like the other files that switch a protection off. An agent can no longer write it (config.self), becausestroq hardenputs a block in it that keeps.envand keys out of what Cursor sends to a model.
Fixed
- The live-check library reads a stored result only from the file of its kind. Still a library: no command uses it. A stand-in result moved into the live file is no result, a result dated more than a few minutes ahead of the clock is none either (with a clock set back it would have outlived every install), and the environment the driver is handed no longer carries
STROQ_*orCLAUDE_CONFIG_DIR, which the model's shell inherits (a ledger path it could delete, a home that sends the hook to the real settings). The store says that its file is tamper-evident and not tamper-proof. - The task-scope library is closed where a review found it open. Still a library: no command uses it. A task id with no permit file is a deleted contract and refuses every tool (it was "not a task", so an agent that could delete the file turned the task off); an address in a command is a network operation whatever class the command got (
git fetch URL,git clone,npm install URLpassed a no-network scope), and--connect-to,--resolveand the proxy options are refused under a scope that lists hosts;Edit,MultiEditandNotebookEditlist a read as well as a write, so a read deny holds against them; a tool the classifier read no resources of (Task,NotebookRead,LSP) is held as an incomplete call; the approval of an ask names the whole call, content included, and the hint names the first operation still lacking one; the refusal text is redacted in the audit; an MCP name with__in the tool is not granted to the server before it; the key store writes its files whole and refuses a key directory that is a link. - The Safe Install contract refuses more names that are one name on some disk or another. Still a library: no command uses it. Found by a review of 0.24.0. A path component that a program which narrows names would turn into a separator (
U+FF0F,U+FF3C), any~and a digit (an NTFS short name), a look-alike space, and the combining mark U+0345 that NTFS upper-cases are refused; a symlink target may not hold a control, an invisible character or U+FFFD; the parts of an npm or GitHub source have the lengths npm and GitHub allow;buildTreetakes{ complete: true }to refuse an entry with no content, and no longer believes the length a byte array reports of itself;schemaFaultdescribes a record's faults without quoting its text.