Repository navigation
How a Flow is Judged
Finding that attacker data reaches a sink is the easy half. The analyzer then asks what the code does to that value on the way, and reports one of three verdicts. The verdict and its evidence are in the finding's metadata.
| Verdict | Result |
|---|---|
| Refuted | Nothing is reported |
| Hotspot | Reported at medium confidence |
| Vulnerability | Reported at the rule's severity |
A dominating guard that establishes safety refutes the flow:
isdigit()- membership in a constant collection
- equality with a constant
- a numeric value proven by
int(),len()or bounded arithmetic - a validator such as
re.fullmatch
A Validator proves its argument safe only for the kinds it declares. A validator declared
for REDIRECT does not refute a command injection through the same value: that flow becomes a
hotspot, and is refuted only when the validator covers every kind reaching the sink.
A Validator may also name a function of the project by its project symbol
(python.hc.accounts.views._allow_redirect), and the refutation recognises it in its own module
too, where a call to it has no imported symbol. A project can therefore declare its own
validation helpers — see Plugins and Models.
Two things lower a flow to a hotspot:
-
A guard that examines the data without proving it safe. A test of the value itself
(
if cmd:,len(cmd) < 10), of the same attribute, or of a method called on it (form.is_valid()). -
An authorization decorator such as
login_required.
if request.method == 'POST':
os.system(request.POST['cmd']) # still a vulnerabilityThe guard reads another attribute of the object the data comes from, so it says nothing about
cmd. This is deliberate: treating it as a guard would quietly downgrade real findings.
When the flow passes the object whole — to the sink, or to a function — a guard on any of its attributes does count.
Each taint flow records how its value is passed to the sink, and an advisory entry point may
name the attacker_arguments through which attacker input actually exploits it:
- by keyword;
- by position, when the argument may be passed positionally — a variadic one taking every later position.
Attacker input in another argument leaves the call reachable rather than exploitable. An
advisory for send_from_directory naming its path no longer counts download_name=; one for
requests.get naming its url no longer counts the body.
An argument unpacked with * or ** may fill any of them. Like a condition the analyzer cannot
decide, it does not rule the call out — so a wrapper forwarding *args, **kwargs still reaches
the entry point.
An entry point naming no argument counts every argument.
data = yaml.load(text) # coretrace: ignore[insecure-deserialization]A bare # coretrace: ignore suppresses every rule on that line; with a list, only those. It also
works in requirements files:
pyyaml==5.3.1 # coretrace: ignore[vulnerable-dependency]
Suppressed findings are kept apart, not erased: the text report counts them, the JSON report
lists them under suppressed, the SARIF log marks them suppressed in source, and they never
affect the exit status.