Repository navigation
Rules
Findings carry a rule id, a severity (critical, high, medium, low) and a
confidence (high, medium, low).
Reported when attacker-controlled data reaches a sensitive call without passing through a sanitizer for that kind of sink.
| Rule | Reached sink |
|---|---|
command-injection |
Shell or process execution (os.system, subprocess with a string, …) |
sql-injection |
A database statement (cursor.execute, SQLAlchemy text, Django raw, …). Parameters of a parameterised query are not statements. |
path-traversal |
A file system path (open, send_file, os.remove, …) |
ssrf |
The URL of an HTTP client request (Requests, httpx, urllib), by position or as url=. The body, headers and query parameters are not the destination. |
xss |
An HTTP response body built without escaping |
insecure-deserialization |
pickle, marshal, dill, jsonpickle, shelve, and the unsafe YAML loaders. yaml.load/load_all given SafeLoader or BaseLoader are not. |
open-redirect |
An HTTP redirect target |
plaintext-credential-storage |
A parameter named like a password stored in a database without hashing. Medium confidence, since a name is a hint. |
exploitable-vulnerability |
An API affected by an advisory of a vulnerable requirement — see Dependencies and Advisories |
Every flow is judged before it is reported: see How a Flow is Judged.
The sanitizer may sit inside a project function the data goes through — a helper that escapes
before it answers, a view that redirects to reverse(...). It then protects every caller, on
every path through the function where it is called.
Function summaries keep, for each parameter, the taint kinds cleared on every path from it to an external call or to the return value. A path that skips the sanitizer, or a kind it does not clear, still reaches the sink.
What a function stores into an object or a nonlocal variable is not treated that way yet.
render_to_string(name, context) returns HTML in which autoescaping escaped every variable, so
data reaching a response through it is not xss — if the template really escapes. On a
directory check the analyzer reads the project's templates to decide.
It establishes escaping only when all of these hold:
- it finds every template of that name under a
templatesdirectory, by the path Django uses (app/page.html), and everything those templates extend or include; - none marks output safe (
|safe,|safeseq,{% autoescape off %}); - none uses a tag or filter beyond Django's own, or loads a library beyond its built-ins
(
static,i18n,l10n,tz,cache,humanize); - none places a variable where HTML escaping does not protect it: a
<script>or<style>block, an event handler (onclick="…"), astyleattribute, a URL attribute not fixed by a relative orhttp(s)://host/prefix, an unquoted attribute, or an attribute name; - no Python file of the project sets
autoescapetoFalse.
Escaping is never assumed where the template cannot be read. A template named by an
expression, one found through a DIRS entry not named templates, one shipped by an installed
package, one rendered through get_template, and Flask's Jinja2 templates all leave the flow
reported as before. A single-file check reads no template.
Django's csrf.get_token returns a token of letters and digits whatever the request holds, so it
is no injection. reverse/reverse_lazy build a local path that attacker data in their
arguments or query cannot turn into another host, so they are no open redirect — but they still
carry other kinds, since reverse leaves ' unquoted.
| Rule | Reported call |
|---|---|
dangerous-eval |
eval or exec, whatever name the file gives them |
weak-crypto |
hashlib.md5, hashlib.sha1 and other broken algorithms |
debug-enabled |
app.run(debug=True) on a Flask application |
missing-timeout |
A Requests or httpx call without a timeout
|
insecure-tls |
verify=False on a Requests or httpx call, or ssl._create_unverified_context()
|
unsafe-xml |
A standard-library or lxml XML parser, which expands entities; defusedxml is the safe replacement |
unsafe-archive-extraction |
tarfile/zipfile extraction or shutil.unpack_archive without a member filter
|
insecure-temp-file |
tempfile.mktemp, which names a file without creating it |
weak-random |
A random function whose result is assigned to a credential-like name, or produced by a function named like one. Medium confidence. |
One finding per literal, with a redacted preview and never the secret itself, in Python sources and in configuration files.
| Rule | Confidence | Trigger |
|---|---|---|
hardcoded-secret |
high | A provider-specific format: AWS, GitHub, Slack, Stripe, Google, private keys, JWTs, SendGrid, Twilio |
hardcoded-credential |
medium | A credential-like name (password, token, api_key, app.config['SECRET_KEY'], …) bound to a real value. Placeholders are excluded; low confidence in test code, fixtures and files such as .env.example, with the context in the metadata. |
high-entropy-string |
low | An opaque high-entropy token. Hex digests (MD5/SHA lengths, or a name such as hash, checksum, commit), subresource-integrity hashes (sha512-…) and character-set constants are not secrets unless a credential name says so. |
| Rule | Trigger |
|---|---|
vulnerable-dependency |
A requirement allows a version affected by an advisory, reported at its line |
reachable-vulnerability |
A call in the project to an API the advisory affects, or a read of an entry point marked read
|
exploitable-vulnerability |
Attacker-controlled data reaches such a call. Critical. |
denied-dependency |
A package the policy denies |
unpinned-dependency |
A requirement without an exact pin when the policy requires pins |
See Dependencies and Advisories.
| Rule | Meaning |
|---|---|
unsupported-syntax |
A function used syntax outside the supported subset; the rest of the file was still analysed |
syntax-error |
Python itself could not parse the file |
Read them together with the coverage line before concluding a project is clean.