The taint-guided security reviewer for AI-generated code. MIT-licensed, self-hostable, and the only one that publishes an honest, reproducible false-positive rate.
Dataflow taint analysis narrows the candidates; an LLM proves exploitability on the few that survive. Odin runs on your own infrastructure with your own model — OpenRouter, OpenAI, Anthropic, or a local LLM via LM Studio / Ollama — and every benchmark number ships with the command to reproduce it.
Why this exists: 2026 studies put ~45% of AI-generated code at introducing a known security flaw — roughly 2.7× the vulnerability density of human-written code — while security-review coverage runs 20–30% lower for AI-authored changes. Odin is the review layer built for that gap: it assumes the code was written fast and checks it like it wasn't.
Odin implements the LLift (OOPSLA 2024) / INFERROI (ICSE 2025) architecture: cheap taint propagation narrows the search space, then an LLM reasons about exploitability only on real candidates. A feedback loop suppresses known false-positive (source, sink) pairs before the LLM runs — so cost and noise drop together over time.
FP rate on 193 clean-code samples — the metric nobody else publishes: dataflow taint tracker 0.0%, deterministic rules 8.8%, vs Semgrep's 2.1%. On real security bugs (SecVulEval), odin-rules catches 64% to Semgrep's 29%, both at 100% precision. Every recall number is computed under line-localized matching — a finding must land on the vulnerable line to count — with ground-truth markers stripped so no rule can score by grepping for them. Full methodology, and where Odin loses: leaderboard.md.
# No Docker, no server, no checkout — just works (BYOK via env var)
uvx odin-review review path/to/file.py
# Rules only — instant, no LLM
uvx odin-review review path/to/file.py --rules-onlySet your provider once:
export ODIN_LLM_PROVIDER=openrouter
export ODIN_OPENROUTER_API_KEY=sk-or-v1-...
export ODIN_OPENROUTER_MODEL=anthropic/claude-sonnet-4-5Install the GitHub App and Odin automatically reviews every PR in your repos — no webhook setup, no token management.
https://your-odin-instance/api/github/app/install
Or self-host and register your own App:
# .env
ODIN_GITHUB_APP_ID=123456
ODIN_GITHUB_APP_PRIVATE_KEY_PATH=/run/secrets/odin-app.pem
ODIN_GITHUB_APP_WEBHOOK_SECRET=your-secretOdin posts structured reviews with inline comments, severity badges, and fix suggestions on every PR automatically.
| Feature | Details |
|---|---|
| Dataflow triage | Intra-procedural taint analysis → LLM reasons about exploitability on narrowed candidates only (LLift/INFERROI architecture) |
| 51 deterministic rules | Python, JS, TS, Go, Rust, Java — 18 are CWE-tagged security rules; zero cost, instant |
| Learning feedback loop | Mark a finding false-positive twice → that (source, sink) pair is suppressed before the LLM runs next time |
| Honest leaderboard | Public FP-rate benchmark on 193 clean samples + CVE recall; every number reproducible, line-localized, contamination-controlled |
| uvx one-binary install | uvx odin-review review <file> — works from a clean machine, BYOK |
| GitHub App | One-click install, auto-registers webhook, reviews every PR |
| GitHub webhook | Manual webhook setup if you prefer |
| MCP server | Use Odin as a tool inside Claude Code or Cursor |
| Local LLMs | LM Studio, Ollama, or any OpenAI-compatible endpoint |
| BYOK | OpenRouter, OpenAI, Anthropic |
| 6 languages | Python, JavaScript, TypeScript, Go, Rust, Java |
Client / GitHub PR ──▶ FastAPI + LangGraph
│
tree-sitter AST parse
│
LangGraph fan-out (parallel)
┌────────────────────┼──────────────────┬──────────────────┐
▼ ▼ ▼ ▼
SecurityAgent QualityAgent DocsAgent DataflowTriage
(LLM call) (LLM call) (LLM call) taint→LLM triage
│ │ │ │
└────────────────────┴──────────────────┴──────────────────┘
│
Rules Engine
(27+ instant checks)
│
synthesize()
(dedup + score + sort)
│
GitHub PR review / Web UI / CLI / SSE stream
Full engineering deep-dive — pipeline, research lineage, the CRAG retriage loop, and the learning loop: docs/architecture.md.
DataflowTriage pipeline:
- Walk each function body with an intra-procedural taint tracker (assignment-chain + call-arg propagation)
- Check the (source_sig, sink_sig) suppression table — skip known-FP pairs before the LLM runs
- LLM reasons about exploitability for remaining candidates only (≤20 per file,
asyncio.Semaphore(4)) - Confirmed false positives feed back into the suppression table — noise drops over time
FP rate is a first-class metric. We report where Odin loses.
| Tool | FP Rate | Notes |
|---|---|---|
odin-dataflow |
0.0% | 0/193 — dataflow taint tracker refuses to fire without a source→sink path |
semgrep |
2.1% | 4/193 — the open-source reference point |
odin-rules |
8.8% | 17/193 — pattern rules trade precision for coverage |
| Tool | Recall | Precision | F1 |
|---|---|---|---|
odin-rules |
64% | 100% | 0.78 |
semgrep |
29% | 100% | 0.44 |
odin-dataflow |
7% | 100% | 0.13 |
| Tool | CVE-Bench crits (50 · SOTA ~13%) | SWE-bench Verified (50 · logic bugs) |
|---|---|---|
odin-rules |
8% | 4% |
semgrep |
8% | 0% |
odin-dataflow |
0% | 2% |
On honesty: an earlier version of this leaderboard reported 86% SecVulEval, 32% CVE-Bench, and 100% SWE-bench for
odin-rules. Those came from a scorer that counted any finding anywhere in a sample as a hit — and worse, the benchmark planted a# BUG:comment on each buggy line, which a comment-linting rule "detected." We rewrote the scorer to require the finding to land on the vulnerable line, and to strip the planted markers before the tool sees the code. The numbers above are what survived. CVE-Bench is genuinely brutal (Odin ties Semgrep below SOTA); SWE-bench measures logic bugs, which deterministic SAST isn't built to catch. The full story is inleaderboard.md.
Every number is reproducible — dataset SHAs pinned, seed fixed at 42:
cd backend
python -m bench.harness --seed 42 # full head-to-head
python -m bench.harness --dataset clean_corpus --tool semgrep --seed 42
python -m bench.harness --dataset cvebench --tool odin-rules --seed 42
python -m bench.harness --json # machine-readableFull methodology + every number: bench/reports/leaderboard.md
CodeRabbit, Greptile, Qodo, CodeQL, and Copilot runners are wired into the same harness but require API keys / hosted access — once enabled they drop into the tables above.
We run Odin against its own backend. The rules-only pass currently reports 102 findings (6 critical, 48 high) — and the honest read is the point: all 6 "critical" findings are false positives. They fire on Odin's own machinery — the rule that detects eval() necessarily contains the string eval; the dataflow sink registry lists pickle and yaml.load as dangerous sinks. None has a user-controlled source→sink path, which is exactly why the dataflow tracker flags zero of them.
That's the whole thesis in one screenshot: pattern rules are noisy (that's the 8.8% FP rate), and taint analysis is what buys back precision. We'd rather show you the false positives on our own code than hide them.
uvx odin-review review backend/app --rules-only # reproduce itgit clone https://github.com/RahulModugula/odin
cd odin
cp .env.example .env
# .env:
# ODIN_LLM_PROVIDER=lmstudio
# ODIN_LMSTUDIO_MODEL=qwen2.5-coder-32b
docker compose -f docker-compose.yml -f docker-compose.lmstudio.yml up# .env:
# ODIN_LLM_PROVIDER=openrouter
# ODIN_OPENROUTER_API_KEY=sk-or-v1-...
# ODIN_OPENROUTER_MODEL=anthropic/claude-sonnet-4-5
docker compose up# ODIN_LLM_PROVIDER=openai
# ODIN_LLM_API_KEY=sk-...
# ODIN_LLM_MODEL=gpt-4o-mini
docker compose up# Install once (no checkout required)
uvx odin-review review path/to/file.py
# Rules only — instant, no LLM, no server
uvx odin-review review path/to/file.py --rules-only
# Staged changes (pre-push check)
uvx odin-review review --staged --rules-only
# Fail CI on high+ severity
uvx odin-review review --staged --fail-on high
# JSON output for scripting
uvx odin-review review path/to/file.py --json | jq .
# Filter by severity and confidence
uvx odin-review review backend/ --min-severity high --min-confidence 0.8
# Noise budget — keep only the top N findings (severity DESC, confidence DESC)
uvx odin-review review backend/ --max-findings 10
# AI Code Validator mode — sharpens the review for Copilot/ChatGPT-authored code
uvx odin-review review generated_file.py --local --ai-generatedInstall as a git pre-push hook:
bash cli/install-hook.shFlags: --staged · --diff REF · --rules-only · --local · --quiet · --min-severity · --min-confidence · --fail-on · --fail-on-score · --max-findings · --ai-generated · --json · --sarif
- VS Code extension:
vscode-extension/— on-save rules, one-click full AI review, hover squiggles. - Live demo instance: deploy the public, rate-limited demo with the config in
infra/demo/(ODIN_DEMO_ENABLED=trueexposesPOST /api/demo/review). - Policy-as-code: drop YAML files in
.odin/rules/to add custom pattern-based rules without writing Python — seeapp/rules/custom_loader.pyfor the schema.
openssl rand -hex 32 # generate webhook secretIn your GitHub repo: Settings → Webhooks → Add webhook
- Payload URL:
https://your-odin/api/webhook/github - Content type:
application/json - Events: Pull requests, Issue comments
# .env
ODIN_GITHUB_TOKEN=ghp_...
ODIN_GITHUB_WEBHOOK_SECRET=your-secretBot commands in PRs: @odin review · @odin help
51 rules across 6 languages plus cross-language and performance checks. Zero cost, instant, no LLM. 18 are CWE-tagged security rules — listed first, since that's what Odin leads with.
| ID | Name | Severity | Language | CWE |
|---|---|---|---|---|
| PY003 | Use of eval() / exec() |
CRITICAL | Python | CWE-95 |
| PY004 | Hardcoded secret / credential | CRITICAL | Python | CWE-798 |
| PY005 | SQL injection via string formatting | CRITICAL | Python | CWE-89 |
| PY010 | Unsafe deserialization (pickle / yaml.load) |
CRITICAL | Python | CWE-502 |
| PY011 | OS command injection (shell=True / os.system) |
CRITICAL | Python | CWE-78 |
| PY012 | Insecure PRNG for a security token | MEDIUM | Python | CWE-330 |
| PY013 | XML External Entity (XXE) injection | HIGH | Python | CWE-611 |
| PY014 | Server-Side Request Forgery (SSRF) | HIGH | Python | CWE-918 |
| JS003 | XSS via innerHTML / dangerouslySetInnerHTML |
HIGH | JS/TS | CWE-79 |
| JS005 | Insecure JWT handling (decode without verify) | HIGH | JS/TS | CWE-347 |
| JS006 | Prototype pollution | HIGH | JS/TS | CWE-1321 |
| GO004 | SQL injection via fmt.Sprintf |
CRITICAL | Go | CWE-89 |
| RS002 | unsafe block usage |
HIGH | Rust | CWE-676 |
| JA005 | SQL injection via string concatenation | CRITICAL | Java | CWE-89 |
| CL004 | Hardcoded credential | CRITICAL | All | CWE-798 |
| PERF003 | Weak cryptographic hash (MD5 / SHA-1) | HIGH | All | CWE-327 |
| PERF004 | Potential path traversal | HIGH | All | CWE-22 |
| PERF005 | Sensitive data in a log statement | HIGH | All | CWE-532 |
| ID | Name | Severity | Language |
|---|---|---|---|
| PY001 | Bare except clause | HIGH | Python |
| PY002 | Mutable default argument | HIGH | Python |
| PY006 | High cyclomatic complexity | MEDIUM | Python |
| PY007 | Overly long function | MEDIUM | Python |
| PY008 | Excessive nesting depth | MEDIUM | Python |
| PY009 | Missing type hints on public function | LOW | Python |
| JS001 | Use of var instead of let/const |
LOW | JS/TS |
| JS002 | console.log left in code |
LOW | JS/TS |
| JS004 | Deep callback nesting | MEDIUM | JS/TS |
| JS007 | async function without await |
LOW | JS/TS |
| TS001 | TypeScript any type |
MEDIUM | TypeScript |
| TS002 | Non-null assertion overuse | MEDIUM | TypeScript |
| GO001 | Error return value ignored | HIGH | Go |
| GO002 | panic() in non-main code |
HIGH | Go |
| GO003 | Potential goroutine leak | MEDIUM | Go |
| GO005 | Mutex Lock without deferred Unlock | MEDIUM | Go |
| GO006 | context.Context not first parameter |
LOW | Go |
| GO007 | Hardcoded IP address | LOW | Go |
| GO008 | Unbuffered channel send may deadlock | MEDIUM | Go |
| RS001 | unwrap() — potential panic |
MEDIUM | Rust |
| RS003 | todo!() / unimplemented!() in production |
HIGH | Rust |
| RS004 | Excessive .clone() |
LOW | Rust |
| RS005 | .expect() without a descriptive message |
LOW | Rust |
| JA001 | System.out.println in production |
LOW | Java |
| JA002 | Raw generic type usage | MEDIUM | Java |
| JA003 | Resource leak — missing try-with-resources | HIGH | Java |
| JA004 | Overly broad exception caught | MEDIUM | Java |
| JA006 | Potential NullPointerException | HIGH | Java |
| CL001 | TODO/FIXME comment | INFO | All |
| CL002 | File too large | MEDIUM | All |
| CL003 | Magic number | LOW | All |
| PERF001 | Regex compiled inside a loop | MEDIUM | All |
| PERF002 | String concatenation in a loop | MEDIUM | All |
provider:
name: openrouter
model: anthropic/claude-sonnet-4-5
review:
agents: [security, quality, docs]
severity_threshold: low
ignore:
paths: [vendor/, node_modules/, "*.min.js"]
rules: [CL001]
quality_gate:
min_score: 70
max_critical: 0
block_on_fail: falseUse Odin as a tool inside Claude Code, Cursor, or any MCP client — it reviews the diff before you commit it.
Claude Code (one line):
claude mcp add --scope user odin -- uvx --from odin-review odin-mcpCursor / Windsurf — add to ~/.cursor/mcp.json:
{
"mcpServers": {
"odin": { "command": "uvx", "args": ["--from", "odin-review", "odin-mcp"] }
}
}Available tools: review_diff (the primary PR-review path), review_code, analyze_file, get_findings, query_codebase.
cd backend
uv venv && uv pip install -e ".[dev]"
uvicorn app.main:app --reload
# Tests
pytest tests/ -v --cov=app
# Lint
ruff check . && ruff format --check . && mypy --strict app/
# Benchmark
python -m bench.harness --dataset clean_corpusThe honest comparison — including where CodeRabbit wins.
| Odin | CodeRabbit Free | CodeRabbit Pro | |
|---|---|---|---|
| Price | Free | Free (limited) | $24/dev/mo |
| Open source | ✅ MIT | ❌ | ❌ |
| Self-hostable | ✅ | ❌ | ❌ |
| Data privacy | ✅ never leaves your infra | ❌ | ❌ |
| Local LLMs (LM Studio, Ollama) | ✅ | ❌ | ❌ |
| BYOK (OpenRouter, OpenAI) | ✅ | ❌ | ✅ |
| Published FP rate | ✅ 0.0% dataflow / 8.8% rules on 193 clean samples | ❌ | ❌ |
| Reproducible benchmarks | ✅ python -m bench.harness |
❌ | ❌ |
| Taint-guided triage (LLift/INFERROI) | ✅ | ❌ | ❌ |
| Learning feedback loop | ✅ suppresses FPs at generator level | ❌ | limited |
| GitHub App one-click install | ✅ | ✅ | ✅ |
| GitHub webhook | ✅ | ✅ | ✅ |
CLI (uvx odin-review review) |
✅ no Docker needed | ❌ | ❌ |
| PR summary & walkthrough | ✅ | ✅ | ✅ |
| Inline comments | ✅ | ✅ | ✅ |
| Deterministic rules | ✅ 51 rules (18 security), 6 languages | ✅ 40+ | ✅ 40+ |
| MCP server | ✅ Claude Code / Cursor | ❌ | ❌ |
| GitLab / Bitbucket | ❌ (GitHub only) | ✅ | ✅ |
Where CodeRabbit wins: more platform integrations (GitLab, Bitbucket, Azure DevOps), more rules out of the box, more mature bot UX, and a larger team maintaining it. If you're on GitLab or want something fully managed, CodeRabbit is a better fit today.
Where Odin wins: if your code can't leave your infrastructure, if you want to understand and audit what's running, if you want FP rates that actually drop over time, or if you want to run it free with your own model.
Odin isn't the only self-hostable reviewer anymore — Kodus and PR-Agent / Qodo Merge are both credible OSS choices, and they're good. Two things set Odin apart:
- License. Odin is MIT; Kodus is AGPLv3. If you want to embed, fork, or ship a reviewer inside a commercial product without copyleft obligations, MIT is the one you can actually build on.
- Architecture. Odin leads with taint-guided triage (dataflow narrows candidates → LLM proves exploitability, the LLift/INFERROI lineage) and a published, reproducible FP rate. Most reviewers — open or closed — are prompt-over-diff; none publish a false-positive number you can regenerate. If you're on GitLab, want the most mature bot UX today, or want a hosted option, the tools above may fit you better.
MIT — use it, fork it, make it better.
