Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ Batou is a security scanner that runs as a Claude Code hook, analyzing code for
```
cmd/batou/main.go Entry point - reads hook JSON from stdin, runs scanner, outputs hints
internal/scanner/ Core scan orchestrator (concurrent rule execution + preprocessing)
internal/rules/ 43 rule categories (676 regex-based rules)
internal/rules/ 45 rule categories (684 regex-based rules)
internal/ast/ Tree-sitter AST parsing (parser, query, filter, context)
internal/analyzer/ Language detection + 15 AST security analyzers
internal/taint/ Taint analysis engine (source -> sink tracking with sanitizers)
internal/taint/astflow/ Go-specific AST taint walker (uses go/ast, tracks channels/goroutines)
internal/taint/tsflow/ Tree-sitter taint walker for 15 languages (Python, JS/TS, Java, Perl, etc.)
internal/taint/languages/ Language-specific taint catalogs (16 languages, 56+ files)
internal/taint/tsflow/ Tree-sitter taint walker for 16 languages (Python, JS/TS, Java, Perl, Zig, etc.)
internal/taint/languages/ Language-specific taint catalogs (17 languages, 65 files)
internal/hints/ Hint generation for Claude feedback (language-specific fix examples)
internal/graph/ Persistent call graph + interprocedural analysis
internal/hook/ Hook I/O (JSON stdin/stdout, exit codes)
Expand All @@ -27,9 +27,9 @@ internal/testutil/ Test framework helpers
## Key Concepts

- **Four-layer analysis** (layers share parsed trees and taint flows — no redundant re-parsing):
- Layer 1: Regex rules (676 pattern-matching rules across 43 categories)
- Layer 2: AST analysis (tree-sitter structural analysis for 15 languages). Tree-sitter tree is cached and shared with Layer 3's tsflow engine.
- Layer 3: Taint analysis (source-to-sink dataflow with 1,069 entries across three engines). TaintFlow objects are cached and passed to Layer 4 for precise interprocedural signatures.
- Layer 1: Regex rules (684 pattern-matching rules across 45 categories)
- Layer 2: AST analysis (tree-sitter structural analysis for 15 languages). Tree-sitter tree is cached and shared with Layer 3's tsflow engine. Zig uses tsflow config but does not yet have a dedicated AST analyzer.
- Layer 3: Taint analysis (source-to-sink dataflow with 1,123 entries across three engines). TaintFlow objects are cached and passed to Layer 4 for precise interprocedural signatures.
- Layer 4: Call graph (persistent interprocedural taint tracking across function boundaries, cross-file caller loading from disk)
- **Confidence scoring**: Each finding gets a computed `ConfidenceScore` (0.0–1.0) reflecting which analysis layers confirmed it. Blocking requires both `Severity >= Critical` AND `ConfidenceScore >= 0.7`. This means regex-only Critical findings (score 0.3–0.5) become hints instead of blocks, while multi-layer-confirmed findings still block.
- **Shared parse cache** (each file parsed once per parser type):
Expand All @@ -38,7 +38,7 @@ internal/testutil/ Test framework helpers
- Layer 3 `TaintFlow` objects → passed to Layer 4's `ComputeTaintSig()` for precise signature derivation (falls back to regex when flows are nil)
- **Three taint engines** (scanner routes automatically by language):
- `astflow`: Go-specific, uses `go/ast` for precise tracking through channels, select, goroutines, and Go idioms. Accepts pre-parsed `GoParseResult` via `AnalyzeGoWithAST()`.
- `tsflow`: Generic tree-sitter walker for 15 languages (Python, JS, TS, Java, PHP, Ruby, C, C++, C#, Kotlin, Rust, Swift, Lua, Groovy, Perl) with per-language config tables. Accepts pre-parsed tree via `AnalyzeWithTree()`.
- `tsflow`: Generic tree-sitter walker for 16 languages (Python, JS, TS, Java, PHP, Ruby, C, C++, C#, Kotlin, Rust, Swift, Lua, Groovy, Perl, Zig) with per-language config tables. Accepts pre-parsed tree via `AnalyzeWithTree()`.
- `taint.Analyze`: Regex-based fallback for languages without tree-sitter support
- **Preprocessing**: CRLF normalization, multi-line continuation joining (backslash + implicit), unicode identifier support
- **AST false-positive filter**: Suppresses regex findings inside comment AST nodes (not strings — SQL/XSS patterns in strings are intentional)
Expand Down Expand Up @@ -99,14 +99,16 @@ Developers and Claude can suppress findings with inline directives in code comme

## Rule Categories

injection, xss, traversal, crypto, secrets, ssrf, auth, generic, logging, validation, memory, xxe, nosql, deser, prototype, massassign, cors, graphql, misconfig, redirect, kotlin, swift, rust, csharp, perl, lua, groovy, golang, java, jsts, python, php, ruby, framework (spring, express, django, flask, rails, laravel, react, tauri)
injection, xss, traversal, crypto, secrets, ssrf, auth, generic, logging, validation, memory, xxe, nosql, deser, prototype, massassign, cors, graphql, misconfig, redirect, kotlin, swift, rust, csharp, perl, lua, groovy, zig, golang, java, jsts, python, php, ruby, framework (spring, express, django, flask, rails, laravel, react, tauri), container, encoding, ssti, jwt, session, upload, race, websocket, oauth, header

## Languages Supported

Go, Python, JavaScript/TypeScript, Java, PHP, Ruby, C, C++, Kotlin, Swift, Rust, C#, Perl, Lua, Groovy
Go, Python, JavaScript/TypeScript, Java, PHP, Ruby, C, C++, Kotlin, Swift, Rust, C#, Perl, Lua, Groovy, Zig

**AST analysis via tree-sitter**: Go, Python, JavaScript/TypeScript, Java, PHP, Ruby, C/C++, Kotlin, Swift, Rust, C#, Lua, Groovy, Perl

**Taint analysis via tsflow** (no dedicated AST analyzer yet): Zig

## Building & Testing

```bash
Expand All @@ -124,7 +126,7 @@ Note: Tree-sitter requires CGo. The Makefile sets `CGO_ENABLED=1` automatically.
- `internal/analyzer/*/` - Each AST analyzer has a `*_test.go` file (goast, pyast, javaast, etc.)
- `internal/taint/` - engine_test.go, scope_test.go, tracker_test.go
- `internal/taint/astflow/` - Go-specific AST taint flow tests (channels, select, goroutines)
- `internal/taint/tsflow/` - Tree-sitter taint walker tests (15 languages)
- `internal/taint/tsflow/` - Tree-sitter taint walker tests (16 languages)
- `internal/graph/` - interprocedural_test.go (cross-function analysis)
- `internal/scanner/` - scanner_test.go (integration), preprocess_test.go (multi-line joining)
- `internal/hook/` - hook_test.go (I/O layer tests)
Expand Down Expand Up @@ -182,7 +184,7 @@ Run after adding new rules to verify no accidental ID collisions or numbering ga
- Stdin is limited to 50MB to prevent OOM
- `BlockWrite` runs AFTER `OutputPreTool` so Claude always gets hints
- Taint analysis uses 0.8x confidence decay for unknown function propagation (applies to all three engines)
- Scanner routes taint analysis: Go → `astflow.AnalyzeGoWithAST` (reuses cached `go/ast`), `tsflow.Supports(lang)` → `tsflow.AnalyzeWithTree` (reuses cached tree-sitter tree, 15 languages including Perl), else → `taint.Analyze` (regex fallback)
- Scanner routes taint analysis: Go → `astflow.AnalyzeGoWithAST` (reuses cached `go/ast`), `tsflow.Supports(lang)` → `tsflow.AnalyzeWithTree` (reuses cached tree-sitter tree, 16 languages including Perl and Zig), else → `taint.Analyze` (regex fallback)
- Layer 3 taint flows are cached in `sctx.TaintFlows` and passed to Layer 4's `PropagateInterproc()` for precise interprocedural signatures
- Layer 4 loads cross-file callers from disk (2MB limit, cached) when they aren't in the current file contents
- Test file paths matter - use non-test paths like `/app/handler.go` to avoid `isTestFile()` exclusion
Expand Down
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<img width="512" height="512" alt="logo_2" src="https://github.com/user-attachments/assets/a3157fb7-68cb-40af-878f-02dc54f62df9" />

A security scanner that catches vulnerabilities in real-time as AI writes code. Built as a [Claude Code hook](https://docs.anthropic.com/en/docs/claude-code/hooks), Batou analyzes every file write across 16 languages using regex, AST, taint analysis, and interprocedural call graph tracking.
A security scanner that catches vulnerabilities in real-time as AI writes code. Built as a [Claude Code hook](https://docs.anthropic.com/en/docs/claude-code/hooks), Batou analyzes every file write across 17 languages using regex, AST, taint analysis, and interprocedural call graph tracking.

High-confidence findings (confirmed by multiple layers) block the write. Lower-confidence findings produce hints — Claude sees the advice without being interrupted by false positives.

Expand All @@ -14,9 +14,9 @@ Claude writes code → Batou intercepts → 4-layer scan → Confidence scoring

| Layer | What | How |
|-------|------|-----|
| 1. Regex | 676 pattern rules across 43 categories | Fast pattern matching for known vulnerability signatures |
| 1. Regex | 684 pattern rules across 45 categories | Fast pattern matching for known vulnerability signatures |
| 2. AST | Tree-sitter parsing for 15 languages | Suppresses false positives in comments, structural analysis |
| 3. Taint | Source-to-sink dataflow (1,069 catalog entries) | Tracks user input through variables to dangerous functions |
| 3. Taint | Source-to-sink dataflow (1,123 catalog entries) | Tracks user input through variables to dangerous functions |
| 4. Call Graph | Interprocedural analysis across files | Persistent cross-function taint tracking within a session |

Parsed trees and taint flows are shared across layers — each file is parsed once.
Expand Down Expand Up @@ -48,11 +48,11 @@ git clone https://github.com/turenlabs/batou.git && cd batou && make build && ma

## What It Detects

**676 rules, 43 categories, 16 languages**
**684 rules, 45 categories, 17 languages**

Injection, XSS, path traversal, crypto weaknesses, hardcoded secrets, SSRF, auth issues, XXE, deserialization, CORS, SSTI, JWT flaws, session issues, file upload, race conditions, log injection, input validation, memory safety, and framework-specific misconfigs (Spring, Express, Django, Flask, Rails, Laravel, React, Tauri).

**Languages:** Go, Python, JavaScript/TypeScript, Java, PHP, Ruby, C, C++, Kotlin, Swift, Rust, C#, Perl, Lua, Groovy
**Languages:** Go, Python, JavaScript/TypeScript, Java, PHP, Ruby, C, C++, Kotlin, Swift, Rust, C#, Perl, Lua, Groovy, Zig

## False Positive Suppression

Expand All @@ -79,14 +79,18 @@ process(rows)

**Targets:** specific rule ID (`BATOU-INJ-001`), category (`injection`), or `all`. Always include a reason after `--`.

## Adding Batou to Your Project's CLAUDE.md

Copy the snippet from [`docs/claude-md-snippet.md`](docs/claude-md-snippet.md) into your project's `CLAUDE.md` so Claude understands how Batou works and can respond to its findings correctly.

## Testing

```bash
make test # Run all tests with race detector
make test-cover # Run with coverage
```

2,000+ tests, 430+ fixtures across 16 languages.
2,000+ tests, 430+ fixtures across 17 languages.

## License

Expand Down
1 change: 1 addition & 0 deletions cmd/batou/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import (
_ "github.com/turenlabs/batou/internal/rules/swift"
_ "github.com/turenlabs/batou/internal/rules/csharp"
_ "github.com/turenlabs/batou/internal/rules/rust"
_ "github.com/turenlabs/batou/internal/rules/zig"
_ "github.com/turenlabs/batou/internal/rules/php"
_ "github.com/turenlabs/batou/internal/rules/ruby"
_ "github.com/turenlabs/batou/internal/rules/python"
Expand Down
67 changes: 67 additions & 0 deletions docs/claude-md-snippet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Batou CLAUDE.md Snippet

Copy the section below into your project's `CLAUDE.md` so Claude understands how Batou works and can respond to its findings correctly.

---

## Copy below this line

```markdown
## Batou Security Scanner

This project uses [Batou](https://github.com/turenlabs/batou), a security scanner that runs as a Claude Code hook. Batou intercepts every `Write`, `Edit`, and `NotebookEdit` tool call and scans the code for vulnerabilities in real-time.

### How it works

Batou runs a 4-layer analysis on every file you write:

1. **Regex rules** — 684 pattern-matching rules across 45 categories (injection, XSS, traversal, crypto, secrets, SSRF, memory safety, etc.)
2. **AST analysis** — Tree-sitter structural analysis that suppresses false positives in comments
3. **Taint analysis** — Source-to-sink dataflow tracking (1,123 catalog entries) that follows user input through variables to dangerous functions
4. **Call graph** — Interprocedural analysis that tracks taint across function boundaries and files

**Blocking threshold:** A finding blocks the write only when `Severity >= Critical AND ConfidenceScore >= 0.7`. Regex-only findings (score 0.3-0.5) produce hints instead of blocks. Multi-layer confirmation (regex + taint, or regex + AST) boosts confidence and can trigger blocks.

**Supported languages:** Go, Python, JavaScript/TypeScript, Java, PHP, Ruby, C, C++, Kotlin, Swift, Rust, C#, Perl, Lua, Groovy, Zig

### When Batou blocks a write

If Batou blocks your write, it means a high-confidence critical vulnerability was detected. You should:

1. Read the finding carefully — it includes the CWE ID, OWASP category, and a description of the vulnerability
2. Fix the vulnerability using the suggested approach in the hint
3. Do NOT suppress the finding unless you are certain it is a false positive
4. Common fixes:
- **Injection (CWE-78, CWE-89):** Use parameterized queries or argument arrays instead of string concatenation
- **Path traversal (CWE-22):** Validate and canonicalize file paths before use
- **Hardcoded secrets (CWE-798):** Use environment variables or a secrets manager
- **XSS (CWE-79):** Escape output or use framework-provided safe rendering
- **Unsafe memory (CWE-457, CWE-588):** Add bounds checks or use safe alternatives

### When Batou gives a hint (does not block)

Hints are lower-confidence findings. Consider the advice but use your judgment. If the code is intentionally written that way (e.g., a test fixture, a safe wrapper), you can proceed or suppress.

### Suppressing false positives

If you are certain a finding is a false positive, suppress it with an inline directive:

```
// batou:ignore BATOU-INJ-001 -- query uses parameterized input
db.Query("SELECT * FROM users WHERE id = " + id)
```

Block suppression for multiple lines:

```
// batou:ignore-start injection
rows := db.Query(dynamicSQL)
process(rows)
// batou:ignore-end
```

- **Targets:** exact rule ID (`BATOU-INJ-001`), category name (`injection`), or `all`
- **Comment styles:** `//`, `#`, `--`, `/*`, `<!--`, `rem` — any comment prefix works
- **Always include a reason** after `--` explaining why the suppression is safe
- Use suppression sparingly — prefer fixing the vulnerability over suppressing it
```
2 changes: 1 addition & 1 deletion docs/perl.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Batou provides comprehensive security scanning for Perl code, covering CGI.pm, PSGI/Plack, Mojolicious, Dancer2, Catalyst, and DBI-based applications. Analysis spans all four layers: regex-based pattern matching (348 rules, Layer 1), tree-sitter AST structural analysis (Layer 2), taint source-to-sink tracking via the tree-sitter AST walker (Layer 3), and interprocedural call graph analysis (Layer 4). Perl coverage includes 25 taint sources across 6 frameworks, 27 sinks spanning 12 vulnerability categories, and 16 sanitizer recognitions to reduce false positives.

Perl taint analysis uses the tree-sitter AST walker (`internal/taint/tsflow/`), the same engine used by the other 14 supported languages. The tree-sitter-perl grammar (vendored from `github.com/tree-sitter-perl/tree-sitter-perl`, MIT license) provides structural AST parsing, enabling taint tracking through variable reassignment, complex expressions, and method call chains with higher precision than regex-based analysis.
Perl taint analysis uses the tree-sitter AST walker (`internal/taint/tsflow/`), the same engine used by the other 15 supported languages. The tree-sitter-perl grammar (vendored from `github.com/tree-sitter-perl/tree-sitter-perl`, MIT license) provides structural AST parsing, enabling taint tracking through variable reassignment, complex expressions, and method call chains with higher precision than regex-based analysis.

## Detection

Expand Down
1 change: 1 addition & 0 deletions internal/analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ var extToLanguage = map[string]rules.Language{
".pm": rules.LangPerl,
".cgi": rules.LangPerl,
".lua": rules.LangLua,
".zig": rules.LangZig,
".sh": rules.LangShell,
".bash": rules.LangShell,
".zsh": rules.LangShell,
Expand Down
1 change: 1 addition & 0 deletions internal/rules/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const (
LangJSON Language = "json"
LangPerl Language = "perl"
LangLua Language = "lua"
LangZig Language = "zig"
LangDocker Language = "dockerfile"
LangTerraform Language = "terraform"
LangAny Language = "*"
Expand Down
Loading