Open source · AI-native SAST

Anvesha

Sanskrit for investigation, quest — a security scanner that investigates code before it judges it. Built around one persistent dataflow graph, not a pile of bolted-on scanners.

Why another SAST tool

Most tools land on one of two extremes. Anvesha is a deliberate bet on a third path.

Pure pattern-matching

Fast and cheap, but blind to context — huge false-positive volumes, and structurally unable to catch bugs with no syntactic signature at all (a missing ownership check is valid code; there's nothing to match against).

Hand everything to an LLM

Genuinely context-aware, but slow, expensive, and unverifiable at scale — every finding is only as trustworthy as one model's say-so.

Anvesha's bet

Do everything deterministic and non-hallucinating with real static analysis. Reserve AI reasoning for what's genuinely contextual — ambiguous semantic judgments, and adversarial verification of an already-narrow set of candidates.

No proof, no finding

Enforced in code, not just policy — the Finding model raises on construction if it has zero citations. Every result is a graph path with exact line evidence.

How it works

Everything is either building the Weave (the graph), walking it, or diffing it against a prior version — not separate subsystems per feature.

1

Graph-Build deterministic+ AI for the ambiguous cases

tree-sitter parses the codebase into a real syntax tree; an original per-language walker turns it into the Weave — nodes are functions, variables, call sites, parameters; edges are calls and data movement. A small deterministic catalog tags obvious source/sink/sanitizer patterns by name (request.args → source, cursor.execute → sink, escape/ sanitize → sanitizer). Anything the catalog can't classify — but that's reachable from a detected entry point — gets one focused question sent to an LLM.

2

Path-Reasoning deterministic+ AI for unrecognized sanitizers

A graph walk finds candidate paths from a tainted source to a sensitive sink, checking deterministically for known sanitizers along the way — including distinguishing a parameterized query from a string-built one via argument position, not just presence of a call. An unrecognized intermediate call gets one question sent to an LLM: does this actually neutralize the value for this sink?

3

Adversarial Consensus Verification AI, deliberately

Every surviving candidate is independently reviewed by two personas working from the same evidence — a prosecutor who must build a concrete exploit argument, and a defender who must actively hunt for a reason it isn't exploitable. Only agreement confirms a finding. Disagreement is kept, not discarded, so you can read why reviewers split.

CLI reference

Everything runs through one command today.

uv sync
uv run anvesha survey path/to/project --format markdown
FlagDefaultWhat it does
--formatjsonOutput as json or markdown.
--out <path>stdoutWrite the report to a file instead of stdout.
--fail-on <level>noneExit 1 on a qualifying finding. When AI verification ran, only confirmed findings qualify; otherwise any finding does.
--ai / --no-ai--aiEnable/disable all LLM-assisted phases. Auto-falls back to --no-ai behavior if claude isn't on PATH.
--llm-model <name>CLI defaultOverride the model used for LLM-assisted phases.
--llm-budget <n>50Max LLM calls for Phase 1 labeling and Phase 3 resolution, each.
--consensus-reviewers22 (unanimous required) or 3 (majority, adjudicator tiebreaks).
--concurrency <n>4Max concurrent Phase 4 verification calls.

The default LLM backend is your own authenticated claude CLI session — no separate API key needed. It's invoked with --restricted (no tool access), the prompt piped over stdin, and --json-schema for schema-validated structured output — see Architecture.

Architecture

One graph model, a pluggable adapter per language, and a narrow LLM-provider interface used only where reasoning actually needs it.

anvesha/
├─ weave/            # the graph: model, diff, scoping, (de)serialize, on-disk store
├─ adapters/         # pluggable per-language parsing — one adapter today: Python/tree-sitter
├─ tracing/          # Phase 3: the deterministic source→sink path walker + sanitizer check
├─ reasoning/        # the LLM layer — provider abstraction + Phases 1, 3-ambiguous, 4
│  ├─ provider.py         # LLMProvider interface — one method: ask_structured(...)
│  ├─ providers/          # claude_cli.py (default) + offline.py (--no-ai fallback)
│  ├─ phase1_labeler.py
│  ├─ phase3_resolver.py
│  └─ phase4_verifier.py
├─ findings/         # the Finding model — citations are non-negotiable
├─ report/           # JSON + Markdown output
└─ cli.py              # `anvesha survey ...`

Adding a language is meant to be "write another adapter," not "redesign the core" — a LanguageAdapter only needs to produce Node/Edge objects; nothing downstream (the walker, the reasoning phases, reporting) is language-specific. Adding an LLM backend is the same shape: a new LLMProvider implementation, nothing upstream changes.

Status & roadmap

Early and under active development.

Built

  • Graph-Build, Path-Reasoning, and Adversarial Consensus Verification — the full loop
  • Python adapter (tree-sitter based)
  • claude-CLI LLM provider + offline fallback
  • JSON & Markdown reporting

Not yet built

  • Risk-lens overlays (business-impact, data-sensitivity, adversary-technique, agentic-risk, org-policy)
  • Business-logic / access-control reasoning pass
  • Environment-inference-based prioritization
  • Retest & PR-review CLI commands
  • SARIF export, deterministic auto-fix
  • Additional language adapters