## https://sploitus.com/exploit?id=80D74300-5816-5724-8F4F-DABBECC9A3A5
# vibesec-static β static vulnerability auditor for Python
A single-file, zero-dependency static scanner for Python source. Parses the
target (never executes it), finds known-dangerous sinks, and tags every finding
with the **closure tier** at which it actually stands β and it *never* claims a
finding is EXPLOITABLE.
Its distinguishing move is honesty about confidence: a decidable "the sink IS
here" (`eval(x)`, `verify=False`) is separated, by construction, from a
taint-model inference "a tainted value probably reached this sink" β because
false positives live in the second class and you should triage it differently.
```bash
python vibesec_static.py -v
# or, installed (pip install .): vibesec-static -v
```
## Honest status (Novelty Catechism applied to itself)
- **The technique DEFLATES-TO-KNOWN.** Static sink-pattern + intra-procedural
taint analysis is what Bandit / Semgrep / CodeQL already do. This re-implements
it. **No novelty of method is claimed** β it's a *trusted auditor you control*,
not a discovery.
- **What it adds is a discipline layer, not a mechanism:** every finding carries
its epistemic tier from the closure-tier ladder, and the tool *never*
says "EXPLOITABLE" β the hard wall (a PoC that fires, or a human) stays owed by
construction.
## Tiers (the contribution)
| Tier | Meaning | Example |
|------|---------|---------|
| **A-DIRECT** | Pattern-presence is a *decidable syntactic fact* β the dangerous sink literally is here. (Still A-FORMALIZED on whether it's *exploitable*.) | `eval(x)`, `verify=False`, `pickle.loads`, a hardcoded key |
| **A-FORMALIZED** | Rests on *our* static taint model (tainted value reached the sink along an over-approximated path) β a producer-authored MAP that may not match runtime. **False positives live here.** | tainted string into `cursor.execute(...)` |
| **BLOCKER** | File could not be parsed β not analyzed (reported, never silently passed). | syntax error |
## The recognition tags (shown next to each finding)
Every finding is annotated with *how* it was recognized β the tag you see in the output:
- **``** β sourceβsink reachability ("how is the safety property broken?") β the taint rules (cmd-injection, SQLi, path-traversal). These land in A-FORMALIZED.
- **``** β a known-bad sink is a one-step syntactic recognition (eval, deserialization, weak hash, TLS-off, secrets, debug). These land in A-DIRECT.
- **`[xN variants]`** β after a hit, siblings of the same rule in the file are counted, so a repeated sink class is one line, not N.
## Calibration (the tool's own A-direct self-check)
`python test_scanner.py` over `samples/`:
- **Recall:** 11/11 planted bug classes recovered on `samples/vulnerable.py`.
- **Precision:** 0 false positives on `samples/clean.py` (safe equivalents of all 11). The bound is asserted, not assumed β a regression makes a false positive visible.
- **Expand-to-class lock:** β₯3 PY.PATH write sinks caught, incl. the write-DESTINATION shapes (`os.rename`/`shutil.copy` where the tainted path is arg1, not arg0). Regressing the rule drops the count β visible failure.
- **Invariant:** no finding is ever tagged EXPLOITABLE.
### Expand-to-class fix (2026-07-17, from the setuptools-CVE realcode miss)
The 2026-06-25 realcode run false-negatived setuptools CVE-2025-47273 because
`PY.PATH` only knew `open()`. Fixed via the inverted-reduction-gate move β expand
the sink from one member to its **standard class**:
- **Suffixes** now cover `open`/`io.open`, `os.{rename,replace,remove,unlink,mkdir,makedirs,rmdir}`, `shutil.{copy,copy2,copyfile,move,rmtree}` β sinks whose positional args are *all* filesystem paths.
- **`taint_args="any"`** (new Rule field): the tainted path may be *any* positional arg, not just the first β the write DESTINATION (`shutil.copy(src, DST)`, `os.rename(tmp, DST)`) is not arg0. First-arg-only taint would have kept missing this class even after suffix expansion. Other rules keep `"first"` (preserves the parameterized-query safety of `PY.SQLI`).
**Still owed (named, not silently dropped):**
- `urlretrieve(URL, path)` β path is arg1 only, arg0 is a URL (not a path); needs arg-specific indices, not a blanket "any".
- `Path.write_text`/`write_bytes` β the path is the *receiver object*, not an argument; needs receiver taint.
- **Bespoke helpers** (setuptools' own `_download_to`) β the actual CVE sink β remain uncatchable without **interprocedural** analysis. Expand-to-class closes the STANDARD write sinks; it does not close project-specific ones. That is the next investment (tier #2), not this fix.
Two precision bugs were caught *during* calibration and fixed: a `shlex.quote()`
sanitizer the taint-walk didn't see through, and a parameterized query where the
scanner wrongly inspected the param-tuple instead of only the query string. Both
were textbook A-FORMALIZED translation-gap errors β exactly the tier the tool
flags as fallible.
## Audit + P0 hardening (2026-07-17)
A 5-arm blind audit (a multi-lens review with a planted-defect null control) ran on this tool;
the null-control arm caught both planted defects, so the instrument was validated. Verdict:
**validated first version with a defect list** β claims all conform, but the detection had real
gaps. It was later independently re-reviewed by a separate operator session. The **P0 batch is
fixed + test-locked**:
- **Never crashes the sweep:** a pathologically deep expression degrades to a BLOCKER; one hostile
file no longer aborts a directory scan.
- **No longer quadratic:** an `build_enclosing_map` (O(1) enclosing-fn lookups) replaced the per-call
and per-assignment re-walks; a 500-function module scans in # scan a file or tree
python vibesec_static.py --json # machine-readable
python vibesec_static.py --min-tier A-DIRECT # decidable findings only
python vibesec_static.py -v # verbose: coverage + per-finding WHY
python vibesec_static.py --no-color # force plain text
# exit code: non-zero if any A-DIRECT finding exists (CI gate on the decidable rung)
```
**`-v`/`--verbose`** prints a coverage line (how many `.py` files were scanned)
and, under each finding, the **WHY** β its epistemic basis: which argument was
tainted and via what expression (`arg[1] carries a tainted path -> dest`), or
which pure-presence rule matched. This makes the false-positive-prone
A-FORMALIZED tier *triageable* β you see exactly why each fired, so dismissing a
false positive is a decision, not a guess. The `why` is also in `--json`.
**Color = signal, not decoration.** In a terminal, findings are colored by TIER β
red A-DIRECT (the sink decidably IS present) / yellow A-FORMALIZED (rests on the
taint model, false positives live here) / blue BLOCKER (unanalyzable) β so the
epistemic confidence is legible at a glance. Auto-OFF when output is piped or
redirected (`--json`, `| grep`, `> file` never receive escape codes), when
`--no-color` is passed, or when the `NO_COLOR` env var is set (no-color.org).
Raw ANSI, zero-dependency; best-effort VT enable on Windows.
## Provenance & direction
Originated as a research-lab exploration (hence the tier vocabulary and the
strict "never claim EXPLOITABLE" discipline), then graduated to this repo once it
was audited and validated. Single-file by design so it drops straight into any
project or CI step.
The direction it's aimed at is **AI-/vibe-generated code** β the failure classes
that show up in machine-written Python β rather than trying to out-scan the large
autonomous auditors on hand-written code. The next real investment is
**interprocedural** analysis (project-specific helper sinks, which the current
intra-procedural pass cannot follow); see the "Still owed" notes above.