Taint analysis
Taint analysis follows data from outside (a request parameter, a form field, a header) through your code, and reports it when it reaches a risky operation such as a SQL query or shell command without being checked. Terms are defined in the glossary.
vulkro scan . # taint runs in every scan
vulkro prove . # turn proven flows into runnable test cases
How it works
Vulkro traces within a function, across functions in a file, and across files along the project's call graph. A finding with a full trace carries every hop from source to sink (SARIF codeFlows) and is marked Proven.
| Language | Cross-file | Rule IDs | Framework sources |
|---|---|---|---|
| JavaScript / TypeScript | Yes | JS-TAINT-* | Express, Fastify, Koa, NestJS, Hapi, Next.js |
| Python | Yes | PY-TAINT-* | Flask, Django, DRF, FastAPI, Starlette, aiohttp, Tornado, Litestar |
| Go | Yes | GO-TAINT-* | net/http, Gin, Echo, Chi, Fiber, gorilla/mux |
| Java | Yes, by the receiver's declared type | JAVA-TAINT-* | Spring MVC / WebFlux, servlets, JAX-RS |
| PHP, C, C++ | No, within one function | PHP request superglobals and framework request objects |
Sources, sinks, sanitisers
| Examples | |
|---|---|
| Sources | req.body, req.query, req.params; FastAPI request.json(); flask.request.*; Django request.GET; route handler parameters |
| Sinks | SQL (db.query, cursor.execute), commands (child_process.exec, subprocess.run(shell=True)), code (eval, pickle.loads, unsafe yaml.load), SSRF (requests.get, fetch), open redirect, template injection, __proto__ writes |
| Sanitisers | Parameterised queries, html.escape, bleach.clean, allowlist enums and Pydantic or Zod schemas, urllib.parse.quote |
Cross-file example
# routes/admin.py
@router.post("/admin/export")
def export(req):
data = req.json() # source
return run_export(data["filter"])
# services/exports.py
def run_export(filter_expr: str):
return db.execute(f"SELECT * FROM logs WHERE {filter_expr}") # sink
The source and sink are in different files. Vulkro follows the call from export to run_export and reports the SQL injection with both hops.
Limits
- No full type inference: sources and sinks are matched by syntax, so a variable named
dbis treated as a database client. - Writes into containers (
obj.x = tainted,arr[0] = tainted,Object.assign) do not carry taint. - Cross-file depth is bounded to keep large repositories fast.
- Java follows a call only when it can bind it to one implementation.
- Salesforce has its own taint engine in
vulkro-sf.
If cross-file taint blocks a build after an upgrade, VULKRO_DISABLE_INTERPROC_TAINT=1 skips only the cross-file pass.
See also: Attack paths, Bruteforce sinks