A CRS (Cyber Reasoning System) that uses Gemini CLI to autonomously find and patch vulnerabilities in open-source projects.
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 Gemini CLI agent with fetched paths + GEMINI.md │
│ gemini -m <model> --approval-mode yolo -d -p <prompt> │
└─────────┬───────────────────────────────────────────────────────────┘
│ -d: prompt with startup evidence paths
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Gemini CLI (autonomous agent) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ 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 Gemini CLI in a single session with generated
GEMINI.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.
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/
gemini_cli.py # Gemini CLI agent (default)
gemini_cli.md # GEMINI.md template with libCRS tool docs
sections/ # Dynamic GEMINI.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 + Gemini 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
- 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-gemini-cli:
source:
local_path: /path/to/crs-gemini-cli
cpuset: "2-7"
memory: "16G"
llm_budget: 10
additional_env:
CRS_AGENT: gemini_cli
GEMINI_MODEL: gemini-3-pro-preview
llm_config:
# Optional: uncomment if you want OSS-CRS to inject an external LiteLLM endpoint.
# litellm:
# mode: external
# external:
# url_env: EXTERNAL_LITELLM_API_BASE
# key_env: EXTERNAL_LITELLM_API_KEYIf you want OSS-CRS to inject an external LiteLLM endpoint, uncomment the llm_config block and make sure EXTERNAL_LITELLM_API_BASE and EXTERNAL_LITELLM_API_KEY are set. oss-crs/sample-litellm-config.yaml remains available as a reference template for LiteLLM-backed setups.
crs-compose up -f crs-compose.yaml| Environment variable | Default | Description |
|---|---|---|
CRS_AGENT |
gemini_cli |
Agent module name (maps to agents/<name>.py) |
GEMINI_MODEL |
gemini-3-pro-preview |
Model passed to gemini -m (strips gemini/ prefix if present) |
AGENT_TIMEOUT |
0 (no limit) |
Agent timeout in seconds (0 = run until budget exhausted) |
Available models:
gemini-3-pro-previewgemini-3-flash-preview
- Execution:
gemini --approval-mode yolo(auto-approve all tool use) - Instruction file:
GEMINI.mdgenerated per run in the target repo - LiteLLM proxy: Configured via
GOOGLE_GEMINI_BASE_URL+GEMINI_API_KEYenv vars
Debug artifacts:
- Log directory:
/root/.gemini(registered viaregister-log-dir) - Per-run logs:
/work/agent/gemini_stdout.log,/work/agent/gemini_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 keygemini_home— path for Gemini CLI state/logs
- 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