batou

skill
Guvenlik Denetimi
Uyari
Health Uyari
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Uyari
  • process.env — Environment variable access in batou-rules/testdata/fixtures/javascript/safe/app_jwt_fixed.js
Permissions Gecti
  • Permissions — No dangerous permissions requested

Bu listing icin henuz AI raporu yok.

SUMMARY

Batou catches risky code while Claude Code is writing files, before bad code lands in your repo.

README.md

Batou

Runtime SAST for AI coding agents. Catches vulnerabilities as code is written, not after.

Built as a Claude Code hook, Batou intercepts every Write, Edit, and NotebookEdit tool call. High-risk findings block the write. Lower-risk findings produce inline hints that guide the agent to fix the issue.

Installation

Homebrew (recommended)

brew install turenlabs/tap/batou && batou-setup

This installs the binary and configures Claude Code hooks automatically.

To update:

brew upgrade batou

Install script

# Quick install (downloads binary + configures hooks globally)
curl -fsSL https://raw.githubusercontent.com/turenlabs/batou/main/install.sh | bash

# Install + configure hooks for a specific project
curl -fsSL https://raw.githubusercontent.com/turenlabs/batou/main/install.sh | bash -s -- --setup /path/to/project

# Or install + configure hooks globally
curl -fsSL https://raw.githubusercontent.com/turenlabs/batou/main/install.sh | bash -s -- --global

Build from source

# Requires Go 1.21+, CGo, gcc/clang
git clone https://github.com/turenlabs/batou.git && cd batou && make build && make install

CLI usage

batou                  (hook mode) read a Claude Code hook event on stdin, scan the touched
                       file, emit security hints. Invoked automatically by the PreToolUse/
                       PostToolUse hooks — not meant to be run by hand.
batou scan <dir>       scan a directory tree; emit one JSON finding per line (JSONL) to stdout
batou findings         report the project-local findings cache (.batou/findings.json)
batou version          print version
batou help             show usage

batou scan is the ad-hoc repo scanner. It prints one JSON object per finding (JSONL — not a
JSON array), so pipe it through jq with -s if you need to slurp the stream into one value:

batou scan ./src > findings.jsonl
batou scan --exclude .batou,node_modules /path/to/repo | jq -s 'length'
batou scan --quiet . | jq -c 'select(.taint_path != null) | {file:.file, line:.line, rule:.rule_id}'

Default behavior changed 2026-05-13. By default batou scan emits only AST / taint /
interprocedural-confirmed findings. Regex-tier (Layer-1) findings — including BATOU-SECRETS-*
(hardcoded API keys, AWS keys, etc.), weak-crypto, and misconfig rules
— are HIDDEN by default
because they're the dominant false-positive source on real codebases. The hook path (batou with
no args, driven by Claude Code) is unaffected and still surfaces every finding.

Restore the old "emit everything" behavior with --with-regex, or run the regex tier on its own
(e.g. for a secrets-only pass) with --regex-only:

batou scan ./src | jq -s 'length'                    # default: dataflow-confirmed only
batou scan --with-regex ./src | jq -s 'length'       # include regex-tier (incl. secrets/crypto)
batou scan --regex-only ./src | jq -s 'length'       # secrets-and-crypto-only pass
batou scan --min-confidence 0.7 ./src                # drop everything below 0.7 confidence

--min-confidence FLOAT is the orthogonal lever (0.7 keeps AST/interproc-confirmed plus
high-confidence taint findings); it can be combined with --with-regex or --regex-only.
--no-regex is accepted as a silent compat alias for the new default.

batou scan writes/updates .batou/callgraph.json (the persistent call graph) relative to the
current directory; pass --exclude .batou to keep it out of a re-scan, or --output FILE to
write the JSONL to a file instead of stdout. For a one-shot CI scan where the on-disk graph
would just be noise, pass --no-callgraph to skip persistence entirely (the graph is still
built in memory for interprocedural analysis during the scan). To write the graph somewhere
other than the cwd, pass --callgraph PATH. The two flags are mutually exclusive. Run
batou scan --help for the full flag list.

Shell wrapper gotcha (zsh vs bash). When wrapping batou scan in shell scripts, note that
zsh does NOT word-split unquoted parameter expansions by default while bash does. Code that
builds a flag string and passes it unquoted —

flags="--quiet --with-regex"
batou scan $flags ./src

— works in bash (which splits $flags into two arguments) but breaks in zsh, which passes the
whole string as a single argument and Go's flag parser then rejects it with
flag provided but not defined: -quiet --with-regex. Use a bash array, or write each
invocation explicitly:

flags=(--quiet --with-regex)
batou scan "${flags[@]}" ./src

JSONL output fields

Each line is a single JSON object. Stable triage fields a reviewer reaches for:

  • file, line, rule_id, severity, title, cwe, owasp, language
  • confidence (high/medium/low) and confidence_score (0.0–1.0)
  • matched_text — short excerpt from the matched code
  • taint_path — structured source→sink steps for taint / interprocedural findings (null otherwise)
  • source_type — taint source category (user_input, network, file_read, env_var, …). Present on taint / interprocedural findings only; omitted on regex/AST.
  • sink_type — taint sink category (sql_query, command_exec, html_output, code_eval, …). Present on taint / interprocedural findings only.
  • is_test_file — bool, true if the path looks like a test / fixture / mock / spec.
  • is_docs_file — bool, true for /docs/, /doc/, /site/, or .md/.rst/.adoc/.markdown/.asciidoc extensions.
  • is_generated_or_vendor — bool, true for vendor/, node_modules/, __generated__/, third_party/, common lockfiles (package-lock.json, yarn.lock, go.sum, Cargo.lock, composer.lock, etc.), *.min.js/*.min.css/*.bundle.js/*.pb.go/*_pb.go/*_pb2.py/*.generated.* files.

The three is_* flags are derived from the file path only — they are cheap and present on every record. source_type/sink_type come from the taint engine's source/sink classifiers and are omitted (not set to empty string) for non-taint findings.

Common triage filter — high-confidence findings in non-test, non-vendor production code:

batou scan ./src \
  | jq -c 'select(.confidence_score >= 0.7 and .is_test_file != true and .is_generated_or_vendor != true)'

Or pull just the SQL-injection taint flows:

batou scan ./src | jq -c 'select(.sink_type == "sql_query")'

Running bare batou from a terminal (no stdin pipe) just prints this usage — the no-arg mode is
only meant to be driven by the Claude Code hook, which pipes a JSON event on stdin.

Architecture

Agent writes code
    |
    v
PreToolUse hook fires
    |
    v
Layer 1: Regex rules (684 patterns, 45 categories)
Layer 2: AST analysis (tree-sitter, 15 languages)
Layer 3: Taint analysis (source -> sink tracking, 3 engines)
Layer 4: Call graph (interprocedural, cross-file, persistent)
    |
    v
Risk scoring: RiskScore = Severity.ImpactWeight * ConfidenceScore
    |
    +-- RiskScore >= 0.7 --> BLOCK (write rejected)
    +-- RiskScore < 0.7  --> HINT (agent sees advice, write allowed)

Each file is parsed once. Trees, taint flows, and AST results are cached and shared across layers.

Languages

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

AST analysis: All of the above via tree-sitter
Taint analysis: Go (go/ast), 16 languages (tree-sitter), regex fallback for others

What It Detects

684 rules across 45 categories:

Injection (SQL, command, code), XSS, path traversal, weak crypto, hardcoded secrets, SSRF, auth/authz, XXE, deserialization, CORS, SSTI, JWT, session management, file upload, race conditions, log injection, input validation, memory safety, OAuth, WebSocket, header injection, encoding issues.

Framework-specific rules: FastAPI, Django, Flask, Express, Spring, Rails, Laravel, React, Tauri, NestJS.

ORM taint tracking: SQLAlchemy, Django ORM, Prisma, Sequelize, TypeORM, Drizzle, Hibernate, MyBatis, JOOQ, GORM, sqlx, ent, bun, ActiveRecord, Eloquent, Doctrine, Entity Framework, Dapper, Diesel, SQLx (Rust), Peewee, Tortoise ORM.

Risk Scoring

Findings carry a RiskScore computed from severity and confidence:

RiskScore = Severity.ImpactWeight * ConfidenceScore

Impact weights: Critical=1.0, High=0.8, Medium=0.5, Low=0.25
Confidence: 0.0-1.0, computed by which layers confirmed the finding
Scenario Confidence RiskScore Result
Regex-only Critical 0.3-0.5 0.3-0.5 Hint
AST-confirmed High 0.7 0.56 Hint
Taint-confirmed Critical 0.85 0.85 Block
Multi-layer agreement 0.95+ 0.95+ Block

The key insight: a Critical regex-only finding (low confidence) becomes a hint, while a High-severity finding confirmed by taint analysis can block. Confidence matters more than severity alone.

Suppression

Suppress findings with inline directives:

# batou:ignore BATOU-INJ-001 -- parameterized query
db.execute(query, params)

Block suppression for multiple lines:

# batou:ignore-start injection -- all queries in this block use ORM
results = User.objects.filter(name=name)
orders = Order.objects.filter(user=user)
# batou:ignore-end

Targets: exact rule ID (BATOU-INJ-001), category (injection, framework, jwt, etc.), or all.

All 45 rule categories are suppressible by name. Adding a new category requires one entry in the CategoryForRule() map in batou-rules/rules/rule.go.

Agent behavior with suppression

Batou detects when an AI agent adds batou:ignore directives and emits a BATOU-SUPPRESS-REVIEW finding telling the agent to fix the code instead of suppressing. The hint output says:

"Fix the underlying issue. Only suppress as a last resort."

This prevents agents from carpet-bombing files with suppress comments instead of writing secure code.

Findings Lifecycle

Findings are tracked across scans in .batou/findings.json:

  • new -- first time this finding appears
  • recurring -- seen in a previous scan, still present
  • fixed -- was active, no longer appears in scan
  • suppressed -- active finding transitioned via batou:ignore

The findings store uses file locking (flock) to prevent corruption from concurrent hook invocations. Corrupted JSON files self-heal on next open.

Lifecycle events are included in the hook output so external dashboards and metrics sinks can track them.

Project Structure

batou-rules/rules/          45 rule categories (684 regex rules)
batou-core/scanner/          Scan orchestrator, confidence scoring, dedup
batou-core/suppress/         Inline suppression parsing and matching
batou-core/taint/            Taint analysis (3 engines: astflow, tsflow, regex)
batou-core/taint/languages/  Per-language taint catalogs (17 languages)
batou-core/analyzer/         Tree-sitter AST analyzers (15 languages)
batou-core/graph/            Persistent call graph + interprocedural analysis
batou-core/hints/            Hint generation for agent feedback
batou-core/reporter/         Result formatting (block messages, risk labels)
batou-core/findings/         Findings persistence + lifecycle tracking
batou-core/hook/             Hook I/O (JSON stdin/stdout, exit codes)
batou-core/ledger/           Session audit logging
batou-core/cmd/batou/        Entry point (hook mode + `scan`/`findings` subcommands)

Building

Requires Go 1.25+, CGo enabled (for tree-sitter).

make build      # Build binary to bin/batou
make test       # Run all tests with race detector

Testing

go test ./batou-core/... ./batou-rules/... -race   # Full test suite
go test ./batou-core/scanner/ -run TestCategorySuppress_Pipeline  # Suppress lifecycle
go test ./batou-core/findings/ -run TestConcurrent  # File locking
go test ./batou-core/suppress/ -v  # All category mappings

OWASP Benchmark

Batou is benchmarked against the OWASP Benchmark for Java and Python:

make bench-owasp-clone   # Clone test data
make bench-owasp         # Run benchmarks (~3,970 test cases)

Current scores (all emitted findings, minConf=0): Java 94.5% TPR / 16.8% FPR (Youden +77.7%), Python 98.0% TPR / 5.9% FPR (Youden +92.1%).

License

MIT

Yorumlar (0)

Sonuc bulunamadi