Let your agents attack your own code by driving security and fuzzing tools to find holes in your code, application, and prompts. Sabot combines deterministic scanners with agent-driven exploration, hammers your own codebase until something breaks, then reports each finding with a reproducing input or a traced path.
Pre-release. This is an early release and still needs wider testing. It is built to be non-destructive: it runs in a container against a read-only copy of your target, and it does not touch product code without your explicit approval. Expect rough edges in the campaign itself rather than damage to your repo.
Claude Code
claude plugin marketplace add srobroek/sabot
claude plugin install sabot@sabot
Codex
Note: Codex has not been fully tested yet, run at your own risk.
codex plugin marketplace add srobroek/sabot
codex plugin add sabot@sabot
APM (Codex, and other APM runtimes)
apm marketplace add srobroek/sabot --name sabot
apm install sabot@sabot --target claude
apm install sabot@sabot --target codex
Pin a release by appending @<tag> to the marketplace-add, e.g.
srobroek/sabot@sabot--v0.1.0.
- beads (
bd): the task-graph substrate the campaign records its run into. A campaign that cannot open a beads run refuses to start; there is no fallback store. - A container runtime (docker, finch, or colima). Every execution phase (fuzz campaigns, live DAST, build-script runs) executes inside a locked-down, disposable container, never on the host. With no runtime present, the static and reading passes still run and the execution phases are reported as a coverage gap rather than run unconfined.
Point it at a repo, a diff, a hook, or an agent definition. It runs recon first, working out what the code assumes about itself, where its trust boundaries sit, and where it deviates from its own patterns, then attacks those assumptions across seven surfaces:
| Surface | Examples of attack surfaces |
|---|---|
| Code | injection, taint-to-sink, unsafe blocks, overflow, deserialization, ReDoS |
| Shell & hooks | command-position bypass, quoting, fail-open inversion, guard evasion |
| Agents & prompts | prompt injection, tool over-grant, exfil paths, unpinned MCP servers |
| Infra & supply chain | IaC misconfig, CI injection, secrets, malicious dependencies |
| Web & frontend | DOM-sink XSS, CSP gaps, client-side secrets, live DAST against the dev server |
| Build & toolchain | build-time execution, install scripts, the xz-tarball vector |
| Robustness | malformed-input crashes, boundary values, resource limits, silent wrong answers |
Standard scanners and fuzzers (opengrep, bandit, clippy, cargo-fuzz, nuclei, …) are borrowed as they come; recon builds the harness that aims them and writes rules for what no pack covers. Findings carry two axes, an evidence tier (proven / reachable / hardening / refuted) and an impact, and are demoted rather than dropped. It is advisory: it writes harnesses, rules, and regression tests, and patches product code only on explicit approval.
A sabot is a wooden shoe, the French clog worn by peasants and later by factory workers. "Sabotage" comes from the legend of striking workers throwing a wooden shoe into the machines to jam them.
Invoke the skill and name a target:
sabotage: red-team the PreToolUse guards in packages/hooks-bash-safety
sabotage: fuzz the FITS header parser in this crate
sabotage: audit this PR for security and robustness issues
claude: /sabotage find issues in my current branch's changes
codex: $sabotage find security holes in my repo's ci/cd pipeline
⚠️ A full-repo scan is slow and token-hungry. Running sabot against an entire repository can take an hour or more and consume a significant number of tokens. It is a deliberately thorough, multi-agent process. Scope tightly: point it at a single directory, a diff, a PR, or one surface. A whole-repo, all-surface run is the exception, not the default.
It runs a five-agent campaign per surface:
- sabot-scout: recon per surface, producing the trust map, invariants, idiom census, and repo-specific rules
- sabot-fuzzer: writes harnesses, seed corpora, and attack vectors
- gremlin: executes scanners and harnesses inside a container, reads for what they miss
- triager: dedups crashes, minimizes to a smallest reproducing input, classifies
- sabot-challenger: sets the evidence tier on every finding, independently
A sixth agent, hardener, applies an approved fix and re-verifies, only after you say so.
Every handoff is a bead, so a campaign that dies mid-run resumes from the graph rather than restarting. The interview blocks before anything is touched, and the approval blocks before product code is patched.
%%{init: {"flowchart": {"wrappingWidth": 460, "nodeSpacing": 30, "rankSpacing": 34}}}%%
flowchart TB
Q["🛑 <b>Interview</b><br/>target · what you fear · surfaces · tools · budget"]
Q --> OPEN["<b>Open the run</b> · epic + one node per surface<br/>provision images, read the repo's security config, pre-pass"]
OPEN --> SCOUT["<b>sabot-scout</b> · trust map, invariants, repo-specific rules"]
SCOUT --> FUZZER["<b>sabot-fuzzer</b> · harnesses, corpora, vectors"]
FUZZER --> GREMLIN["<b>gremlin</b> · runs it all in a container, reads for the rest"]
GREMLIN -->|crashes| TRIAGER["<b>triager</b> · dedup, minimize,<br/>memory-safety vs robustness"]
GREMLIN -->|findings| CHAL
TRIAGER --> CHAL["<b>sabot-challenger</b> · sets the evidence tier<br/><i>demotes, never deletes</i>"]
CHAL --> REPORT["<b>Report</b> · tier + impact + file:line, citing bead ids"]
SKIP["missing tool · crashed scanner · budget cap"] -.->|"a coverage gap,<br/>never a clean result"| REPORT
REPORT --> HARD["🛑 <b>Approval</b> → <b>hardener</b> patches product code"]
HARD --> REVERIFY["re-run gremlin + triager to verify"]
REVERIFY -.->|regression| HARD
style Q fill:#4a1010,stroke:#ff6b6b,stroke-width:2px,color:#fff
style HARD fill:#4a1010,stroke:#ff6b6b,stroke-width:2px,color:#fff
style SKIP fill:#3a2d0a,stroke:#d4a017,color:#fff
style REPORT fill:#0f3320,stroke:#4caf50,stroke-width:2px,color:#fff
The roles are split so nothing grades its own work. sabot-fuzzer writes harnesses it
never runs. gremlin runs harnesses it cannot edit. sabot-challenger, which found
nothing itself, decides what each finding proves.
- full (default): every step
- quick: recon plus a smoke campaign, no new harnesses or sabot-challenger
- audit-only: describe findings, never patch (regression tests are still written)
- harness-only: author harnesses and corpora
- Attacks only local code you own. Network targets, public endpoints, and live DoS stay out of scope.
- Execution is container-isolated: target mounted read-only, network denied, resource-capped, non-root
- A campaign never generates an input whose effect is irreversible; it fuzzes the code path that receives
rm -rf, it never runs it - Product code is patched only on explicit approval, behind a verification re-run
sabot drives mature, widely-used tools rather than reinventing them. Recon picks which apply to a target and builds the harness that aims them. Note that these tools are installed in the container on-demand depending on the agent's needs, and that these tools are driven by the agent based on its findings.
| Tool | What it does |
|---|---|
| Opengrep | AST/dataflow pattern scanning across languages; the LGPL Semgrep fork with the closed rules restored, run against the baked Semgrep rule packs |
| Joern | code-property-graph interprocedural taint queries |
| ast-grep | structural search and repo-specific rule synthesis |
| weggli | C/C++ semantic vulnerability pattern search |
| Bearer | dataflow SAST for sensitive-data leak paths |
| Ruff · Bandit | Python lint + security lint |
| Clippy · gosec · golangci-lint | Rust and Go lint/security |
| Tool | What it does |
|---|---|
| libFuzzer | coverage-guided native fuzzing, through cargo-fuzz or -fsanitize=fuzzer |
| cargo-fuzz | libFuzzer for Rust |
| atheris | coverage-guided fuzzer for Python |
| Jazzer.js | coverage-guided fuzzer for JS/TS |
| Hypothesis · HypoFuzz | Python property-based + coverage-guided testing |
| proptest · fast-check | property testing for Rust and JS/TS |
| radamsa · zzuf | seed-driven mutation fuzzers |
| schemathesis | API fuzzing from an OpenAPI/GraphQL spec |
| Grammarinator · dharma | grammar-based generation |
| Tool | What it does |
|---|---|
| shrinkray | generic test-case reducer |
| C-Reduce | C/C++ source reduction |
| Tool | What it does |
|---|---|
| osv-scanner · Grype | CVE scanning across lockfiles |
| cargo-audit · cargo-deny · cargo-vet | Rust advisory, licence, and audit policy |
| cargo-semver-checks | Rust breaking-change detection |
| GuardDog | malicious-package detection (typosquats, install-time exfil) |
| OSSF Scorecard | dependency security-posture scoring |
| Tool | What it does |
|---|---|
| gitleaks | secret detection across the tree and git history |
| TruffleHog | 800+ verified secret detectors |
| Kingfisher | secret scanner that live-validates a hit |
| Tool | What it does |
|---|---|
| Trivy · Checkov | IaC misconfig, container, and secret scanning |
| hadolint · kube-linter | Dockerfile and Kubernetes linting |
| zizmor · poutine | CI/CD workflow-injection and supply-chain scanning |
| actionlint | GitHub Actions lint. pinact was dropped: zizmor --offline reports an unpinned action itself |
| tflint | Terraform provider-aware linting |
| Tool | What it does |
|---|---|
| Nuclei | template-driven DAST against a local dev server |
| OWASP ZAP | passive/active web scanning |
| retire.js | known-vulnerable JS library detection |
| eslint-plugin-no-unsanitized | DOM-sink XSS lint |
| Tool | What it does |
|---|---|
| ShellCheck · shfmt | shell lint and format |
| Miri · cargo-careful | Rust undefined-behaviour detection |
| cargo-geiger | Rust unsafe usage census |
| ASan / UBSan / MSan / TSan | compiler sanitizers for native builds |
| Tool | What it does |
|---|---|
| CodeQL | interprocedural taint via a queryable database |
| Path | What |
|---|---|
packages/sabot/.apm/skills/sabotage/ |
the skill router + reference docs |
.../scripts/fuzz-cli.py |
a JSON-stdin/CLI adversarial harness for any hook or guard |
.../scripts/run-contained.sh |
the container wrapper every execution phase runs through |
.../references/containers/ |
per-surface Dockerfiles (rust, python, node), extensible |
packages/sabot/.apm/agents/ |
sabot-scout, sabot-fuzzer, gremlin, triager, sabot-challenger, hardener |
License: Apache-2.0
