Skip to main content

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.

LanguageCross-fileRule IDsFramework sources
JavaScript / TypeScriptYesJS-TAINT-*Express, Fastify, Koa, NestJS, Hapi, Next.js
PythonYesPY-TAINT-*Flask, Django, DRF, FastAPI, Starlette, aiohttp, Tornado, Litestar
GoYesGO-TAINT-*net/http, Gin, Echo, Chi, Fiber, gorilla/mux
JavaYes, by the receiver's declared typeJAVA-TAINT-*Spring MVC / WebFlux, servlets, JAX-RS
PHP, C, C++No, within one functionPHP request superglobals and framework request objects

Sources, sinks, sanitisers​

Examples
Sourcesreq.body, req.query, req.params; FastAPI request.json(); flask.request.*; Django request.GET; route handler parameters
SinksSQL (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
SanitisersParameterised 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 db is 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