Skip to content

Repository files navigation

VulnHunterX

SAST (CodeQL, Semgrep, OpenGrep) + LLM vulnerability hunting and verification

A Python framework that pairs static analysis with multi-turn LLM verification to suppress false positives in SAST findings, implementing the Vulnhalla methodology of guided-question, evidence-anchored triage.

Python 3.12+ License: MIT SAST


Table of Contents


Overview

SAST engines deliberately over-approximate — they flag every program point that might be vulnerable. The dominant cost of running them in production is not analysis time but human triage. VulnHunterX automates triage by feeding each SARIF finding to an LLM that must answer a rule-specific checklist of evidence-anchored questions, request additional code context when needed (multi-turn), and emit a structured verdict with a confidence score.

Source  ──>  Static Analysis  ──>  SARIF Findings  ──>  LLM Verification  ──>  Verdicts
(prepare)    (CodeQL/Semgrep/      (rule, file,          (guided questions,      (TP/FP/NMD +
              OpenGrep)             line, severity)       multi-turn context)     confidence)

The Vulnhalla methodology forces the LLM to:

  • answer rule-specific guided questions before committing to a verdict,
  • request callers, structs, globals, free-sites, etc. as needed via a fixed context vocabulary,
  • reason across multiple turns rather than pattern-match a single snippet.

Key features

Feature Description
Languages C, C++, Python, JavaScript, PHP, Java, Go, C#
SAST engines CodeQL, Semgrep (live registry.semgrep.dev packs), OpenGrep (registry-free, runs the vendored offline config/opengrep-rules/) — --tool codeql|semgrep|opengrep|both|all
Security rule profiles standard → extended → maximum → extended-registry → full (see config/RULES.md)
Guided questions 394 rule-specific templates across 7 per-language banks plus a fallback
LLM providers OpenAI, Anthropic, Gemini, DeepSeek, Ollama (local or Ollama Cloud) — via LiteLLM
Multi-turn verification Dynamic context expansion (callers, structs, globals, macros, free-sites)
Inputs Git URL, local directory, or batch list (repos.yaml)
Reports Markdown, EN/VI, executive summary + per-finding detail
Benchmarking Precision/recall across 6 ground-truth datasets (see benchmarks/README.md)

Quick Start

Prerequisites

Install

git clone https://github.com/vinsoc-cyber/VulnHunterX.git && cd VulnHunterX
uv venv --python python3.12 .venv && source .venv/bin/activate
uv pip install -e ".[dev]"

cp env.example .env       # add OPENAI_API_KEY / ANTHROPIC_API_KEY / GEMINI_API_KEY / OLLAMA_API_BASE
vuln-hunter-x check-env   # verify toolchain

Requires uv (curl -LsSf https://astral.sh/uv/install.sh | sh). Plain python3.12 -m venv .venv + pip install -e ".[dev]" also works if you'd rather not install uv.

First run

The fastest path is one of the per-language example scripts under examples/, which clone a real-world target, a vulnerable target, and run the full pipeline against both so the FP-vs-TP contrast is visible:

python examples/pipeline_python.py
# Common flags: --dry-run, --skip-clone, --api
Script Language Real-world Vulnerable
pipeline_python.py Python pyyaml dvpwa
pipeline_javascript.py JavaScript minimist nodegoat
pipeline_c.py C c-ares dvcp
pipeline_cpp.py C++ re2 insecure-coding-examples
pipeline_java.py Java commons-collections webgoat
pipeline_php.py PHP monolog dvwa
pipeline_go.py Go gin govwa
pipeline_csharp.py C# newtonsoft-json webgoat-net

One-shot scan (or guided wizard)

# Run the whole pipeline (prepare → analyze → verify → report) in one command
vuln-hunter-x scan --url https://github.com/org/app.git --lang csharp --profile full --limit 10

# Or be prompted for everything interactively
vuln-hunter-x interactive

scan accepts the union of the per-stage flags (--url/--local-path/--repo, --lang, --profile, --tool, --provider/--model, --limit) plus --skip-verify / --skip-report to stop early.

Or run stages directly

vuln-hunter-x prepare --repo pyyaml
vuln-hunter-x analyze --repo pyyaml --profile extended
vuln-hunter-x verify  --repo pyyaml --limit 5
vuln-hunter-x report  --repo pyyaml --lang python

C# is compiled but uses CodeQL buildless extraction by default (no dotnet build required); pass --build-command "dotnet build" for full fidelity:

vuln-hunter-x scan --url https://github.com/org/app.git --lang csharp

Add your own repository

# Single-shot from URL
vuln-hunter-x prepare --url https://github.com/org/app.git --lang python

# Existing checkout
vuln-hunter-x prepare --local-path /path/to/app --lang python --name app

# Compiled languages need a build command
vuln-hunter-x prepare --url https://github.com/org/lib.git --lang c --build-command "make"

Or list it under repos: in config/repos.yaml and run vuln-hunter-x prepare --repo <name>.

Troubleshooting

Symptom Fix
CodeQL CLI not found Add to PATH or set CODEQL_PATH in .env
Semgrep CLI not found Set SEMGREP_PATH in .env
OpenAI API key not configured Add OPENAI_API_KEY=sk-... to .env
could not resolve module cpp codeql pack install config/queries/tools/cpp
Database is already finalized Normal — analysis proceeds automatically

Pipeline Stages

# Command Input Output
1 prepare (alias clone) URL / local path / repos.yaml Source + CodeQL DB + context CSVs
2 analyze CodeQL DB and/or source tree SARIF findings
3 verify SARIF + context CSVs JSON verdicts + reasoning
4 report Verification results Markdown report (EN/VI)
Goal Required stages Optional
Static analysis only 1, 2 —
LLM verification 1, 2, 3 4 (report)

Stages 2–3 accept --local-path to operate directly on an arbitrary directory.


CLI Reference

check-env

vuln-hunter-x check-env

Verifies CodeQL CLI, Semgrep/OpenGrep, and LLM provider keys.

prepare (alias: clone)

Clone a repository, create a CodeQL database, and extract context CSVs (functions, callers, structs, globals, macros, classes).

vuln-hunter-x prepare                                                 # batch from repos.yaml
vuln-hunter-x prepare --url https://github.com/org/repo.git --lang go
vuln-hunter-x prepare --local-path /path/to/repo --lang python
vuln-hunter-x prepare --skip-clone --skip-db --force --repo libucl    # re-extract context only
Option Description Default
--config PATH Path to repos.yaml —
--url URL Git URL to clone —
--local-path PATH Existing local directory —
--name NAME Repository name (auto-derived if omitted) —
--lang LANG Language — required with --url or --local-path —
--build-command CMD Build command for compiled languages —
--repo NAME Filter to one repository (config mode) All
--skip-clone / --skip-db / --skip-context Skip stages false
--backend {auto,codeql,treesitter} Context extraction backend auto
--ask-llm Ask the LLM for help if the build fails false
-f, --force Force re-extraction of context CSVs false
--dry-run Preview actions false

Context extraction backends (--backend)

Context CSVs (functions, callers, structs/classes, globals, macros) feed multi-turn LLM verification. Two backends produce them:

Backend When to use Notes
auto (default) Most workflows CodeQL where a database was built; tree-sitter as fallback for repos where the DB build failed
codeql Highest precision Requires a valid CodeQL DB. Semantic-level extraction; adds C/C++-specific context (free_sites, destructors, field_writes) used by memory-safety guided questions
treesitter No CodeQL DB, quick iteration Syntactic extraction — fast, no DB build. Covers C, C++, Python, JavaScript, Java, PHP, Go, C#; skips the C/C++-specific context types listed above

Tree-sitter grammars (tree-sitter-c/cpp/python/javascript/java/php/go/c-sharp, pinned to >=0.23,<1.0) ship with the standard pip install -e . — no extra setup. Both backends emit the same CSV layout, so downstream stages (analyze, verify, report) work identically regardless of backend. vuln-hunter-x check-env verifies tree-sitter availability alongside CodeQL.

analyze

Run CodeQL, Semgrep, and/or OpenGrep against a prepared repo or a local path.

vuln-hunter-x analyze --repo libucl
vuln-hunter-x analyze --tool all --repo c-ares --profile full
vuln-hunter-x analyze --tool semgrep --local-path /path/to/project --lang python --profile extended
Option Description Default
--tool {codeql,semgrep,opengrep,both,all} Analyzer(s). both = CodeQL+Semgrep; all adds OpenGrep codeql
--profile {standard,extended,maximum,extended-registry,full} Rule profile — scales Semgrep/CodeQL coverage; OpenGrep uses the fixed vendored rule set regardless (see config/RULES.md) standard
--category CAT Filter by security category (repeatable) All
--local-path PATH Analyze a local directory —
--name NAME Repository name auto-derived
--semgrep-config CFG / --opengrep-config CFG Override rule pack(s) from profile
--codeql-suite SUITE Override CodeQL suite from profile
--repo NAME / --lang LANG Filters All
-j, --jobs N Parallel CodeQL analyses 2
--json Also output findings JSON false
-f, --force Re-run even if SARIF exists false
--dry-run / -v Preview / verbose false

verify

Verify SARIF findings using multi-turn LLM reasoning.

vuln-hunter-x verify --repo libucl
vuln-hunter-x verify --repo c-ares --lang c --category memory-safety
vuln-hunter-x verify --provider ollama --model ollama/llama3.2 --repo libucl
vuln-hunter-x verify --provider gemini --model gemini-2.5-flash --repo libucl
vuln-hunter-x verify --local-path /path/to/project --lang python --limit 20
Option Description Default
--config PATH Configuration file config/confirm_findings.yaml
--local-path PATH Verify findings for a local directory —
--name NAME Repository name auto-derived
--repo NAME / --lang LANG Filters All
--sarif PATH Process a specific SARIF file —
--provider {openai,anthropic,ollama,deepseek,gemini} LLM provider from config
--model NAME Model name from config
--temperature F / --max-tokens N LLM tuning from config
--max-iterations N Max conversation rounds per finding 3
-j, --jobs N Concurrent findings to verify (1 = sequential) 4
--limit N Maximum findings to process Unlimited
--category CAT Filter by category (repeatable, same vocabulary as analyze) All
--include-tests Include findings under test/, tests/ false
-v / -q Verbose / quiet normal
--log-file PATH Persist LLM conversations —
--dry-run Preview findings false

report

Generate a markdown report from verification results. verify already writes report.md and report_vi.md; use this to regenerate or change the output path.

vuln-hunter-x report --repo c-ares --lang c
vuln-hunter-x report --results-dir output/c/c-ares/verification_results
vuln-hunter-x report --repo libucl -o my-report.md --lang-report en
Option Description Default
--results-dir PATH Path to a verification_results/ directory auto-discover
--repo NAME / --lang LANG Auto-discover by repo and language —
-o, --output PATH Output path report.md in results dir
--lang-report {en,vi,all} Report language(s) all

Report sections: executive summary · findings overview (before/after verdicting) · severity breakdown · CWE distribution · per-finding detail (verdict, confidence, reasoning, dataflow).

Run any subcommand with --help for full options.

info

vuln-hunter-x info

Prints the resolved configuration — provider, model, paths.

scan

Runs the full pipeline (prepare → analyze → verify → report) in one command by composing the individual stages. Accepts the union of their flags.

vuln-hunter-x scan --url https://github.com/org/app.git --lang csharp --profile full --limit 10
vuln-hunter-x scan --repo pyyaml --tool both             # config-mode, from repos.yaml
vuln-hunter-x scan --local-path ./app --lang go --skip-report
Option Description Default
--url / --local-path / --repo Source selection (mirrors prepare) —
--lang Language (required with --url/--local-path) —
--tool codeql / semgrep / opengrep / both / all both
--profile Rule profile standard
--provider / --model / --limit LLM settings for the verify stage from config
--skip-verify / --skip-report Stop after analyze / skip report off

If the CodeQL database build fails (e.g. a compiled project that needs a specific build command), scan continues with source-only analysis when the analyzer can run without a database (--tool semgrep/opengrep/both/all) — CodeQL findings are skipped but Semgrep still runs. With --tool codeql the run aborts, since CodeQL needs the database.

interactive (alias: wizard)

A guided wizard that prompts for the inputs above, then dispatches a scan. The friendliest entry point — no flags to remember.

vuln-hunter-x interactive

It runs an environment check first and exits if no analyzer (CodeQL/Semgrep/OpenGrep) is installed. Each answer is validated as you enter it: a local path must exist, the chosen analyzer must be installed (unavailable ones are marked and rejected), and the LLM provider is live-tested — if it fails you can re-enter it, continue with verification skipped, or abort, instead of failing partway through the run.

Shell completion (optional)

Install the cli extra and enable argcomplete:

pip install "vuln-hunter-x[cli]"
# bash/zsh, for this shell:
eval "$(register-python-argcomplete vuln-hunter-x)"
# or globally (bash): activate-global-python-argcomplete

Python API

from pathlib import Path
from vuln_hunter_x import VerificationEngine
from vuln_hunter_x.reporting.markdown import MarkdownReportGenerator

engine = VerificationEngine.from_config("config/confirm_findings.yaml")
result = engine.verify_sarif("output/c/libucl/libucl.sarif", lang="c", repo_name="libucl")

for verdict in result.verdicts:
    print(f"{verdict.finding.rule_id}: {verdict.verdict} ({verdict.confidence})")

engine.save_results(result)
MarkdownReportGenerator().generate(
    result, Path("output/c/libucl/verification_results/report.md")
)

Rules & Coverage

config/RULES.md is the authoritative inventory — every profile, every custom CodeQL query, every custom Semgrep rule, with CWE tags and severities. Quick summary:

Metric Count
Rule profiles 5 (standard → full)
Security categories 12
CWE entries in routing map 124
Custom CodeQL queries 64 (C/C++ 16, Java 10, JavaScript 14, Python 11, Go 8, C# 5)
Custom Semgrep rules 103 (Python 22, JavaScript 14, Java 16, Go 19, PHP 14, C/C++ 4, C# 14)
Built-in CodeQL suites security-extended (~200), security-and-quality (~400)
Built-in Semgrep universal packs 8
Built-in Semgrep per-language packs 10 (django, flask, nodejs, gosec, …)
Guided-question templates 394 across 7 per-language banks + 1 fallback

Coverage growth from --profile standard to --profile full is roughly 5×–10× more rules per scan. Per-language registry packs (p/django, p/gosec, …) are only applied to matching repos so cross-language scans aren't polluted.

To add a custom rule:

  • CodeQL — drop <name>.ql into config/codeql-custom/<lang>/src/ with @id <lang>/<name> matching a guided-question key.
  • Semgrep — append a rule to config/semgrep-custom/<lang>.yaml with metadata.cwe: ["CWE-NNN"] so CWE-based routing works.

Both are activated by --profile full. Run python scripts/audit_rule_coverage.py --fail-on-gaps to verify the wiring.


Configuration

Priority: CLI args > environment variables > config file > defaults.

Environment variables (.env)

Variable Description
OPENAI_API_KEY OpenAI API key
ANTHROPIC_API_KEY Anthropic API key
GEMINI_API_KEY Google Gemini (AI Studio) API key. Comma-separated for a pool (k1,k2,k3); 2+ keys enable round-robin rotation with per-key 429 cooldown. GEMINI_API_KEYS is accepted as an alias; GOOGLE_API_KEY as a single-key fallback. Vertex AI is not supported (future work).
OLLAMA_API_BASE Ollama server URL (http://localhost:11434 for local, https://ollama.com for cloud)
OLLAMA_API_KEYS Ollama Cloud bearer token(s). Comma-separated for a pool (k1,k2,k3); 2+ keys enable round-robin rotation with per-key 429 cooldown. Required when OLLAMA_API_BASE points at ollama.com or the model carries a :cloud / -cloud tag.
LLM_PROVIDER / LLM_MODEL Override default provider / model
CODEQL_PATH / SEMGREP_PATH / OPENGREP_PATH Tool paths if not on PATH

LLM / verification settings (config/confirm_findings.yaml)

provider: openai          # openai | anthropic | ollama | deepseek | gemini
model: gpt-4o
temperature: 0.0          # greedy by default; verification is seeded for reproducibility
seed: 42                  # fixed sampling seed (null disables)
max_tokens: 1500
max_iterations: 3         # conversation rounds per finding

Repository list (config/repos.yaml)

repos:
  - name: pyyaml
    url: https://github.com/yaml/pyyaml.git
    language: python

  - name: libucl
    url: https://github.com/vstakhov/libucl.git
    language: c
    build_command: "mkdir -p build && cd build && cmake .. && make"
Field Description
name Short identifier used in output paths
url Git clone URL
language c · cpp · python · javascript · php · java · go · csharp
build_command Required for C/C++; omit for interpreted languages

Guided questions (config/prompts/)

Rule-specific question banks that force the LLM to reason step-by-step.

File Language Rule sets
cpp_questions.yaml C/C++ 59
python_questions.yaml Python 56
javascript_questions.yaml JavaScript / TypeScript 51
go_questions.yaml Go 50
java_questions.yaml Java 50
php_questions.yaml PHP 50
default_questions.yaml Fallback 1

The verifier matches each SARIF ruleId in three tiers — exact match → prefix/normalized → CWE map — falling back to default_questions.yaml only when none hits. See config/RULES.md § 7.


Project Structure

VulnHunterX/
├── src/vuln_hunter_x/
│   ├── cli/           # CLI commands
│   ├── codeql/        # Database creation, analysis, context extraction
│   ├── context/       # Heuristic + tree-sitter context extraction
│   ├── core/          # Types, config, constants
│   ├── llm/           # LLM client (LiteLLM) and prompt construction
│   ├── opengrep/      # OpenGrep integration
│   ├── questions/     # Guided-question loader
│   ├── reporting/     # Markdown report generation
│   ├── sarif/         # SARIF parsing
│   ├── semgrep/       # Semgrep integration
│   └── verification/  # Multi-turn verification engine
├── config/
│   ├── RULES.md                # Authoritative rule inventory
│   ├── rule_categories.yaml    # Profiles, categories, CWE map
│   ├── codeql-custom/          # Custom CodeQL queries (full profile)
│   ├── semgrep-custom/         # Custom Semgrep rules (full profile)
│   ├── prompts/                # Guided questions + system prompt
│   ├── queries/                # CodeQL context queries (per language)
│   ├── confirm_findings.yaml   # LLM and verification settings
│   └── repos.yaml              # Repository definitions
├── benchmarks/        # Evaluation framework — see benchmarks/README.md
├── examples/          # Per-language pipeline scripts
├── scripts/           # audit_rule_coverage.py, etc.
├── tests/
└── output/
    └── <lang>/<repo>/
        ├── database/              # CodeQL database
        ├── *.sarif                # SARIF results
        ├── context/               # Extracted CSVs
        └── verification_results/  # Verdict JSON + report.md

Development

pip install -e ".[dev]"
pytest tests/
ruff check src/
ruff format src/
mypy src/

See CONTRIBUTING.md and CHANGELOG.md.


Benchmark

VulnHunterX ships a standalone benchmark framework comparing the full pipeline against raw-sast and ablation baselines on six ground-truth datasets (SecLLMHolmes, Juliet C/C++, DiverseVul, OWASP BenchmarkJava, OWASP BenchmarkPython, RealVuln).

python benchmarks/scripts/run_benchmark.py \
    --dataset secllmholmes --approach all \
    --model gpt-4o-mini --limit 50

Full documentation — datasets, per-dataset playbooks, metrics, resume semantics — lives in benchmarks/README.md. The literature review and design-decision rationale are in benchmarks/RESEARCH.md.


License

MIT — see LICENSE.


References

About

A Python framework for combining static analysis with LLM-based bug verification

Topics

Resources

Contributing

Security policy

Stars

15 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages