- cli.py: --llm -> --review, param and output key updated - orchestrator.py: generate_llm -> generate_review, output .llm.md -> .review.md, import llm_exporter -> review_exporter - llm_exporter.py renamed to review_exporter.py; internal header and section titles updated to remove tool-name references - .gitignore: report.llm.md / *.llm.md -> report.review.md / *.review.md - README.md, demo/README.md: all --llm flags and report.llm.md references updated to --review and report.review.md
codeflow
Static taint-analysis and visualization tool for source code security review.
Traces user-controlled input from sources (HTTP parameters, headers, body, files) through the call graph to sinks (SQL queries, shell commands, file writes, deserializers, template engines) and produces an interactive HTML report, plus a Markdown review document for assisted triage.
Demo
VulnBank is an intentionally vulnerable Flask app covering the most common sink types. Run codeflow against it and open the report to see taint paths highlighted in the interactive graph.
pip install flask
python demo/app.py # start VulnBank at http://127.0.0.1:5001
python run.py analyze demo --output demo/report.html --review
See demo/README.md for exploitation hints and a full vulnerability table.
Supported languages
| Language | Support level | Frameworks detected |
|---|---|---|
| Python | Deep | Flask, FastAPI, Django (partial), Click, argparse |
| JavaScript / TypeScript | Deep | Express, Fastify, Koa |
| Java | Deep | Spring Boot (@RestController), raw Servlet |
| Go | Deep | net/http, Gin, Echo, Chi, gorilla/mux |
| C# | Deep | ASP.NET Core, ASP.NET MVC, WebAPI |
| Ruby, PHP, Rust, Kotlin, Swift | Structural | — (function/class graph, no taint) |
Deep = dedicated extractor with entry-point detection, source/sink patterns, taint propagation.
Structural = generic extractor — call graph and module structure only, no taint tracking.
What it detects
Sources (user-controlled input)
- URL path parameters (
/user/<id>) - Query string (
?q=…) - Request body — JSON (
request.get_json(),@RequestBody,req.body), form fields, file uploads, raw body - HTTP headers and cookies
- CLI arguments (
sys.argv,argparse,click,os.Args,flag.*) - Environment variables (
os.getenv,System.getenv,os.Getenv,Environment.GetEnvironmentVariable)
Sinks (dangerous operations)
| Sink type | Severity | Examples |
|---|---|---|
command_execution |
🔴 CRITICAL | subprocess.run, exec, os.system, exec.Command, Process.Start |
code_execution |
🔴 CRITICAL | eval, exec, compile, newInstance, Activator.CreateInstance |
template_injection |
🟠 HIGH | render_template_string, Template.render, .process(), Engine.Razor.Run |
database |
🟠 HIGH | execute, executeQuery, ExecuteNonQuery, db.Query, FromSqlRaw |
deserialization |
🟠 HIGH | pickle.loads, readObject, JsonConvert.DeserializeObject, json.Unmarshal |
file_write |
🟡 MEDIUM | open(…, 'w'), Files.write, File.WriteAllText, os.WriteFile |
file_read |
🟡 MEDIUM | open(…, 'r'), send_file, Files.readAllBytes, os.ReadFile |
http_request |
🔵 LOW | requests.get, http.Get, HttpClient.GetAsync |
Quick start
No virtual environment required. On first run, dependencies are installed automatically into a local vendor/ folder.
python run.py analyze <repo_path> --output report.html
Flags
| Flag | Default | Description |
|---|---|---|
--output / -o |
required | Path for the HTML report |
--review |
off | Also generate report.review.md — Markdown taint review document |
--burp |
off | Also generate report.burp.json — structured JSON of endpoints and taint paths |
--cluster-threshold |
5 | Collapse sibling nodes into clusters above this count (0 = off) |
--max-file-size |
5 | Skip files larger than N MB |
--verbose / -v |
off | Print per-phase progress |
Examples
# HTML report only
python run.py analyze ./target-app --output report.html
# HTML + taint review document
python run.py analyze ./target-app --output report.html --review
# HTML + Burp JSON
python run.py analyze ./target-app --output report.html --burp
# All three outputs
python run.py analyze ./target-app --output report.html --review --burp
Output files
report.html
Interactive single-file report. Features:
- Overview panel — module dependency graph, all flows at a glance
- Per-flow panels — one panel per entry point, showing the full taint graph
- Nodes colour-coded by type: 🟣 entry point, 🔴 tainted sink, 🟠 source, 🟡 function, 🔵 boundary
- Pan, zoom, drag
- Severity filter sidebar
- PDF-printable (SVG viewBox is recomputed on
beforeprint)
report.review.md
Markdown review document for assisted triage. Contains:
- Reviewer instructions explaining what codeflow can and cannot detect
- Summary table of tainted flows by severity
- Per-flow sections with:
- Sources table (param name, input type, file:line)
- Tainted sinks table
- One
⚠️ Taint pathblock per source→sink pair, with real code snippets (±3 lines) at every hop,>>>markers on focal lines - Explicit review question per path (TRUE POSITIVE / FALSE POSITIVE classification)
Use this file to review each taint path and dismiss false positives without sharing the entire codebase.
report.burp.json (opt-in)
Machine-readable JSON listing all endpoints, user-controlled parameters, sink node IDs, and taint paths as node-ID chains. Intended for custom tooling or Burp extension integration.
How taint works
- Source nodes are created for every place user input enters the code.
- Taint propagates forward through
CALLSandDATA_FLOWedges — every function that receives or passes tainted data becomes tainted. - Taint stops at sanitizer calls (
html.escape,shlex.quote, hashing functions, etc.). - When tainted data reaches a sink node the path is recorded as a flow and scored by severity.
Known limitations
- No conditional-branch awareness —
if x not in allowed: abort()does not stop taint. Validation guards will appear as false positives. - No type-coercion modelling —
int(user_input)eliminates injection risk in practice but taint still propagates. - No parameterised-query detection —
cursor.execute("SELECT … WHERE id = ?", (x,))is safe but flagged the same as string concatenation. - No inter-procedural data-flow inside structs/maps — field-level taint after
json.Unmarshal(&obj)is not tracked.
These limitations make the tool deliberately conservative — it over-reports and expects a human reviewer second pass to dismiss false positives.
Architecture
codeflow/
├── run.py # Self-bootstrapping launcher (no venv needed)
├── requirements.txt
├── codeflow/
│ ├── cli.py # Click CLI entry point
│ ├── orchestrator.py # Pipeline coordinator
│ ├── discovery/
│ │ ├── file_scanner.py # Recursive file discovery
│ │ └── language_detector.py # Extension → language mapping
│ ├── analysis/
│ │ ├── ast_parser.py # tree-sitter wrapper (all languages)
│ │ ├── base_extractor.py # Extractor base class + ExtractionResult
│ │ ├── python_extractor.py # Deep: Flask, FastAPI, Click, argparse
│ │ ├── js_extractor.py # Deep: Express, Fastify, Koa
│ │ ├── java_extractor.py # Deep: Spring Boot, Servlet
│ │ ├── go_extractor.py # Deep: net/http, Gin, Echo, Chi, gorilla/mux
│ │ ├── csharp_extractor.py # Deep: ASP.NET Core, MVC, WebAPI
│ │ ├── generic_extractor.py # Structural: all other languages
│ │ ├── graph_builder.py # Assembles CodeGraph from extractor results
│ │ ├── taint_tracker.py # Forward taint propagation
│ │ ├── boundary_marker.py # Promotes BOUNDARY → SINK for dangerous calls
│ │ ├── flow_identifier.py # BFS per entry point → Flow objects
│ │ ├── severity_scorer.py # Assigns CRITICAL/HIGH/MEDIUM/LOW/INFO
│ │ └── clusterer.py # Collapses sibling nodes above threshold
│ ├── models/
│ │ ├── node.py # Node, NodeType
│ │ ├── edge.py # Edge, EdgeType
│ │ ├── graph.py # CodeGraph (networkx wrapper)
│ │ └── flow.py # Flow dataclass
│ └── renderer/
│ ├── layout_engine.py # Sugiyama-style DAG layout
│ ├── svg_renderer.py # SVG generation (nodes, edges, lanes)
│ ├── html_builder.py # Packages SVGs into single-file HTML
│ ├── review_exporter.py # Markdown taint review document
│ └── burp_exporter.py # JSON endpoint + taint-path export
└── tests/
├── unit/ # Per-module unit tests
├── integration/ # Multi-file fixture tests
├── smoke/ # End-to-end smoke test
└── fixtures/ # Flask, Express, CLI sample apps
Corporate / offline use
run.py is self-bootstrapping:
- First run — installs dependencies into
vendor/usingpip --target(no admin rights, no venv) - Subsequent runs — skips bootstrap, loads from
vendor/directly - Proxy — set
CODEFLOW_PIP_ARGS=--proxy http://proxy.corp:8080before first run - Offline — copy
vendor/from a machine with internet access; subsequent runs need no network
# First run (needs internet once)
python run.py analyze ./app --output report.html
# All subsequent runs (instant, offline)
python run.py analyze ./app --output report.html --review
Dependencies
| Package | Version | Purpose |
|---|---|---|
tree-sitter |
0.21.3 | AST parsing core |
tree-sitter-languages |
1.10.2 | Pre-compiled grammars for all supported languages |
networkx |
3.3 | Graph data structure and path algorithms |
click |
8.1.7 | CLI framework |
jinja2 |
3.1.4 | HTML report templating |
pathspec |
0.12.1 | .gitignore-style file exclusion patterns |
