Vulnerability scanning with your scanners, your threat feeds, and a coding agent that opens the fix PR.
patchbot inventories the packages in your repository, matches them against any threat feed, accepts findings from any scanner, and fixes what it finds. When a version bump is enough, it bumps. When the bump breaks the build, it hands the failure to a coding agent. It re-scans before opening each pull request.
Run it as a CLI, as a GitHub Action, or as a scheduled Claude Managed Agents deployment with no CI involved.
Why patchbot
| Dependabot / Renovate | Trivy / Grype alone | patchbot | |
|---|---|---|---|
| Bring your own threat feed (private advisories, OSV format) | no | no | yes |
| Bring your own scanner (any tool that emits SARIF / Trivy / Grype JSON) | no | n/a | yes |
| Version bump + lockfile regeneration with no model call | yes | no | yes |
| Fixes that need code changes (breaking major bump, failing tests) | no | no | yes, agent tier |
| Re-scan before every PR | no | no | yes |
| Agent runs off the CI runner; secrets stay out of the sandbox | no | no | yes, Managed Agents |
| Works without CI (scheduled, any git host) | no | no | yes |
Installation
pip install patchbot # scan + bump-only fixes
pip install "patchbot[api]" # + Anthropic-backed agent tiers (api, managed)
Requires Python 3.11 or newer. No other runtime dependencies.
Quick start
patchbot scan # inventory -> OSV.dev -> table report
patchbot fix --dry-run # preview the fix plan, change nothing
patchbot fix --pr # bump or fix, verify, open one PR per package
How it works
flowchart LR
inv["Inventory<br/>lockfiles · CycloneDX SBOM"]
feeds["Feeds<br/>OSV.dev · your OSV file / URL"]
scanners["Scanners<br/>trivy · grype · osv-scanner · any cmd"]
findings["Findings<br/>(deduplicated)"]
report["Report<br/>table · json · SARIF"]
fix["Fix<br/>bump → verify → agent → verify → PR"]
inv --> feeds --> findings
inv --> scanners --> findings
findings --> report
findings --> fix
Inventory. Parsers for npm, pnpm, and yarn lockfiles, requirements*.txt, pyproject.toml, go.mod, and Cargo.toml. For other ecosystems, point patchbot at a CycloneDX SBOM from syft, cdxgen, or your own tooling.
Feeds. OSV.dev is on by default. To add a private feed, serve OSV-schema JSON from a file or URL. You keep one format.
Scanners. Wrappers for trivy, grype, and osv-scanner. The command scanner runs any tool that writes SARIF, Trivy, Grype, or osv-scanner JSON to stdout.
Fix. One branch per vulnerable package, in four steps:
- Bump. patchbot rewrites the pin to the lowest version that clears every advisory and regenerates the lockfile with the ecosystem's own tool. No model call.
- Verify. patchbot re-scans, runs
test_cmd, and rejects any diff that reaches outside the dependency surface (CI config, dotfiles). - Escalate. If step 2 fails, patchbot briefs the agent with the failure itself: "bumped
lodashto 4.18.0; tests fail with ...". The agent fixes the code. - Verify again. Then commit, push, and open the PR with the advisory table in the body.
--agent none skips step 3 and gives you bump-only mode.
Commands
| Command | What it does |
|---|---|
patchbot scan [paths...] |
Run the pipeline and print a report. Exits 1 when any finding meets --fail-on (default high). |
patchbot fix [paths...] |
Run the fix loop. --dry-run previews; --pr pushes and opens pull requests through gh. |
patchbot plugins |
List each registered feed, scanner, and agent, built in and third party. |
patchbot managed ... |
Set up (init) and schedule (deploy, list, pause, unpause) the Managed Agents backend. |
patchbot scan
patchbot scan --format json
patchbot plugins
Configuration
Put patchbot.toml in the repository root. CLI flags override the file.
[inventory]
paths = ["."]
# sbom = "sbom.cdx.json"
[feeds.osv]
enabled = true
[feeds.internal] # your own threat feed
type = "url" # or "file" with path = "..."
url = "https://intel.example.com/advisories.json"
[scanners.trivy]
enabled = true
[scanners.custom] # your own scanner
type = "command"
cmd = "./scan.sh"
format = "sarif" # sarif | trivy | grype | osv-scanner
[report]
fail_on = "high" # critical | high | medium | low | none
ignore = ["GHSA-xxxx-xxxx-xxxx"]
[fix]
agent = "claude" # claude | codex | api | command | managed | none
model = "claude-opus-5" # optional; forwarded to the agent
max_prs = 5
test_cmd = "npm test"
# cmd = "aider --yes --message {prompt}" # agent = "command" only
[fix.managed] # agent = "managed" only; printed by `patchbot managed init`
agent_id = "agent_..."
environment_id = "env_..."
vault_id = "vlt_..."
Choosing an agent backend
| Backend | Runs where | Needs | Use it for |
|---|---|---|---|
claude / codex |
Your machine or runner, through the CLI on PATH |
The CLI, logged in | Local development |
api |
Your runner, in process | pip install patchbot[api], ANTHROPIC_API_KEY |
CI without Node |
command |
Wherever your tool runs | Any agent that accepts a prompt (aider, opencode, ...) |
Non-Anthropic models |
managed |
Anthropic's sandbox, off your runner | patchbot managed init once, three IDs as secrets |
CI where the agent must not see repo secrets |
Managed Agents, the recommended CI backend
An agent with a shell on your CI runner sits next to your repository secrets. The managed backend moves the agent into a Claude Managed Agents sandbox. Anthropic's git proxy authenticates git push; a vaulted GitHub MCP credential creates the pull request. Your GitHub token stays outside the sandbox. patchbot then re-scans the pushed branch on your side before it treats the fix as done.
pip install "patchbot[api]"
patchbot managed init --github-mcp-token "$GITHUB_MCP_TOKEN"
# prints agent_id, environment_id, vault_id; store them as repo secrets
patchbot fix --agent managed --pr
GitHub Actions
Scan and publish to the Security tab
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- uses: PrismorSec/patchbot@v0
with:
fail-on: high
Auto-fix with Managed Agents (no Node, no agent on the runner)
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: PrismorSec/patchbot@v0
with:
fix: "true"
agent: managed
managed-agent-id: ${{ secrets.PATCHBOT_AGENT_ID }}
managed-environment-id: ${{ secrets.PATCHBOT_ENVIRONMENT_ID }}
managed-vault-id: ${{ secrets.PATCHBOT_VAULT_ID }}
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Auto-fix with the in-runner api backend
permissions:
contents: write
pull-requests: write
security-events: write
steps:
- uses: actions/checkout@v4
- uses: PrismorSec/patchbot@v0
with:
fix: "true"
agent: api
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
Weekly, against a private feed
on:
schedule:
- cron: "0 6 * * 1"
steps:
- uses: actions/checkout@v4
- uses: PrismorSec/patchbot@v0
with:
config: patchbot.toml # [feeds.internal] url = ...
action.yml documents each input. .github/workflows/patchbot-fix-example.yml is a complete workflow you can copy.
A pull request opened by patchbot fix --pr:
Scheduled deployments (no CI)
patchbot managed deploy creates a cron-scheduled Managed Agents session that clones your repositories, scans, fixes, and opens pull requests. Nothing in it depends on GitHub Actions, so it works with GitLab and Bitbucket too.
patchbot managed deploy \
--repos owner/api,owner/web \
--cron "0 6 * * *" --tz UTC \
--agent-id agent_... --environment-id env_... --vault-id vlt_... \
--run-now # fire one session now to test
Manage it with patchbot managed list | pause | unpause. Anthropic jitters scheduled runs by up to nine minutes, and 1 to 3 AM local wall-clock times can skip or double-fire across DST changes. Schedule outside that window, or use UTC.
Extending patchbot
Register a scanner, feed, or agent from your own package through entry points. No fork needed.
# your_package/pyproject.toml
[project.entry-points."patchbot.scanners"]
mytool = "your_package.scanner:run" # run(cwd: str, config: dict) -> list[Finding]
[project.entry-points."patchbot.feeds"]
myfeed = "your_package.feed" # match(packages, config) -> list[Finding]
[project.entry-points."patchbot.agents"]
myagent = "your_package.agent" # run(prompt, cwd, model=None, timeout=600, config=None) -> int
If you would rather not write Python, [scanners.*] type = "command" and [feeds.*] type = "url" | "file" cover most cases. cmd values run through your shell, so point them at tools you trust.
FAQ
Why did a fix report failed? patchbot prints the reason: the advisory still reproduces after the bump or the agent run, test_cmd failed (with the log tail), or the change touched files outside the dependency surface. patchbot discards the branch and commits nothing.
What sets the exit code? scan exits 1 when any finding meets --fail-on or worse; none exits 0. fix exits 1 if it could not fix any package.
How do I suppress one advisory? [report] ignore = ["GHSA-..."].
Do I need Node in CI? Only for --agent claude or codex. The api and managed backends need Python alone.
Which ecosystems include transitive dependencies? npm, pnpm, and yarn, through their lockfiles. For Python, Go, and Rust, set [inventory] sbom to a CycloneDX file to scan the full resolved tree.
Development
git clone https://github.com/PrismorSec/patchbot && cd patchbot
python -m venv .venv && .venv/bin/pip install -e ".[dev,yaml,api]"
.venv/bin/pytest
docs/screenshots.sh # regenerate docs/img (needs charmbracelet/freeze)
examples/demo-npm ships with a vulnerable lodash on purpose. The repository's own CI scans it, so the Security tab always shows live findings.
Comments