Lint-Owl logo

Open source, agent-native

Show me the path,
not the warning.

Lint-Owl is a static analyzer whose result is the data-flow path from an untrusted source to a dangerous sink. It proves that user input actually reaches the exploit, and shows the exact chain.

View on GitHub Try the demo

Run the taint engine yourself.

This box runs a small taint tracer in your browser, built from the same sources, sanitizers, and sinks as the real analyzer. Edit the code and press Scan to see the path it finds.

How to use this playground

Pick a language, load a preset with the buttons (or paste your own code), then press Scan. Each finding is shown as a source-to-sink chain: the line where untrusted input enters, the lines it flows through, and the dangerous sink it reaches. Try the Sanitized (safe) preset to see how a sanitizer clears taint and produces no finding.

runs entirely in your browser, same sources, sanitizers, and sinks as the CLI

A finding is a path, not a line number.

Most tools point at a line and say this looks risky. Lint-Owl follows the taint from where it enters to where it detonates.

command-injection
sourceline 4host = request.args.get("host")
flowsline 5cmd = "ping -c 1 " + host
sinkline 6os.system(cmd)

The same path, in your terminal.

The CLI prints each finding as a source-to-sink chain and exits non-zero, so it drops straight into CI or a pre-commit hook.

real CLI output. a known sanitizer (int, shlex.quote) on the path clears the taint and the finding disappears

How it differs

Linters

Flag local smells. No data flow across statements, no source-to-sink reasoning.

Semgrep

Matches syntactic patterns. A pattern does not prove the input actually reaches the sink through assignments.

CodeQL

A real dataflow engine, but heavy. Build a database, learn a query language. Overkill for one file.

Lint-Owl

One small binary whose output is the tainted path itself, and an MCP tool an AI asks "does user input reach this exec".

The classes it tracks.

Each class is just a set of sources, sinks, and sanitizers, so adding one is data, not code. Point --config at a JSON file to add your framework's own.

command injection · critical

Untrusted input reaches os.system, subprocess, os.popen, eval, exec, or child_process.exec.

SQL injection · critical

Concatenated input reaches a cursor.execute, executemany, or query call.

SSRF · high

Input reaches requests, urllib.urlopen, httpx, fetch, axios, or an http client.

path traversal · high

Input reaches open, fs.readFile, or fs.createReadStream without normalization.

insecure deserialization · critical

Input reaches pickle.loads, yaml.load, marshal.loads, or dill.loads.

sanitizer aware

int, float, shlex.quote, encodeURIComponent, and friends clear taint, so quoted or cast input is not flagged.

One Rust binary, every surface.

CLI

Scan a file or a whole tree of Python, JavaScript, and PHP. Exits non-zero on findings, so it drops into CI or a pre-commit hook.

SARIF and JSON

--sarif emits SARIF 2.1.0 for GitHub code scanning and IDEs. --json gives the raw findings for any other tool.

HTTP API and console

serve --port 8080 opens a paste-code console and a /scan endpoint that returns the same source-to-sink chains.

MCP server

The lint_owl_scan tool over stdio, so an agent reviewing a diff can ask "does user input reach a sink here" and get the chain back.

# one file, a whole repo, SARIF for CI, or an agent tool
lint-owl scan app.py
lint-owl scan path/to/repo --sarif
lint-owl serve --port 8080
lint-owl mcp

Sanitizer awareness and control-flow and inter-procedural taint are built in. Findings are still candidate paths a human confirms, and that honesty is written into the README.