A CRS (Cyber Reasoning System) that uses opencode to autonomously find and patch vulnerabilities in open-source projects using open-source LLMs.
Given any boot-time subset of vulnerability evidence (POVs, bug-candidate reports, diff files, and/or seeds), the agent analyzes the inputs, edits source code, builds, tests, iterates, and writes one final patch for submission.
┌─────────────────────────────────────────────────────────────────────┐
│ patcher.py (orchestrator) │
│ │
│ 1. Fetch startup inputs & source │
│ crs.fetch(POV/BUG_CANDIDATE/DIFF/SEED) │
│ crs.download(src) │
│ │ │
│ ▼ │
│ 2. Launch opencode with fetched paths + AGENTS.md │
│ opencode run --dangerously-skip-permissions │
│ --model <provider>/<model> ... │
└─────────┬───────────────────────────────────────────────────────────┘
│ prompt with startup evidence paths
▼
┌─────────────────────────────────────────────────────────────────────┐
│ opencode (autonomous agent, open-source models via LiteLLM) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Analyze │───▶│ Fix │───▶│ Verify │ │
│ │ │ │ │ │ │ │
│ │ Read │ │ Edit src │ │ apply-patch │──▶ Builder │
│ │ startup │ │ git diff │ │ -build │ sidecar │
│ │ evidence │ │ │ │ │◀── rebuild_id │
│ └──────────┘ └──────────┘ │ run-pov ────│──▶ Runner │
│ │ (all POVs)│◀── retcode │
│ ▲ │ apply-patch │──▶ Builder │
│ │ │ -test │◀── retcode │
│ │ └──────┬───────┘ │
│ │ │ │
│ └── retry ◀── fail? │
│ │ pass │
│ ▼ │
│ Write .diff to /patches/ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────┐
│ patcher.py │
│ submit(first patch) ───▶ oss-crs framework
└─────────────────────────┘
run_patcherfetches available startup inputs (POV,BUG_CANDIDATE,DIFF,SEED) once, downloads source, and passes the fetched paths to the agent.- The evidence is handed to opencode in a single session with generated
AGENTS.mdinstructions. No additional inputs are fetched after startup. - The agent autonomously analyzes evidence, edits source, and uses libCRS tools (
apply-patch-build,run-pov,apply-patch-test) to iterate as needed through the builder sidecar. - When the first final
.diffis written to/patches/, the patcher submits that single file withcrs.submit(DataType.PATCH, patch_path)and exits. Later patch files or modifications are ignored.
The agent is language-agnostic — it edits source and generates diffs while the builder sidecar handles compilation. The sanitizer type (address only in this CRS) is passed to the agent for context.
opencode natively targets <provider>/<model>. Because OSS-CRS exposes a LiteLLM proxy, this CRS declares LiteLLM as a custom opencode provider (@ai-sdk/openai-compatible) and points baseURL at OSS_CRS_LLM_API_URL. Any model listed in your LiteLLM config (Qwen, DeepSeek, Llama, gpt-oss, ...) becomes addressable as oss-crs/<model-name> inside opencode.
patcher.py # Patcher module: one-time fetch of optional inputs → agent → first-patch submit
pyproject.toml # Package config (run_patcher entry point)
bin/
compile_target # Builder phase: compiles the target project
agents/
opencode.py # opencode agent (default)
opencode.md # AGENTS.md template with libCRS tool docs
sections/ # Dynamic AGENTS.md section partial templates
template.py # Stub for creating new agents
oss-crs/
crs.yaml # CRS metadata (supported languages, models, etc.)
example-compose.yaml # Example crs-compose configuration
base.Dockerfile # Base image: Ubuntu + Node.js + opencode CLI + Python
builder.Dockerfile # Build phase image
patcher.Dockerfile # Run phase image
docker-bake.hcl # Docker Bake config for the base image
sample-litellm-config.yaml # LiteLLM proxy config template (open-weight models)
- oss-crs — the CRS framework (
crs-composeCLI)
Builder and runner sidecars are injected automatically by the framework — no separate builder setup is needed.
Copy oss-crs/example-compose.yaml and update the paths:
crs-opencode:
source:
local_path: /path/to/crs-opencode
cpuset: "2-7"
memory: "16G"
llm_budget: 10
additional_env:
CRS_AGENT: opencode
OPENCODE_MODEL: qwen3-coder-480b
llm_config:
litellm:
mode: internal
internal:
config_path: ./example/crs-opencode/litellm-config.yamloss-crs/sample-litellm-config.yaml is a starting template that routes common open-weight models (Qwen 3 Coder, DeepSeek V3.1, Llama 3.3, gpt-oss-120b) through an OpenAI-compatible backend such as vLLM, TGI, or a hosted open-source gateway. Point LiteLLM at your own endpoint by exporting:
export OSS_CRS_OPEN_WEIGHT_API_BASE=https://my-vllm-host/v1
export OSS_CRS_OPEN_WEIGHT_API_KEY=sk-...crs-compose up -f crs-compose.yaml| Environment variable | Default | Description |
|---|---|---|
CRS_AGENT |
opencode |
Agent module name (maps to agents/<name>.py) |
OPENCODE_MODEL |
qwen3-coder-480b |
Bare model id (will be scoped under the provider) or full provider/model |
OPENCODE_PROVIDER_ID |
oss-crs |
opencode provider id bound to the LiteLLM proxy |
OPENCODE_MODELS |
(empty) | Comma/whitespace list of additional model ids to register in opencode config |
OPENCODE_AGENT_NAME |
(empty) | Optional opencode --agent name |
OPENCODE_VERSION |
1.4.11 |
Prepare-phase version pin for the opencode CLI |
AGENT_TIMEOUT |
0 (no limit) |
Agent timeout in seconds (0 = run until budget exhausted) |
- Execution:
opencode run --dangerously-skip-permissions --model <provider>/<model> --dir <src>(non-interactive, auto-approves any permission prompt). - Instruction file:
AGENTS.mdgenerated per run in the target repo. - LiteLLM proxy: Framework provides
OSS_CRS_LLM_API_URL+OSS_CRS_LLM_API_KEY; the agent injects them into~/.config/opencode/opencode.jsonas a custom@ai-sdk/openai-compatibleprovider so any LiteLLM-listed model is reachable as<provider>/<model>.
Debug artifacts:
- Log directories:
~/.local/share/opencode,~/.local/state/opencode,~/.config/opencode(registered viaregister-log-dir). - Per-run logs:
/work/agent/opencode_stdout.log,/work/agent/opencode_stderr.log.
The agent is instructed to satisfy these criteria before writing a patch:
- Builds — compiles successfully
- POVs don't crash — all provided POV variants pass (if POVs were provided)
- Tests pass — project test suite passes (or skipped if none exists)
- Semantically correct — fixes the root cause with a minimal patch
Runtime remains trust-based: the patcher does not re-run final verification. Once the first .diff is written to /patches/, the patcher submits that single file and exits. Submitted patches cannot be edited or resubmitted, so the agent should only write to /patches/ when it considers the patch final.
- Copy
agents/template.pytoagents/my_agent.py. - Implement
setup()andrun(). - Set
CRS_AGENT=my_agent.
The agent receives:
- setup(source_dir, config) config keys:
llm_api_url— optional LiteLLM base URLllm_api_key— optional LiteLLM keyopencode_config_dir/opencode_data_dir/opencode_state_dir— XDG paths opencode writes into
- source_dir — clean git repo of the target project
- pov_dir — boot-time POV input directory (may be empty)
- bug_candidate_dir — boot-time bug-candidate directory (may be empty)
- diff_dir — boot-time diff directory (may be empty)
- seed_dir — boot-time seed directory (may be empty)
- harness — harness name for
run-pov - patches_dir — write exactly one final
.diffhere - work_dir — scratch space
- language — target language (c, c++, jvm)
- sanitizer — sanitizer type (
addressonly) All optional inputs are boot-time only. The patcher fetches them once and passes directory paths to the agent; no new POVs, bug-candidates, diff files, or seeds appear during the run.
The agent has access to three libCRS commands (builder/runner sidecars are resolved automatically via BUILDER_MODULE env var set by the framework):
libCRS apply-patch-build <patch.diff> <response_dir>— build a patchlibCRS run-pov <pov> <response_dir> --harness <h> [--rebuild-id <id>]— test against a POV (omit--rebuild-idfor base build)libCRS apply-patch-test <patch.diff> <response_dir>— run the project's test suite
For transparent diagnostics, always inspect response_dir logs:
- Build:
stdout.log,stderr.log,retcode - POV:
stdout.log,stderr.log,retcode - Test:
stdout.log,stderr.log,retcode