Share
## 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.