Skip to content

v0.6.0 - Copilot CLI adapter

Choose a tag to compare

@AGGIB AGGIB released this 06 Sep 11:05
· 79 commits to main since this release

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.