Skip to content

Land the Python tool-native probe row and complete wave N1 - #86

Merged
DavidBakerEffendi merged 4 commits into
mainfrom
dave/native-python
Aug 27, 2026
Merged

Land the Python tool-native probe row and complete wave N1#86
DavidBakerEffendi merged 4 commits into
mainfrom
dave/native-python

Conversation

@DavidBakerEffendi

Copy link
Copy Markdown
Collaborator

Summary

Closes #16. The third wave-N1 language, completing the tool-native profile for Java/JavaScript/Python.

Adapter Scored Result
CodeQL 2.26.3 12 (6/6) 10/12 — every positive found; the two FPs are the preregistered hazards (shlex.quote credit-scoping — confirmed in sharper form: the one crediting query family never fires on this sink — and the keyless env persistence source)
Semgrep CE 1.174.0 12 (6/6 via Amendment A8) 8/12 — every positive found; FPs split between pure sink-existence (the doc's predicted most-likely observation) and keyless/sanitizer-less taint rules
Bifrost / Joern 0 preregistered unsupported, uninvoked (verified with a nonexistent-binary probe)
  • Amendment A8: rule-text evaluation of the 91 vendored Python rules promoted all six Semgrep cells — one shipped audit rule binds os.environ/sys.argvos.system, falsifying the framework-shaped generalization for exactly this language; the cross-language asymmetry (Python 6/6, JS/Java 0/6) is itself the published product fact. Introduces the language-dimension partition mechanism (NATIVE_PARTITION_AMENDMENTS) the per-language snapshots genuinely require.
  • Semantic ruling resolved by the doc, with no code change: the reported away-from-anchor divergence was a mis-description — the preregistered outcome-honesty section, main's reconciler, and this row's implementation all score activated-tool coverage misses as not-reached, reserving inconclusive for unreadable/ambiguous locations. JS/Java reports untouched; Python's re-runs under the unified arm are byte-identical in outcomes and diagnostics.
  • One unified CodeQL + Semgrep native arm through native_anchor_tally_outcome; --codeql-packs forwarding removed per the activation contract.

Issue #16's acceptance criteria are met: native coverage recorded without modifying canonical fixtures ✓ · separate reports/scorecards ✓ · model/version provenance and activation configuration retained (vendored snapshots with per-file digests; suite+threat-model pinned) ✓ · no aggregate combines native coverage with controlled accuracy ✓.

Validation

cargo fmt --check · cargo test (162) · validate (852 cases) · validate-reports (66) · validate-freeze · generate-results --check.

🤖 Generated with Claude Code

DavidBakerEffendi and others added 4 commits August 27, 2026 16:06
Wave N1's Python population for the tool-native model profile
(docs/native-profile.md): twelve fixtures over real CPython identities, the
vendored Semgrep activation snapshot, and the runner arms that invoke an
analyzer over a scored native cell.

The fixtures are `cases/taint/python/native-<short>-{positive,negative}/`,
`score_tier: "modeling"`, `model_profile: "tool-native"`, provenance revision
`n1-native-python`, and no `tool_model_references` at all — a native run loads
only models the vendor ships, and the no-benchmark-models gate enforces it.
Every endpoint is a real platform identity by its real module path
(`os.environ`, `os.system`, `os.path.join`, `shlex.quote`,
`base64.b64encode`/`b64decode`, `sys.argv`), so a shipped model has something
to bind to rather than a same-named local stand-in. Each negative keeps the
sink present and identical, so a rule that fires on sink existence alone takes
a false positive rather than an unearned true negative.

Native anchoring differs from every benchmark-controlled population by
construction: a native fixture declares no endpoint, so a marker sits on the
real platform-API callsite and reconciliation binds a finding to that line.
An anchor still only decides which finding belongs to which assertion; it
never tells an analyzer what a sink is.

The partition gains a language dimension. It was preregistered
language-agnostically, before any snapshot existed, but a vendored snapshot is
per language and can only answer that language's cells — so the lookup now
takes a language and an additive, dated amendment table sits in front of it.
No amendment is recorded by this commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two changes, both dated 2026-08-27 and both landing as their own commit per
docs/native-profile.md#preregistration-and-immutability.

The partition is now keyed by tool x language x template. It was preregistered
language-agnostically, which was right while every Semgrep cell read "to be
verified at vendoring" — no snapshot existed for any language. A vendored
snapshot is per language by this document's own rule, so verifying Python's
cells cannot speak for Java's or JavaScript's, and a partition that could not
say so would force one language's evidence onto the other two. No preregistered
cell's decision changes; the twenty-four cells stay the default for every
language with no amendment row.

Semgrep CE 1.174.0 x Python is promoted from 0/6 to 6/6, from rule text over
the pinned snapshot and before any scan of the Python population. The
preregistered rationale was that the upstream taint rules bind their sources to
framework endpoints, which is true of dangerous-system-call.yaml and false of
audit/dangerous-system-call-tainted-env-args.yaml: that rule's pattern-sources
are os.environ, os.getenv, sys.argv and its pattern-sinks are os.system and the
os.popen family, so one shipped rule binds both endpoints of all six Python
templates. What remains is the measurement, not the activation.

Java and JavaScript are untouched. No published freeze is invalidated: no
freeze manifest contains a tool-native report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The first tool-native results: four reports, twelve assertions each, with raw
evidence retained per case. Against the blind-pair baseline of six of twelve —
a tool that has never heard of os.system answers "no flow" on both cells and
banks a free true negative per pair — CodeQL decides ten and Semgrep CE eight.
Bifrost and Joern retain twelve preregistered unsupported decisions each,
without the binary being invoked at all; verified by running each with a
nonexistent path and getting byte-identical evidence.

Both activated tools found every positive. Every error either made is a false
positive on a negative, which is a different product fact from a coverage gap,
which is why the true-positive and false-positive rates are published together
and neither alone.

CodeQL's two false positives are the two hazards the preregistration named in
advance and are now measured rather than predicted: shlex.quote is a barrier
only for py/shell-command-constructed-from-input, which does not own the
os.system sink, so py/command-line-injection reports the sanitized negative;
and os.environ is itself a shipped source, so the persistence negative's
distinct key is never looked at. One query decided the whole column.

Semgrep's four split two ways. Categories P and O are pure sink-existence
findings from audit/dangerous-system-call-audit, a pattern rule with no taint
in it whose only exclusion is a literal first argument — the single most likely
observation this profile preregistered about any tool. Categories Z and B are
the taint rule itself, which declares no pattern-sanitizers and whose
os.environ source matches a store read whatever key is subscripted. S and E
earn their true negatives because symbolic propagation resolves the clean local
back to a literal and the audit rule's own exclusion catches it.

Nothing was tuned. No fixture was adjusted and no vendored rule was edited to
make a shipped model fire.

No frozen report or pre-existing case is touched; these are new paths, and no
freeze manifest contains a tool-native report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lands Python's tool-native row on top of the JavaScript and Java rows,
completing wave N1 and issue #16. Three things had to be reconciled.

**The semantic question, settled against the document.** The two branches
appeared to disagree on what an activated tool's empty suite scores. The
preregistration decides it, and it was written before either row:
"A coverage miss by an activated model set is neither — it is a plain
`not-reached`, which on a positive cell is a false negative and is exactly
the number this profile is built to publish", with `inconclusive` reserved
for "the shipped models were activated and the analysis did not complete"
(docs/native-profile.md#outcome-honesty).

Read against main's actual reconciler, the disagreement was smaller than
reported: `native_sarif_anchor_outcome` already returned `not-reached` for
zero findings and for findings only away from the anchor, and reserved
`inconclusive` for unreadable or doubly-matched locations. That is the
document's reading and Python's reading. No reconciler was changed, so no
JavaScript or Java report was re-run and no published row drifts.

**One arm.** Main's unified implementation wins and Python's parallel
reconciler is dropped: `NativeFindingLocation`, `native_sink_callsites`,
`native_location_matches`, and `native_evidence_outcome` are gone, along
with the Python arm's `--codeql-packs` forwarding, which the activation
contract forbids. The Semgrep arm Amendment A8 requires is ported onto
main's `SinkAnchorLocation` and `SarifAnchorMatch`. The two arms differ
only in how they extract a location from their evidence shape; both tally
through one new `native_anchor_tally_outcome`, so they cannot drift into
two readings of the outcome vocabulary.

**The language-dimension partition** is kept — a vendored snapshot is per
language, so one language's rule text can only answer its own cells — and
ported onto main's shapes. JavaScript and Java Semgrep stay 0/6 with their
A6 and A7 rationales untouched.

Python's amendment is renumbered from "N-A1" into the repository's
cross-document monotonic sequence as **A8**, with every cross-reference and
anchor updated.

Re-ran both Python arms that the unified code path touches. No drift:
configuration hashes unchanged (CodeQL
73de6c6787622ca988d0b4f6be9a972ece7e19b42c70964aa48960133d19e15d, Semgrep
e6b4975cdf103c322e96d48de82f2098dfecc1a9fcd85151ff471190f825b335), and
every outcome, classification, and diagnostic identical. CodeQL 10/12,
Semgrep CE 8/12, Bifrost and Joern 0/6 uninvoked. Python's provenance uses
the nested layout and satisfies main's `fact()` reader unchanged; the
flagged JavaScript-flat vs Java-nested divergence is left to its own change.

cargo fmt --check clean, 162 tests pass, 852 cases validate, 66 reports
validate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@DavidBakerEffendi
DavidBakerEffendi merged commit 9661394 into main Aug 27, 2026
2 checks passed
@DavidBakerEffendi
DavidBakerEffendi deleted the dave/native-python branch August 27, 2026 15:18
DavidBakerEffendi added a commit that referenced this pull request Aug 27, 2026
…#87)

Pre-freeze corrections ahead of the v0.10.8 re-pin and v0.5.0 freeze.
Nothing here binds a Bifrost version: the committed pin stays v0.10.6,
no report bytes change (the only additions under `reports/` are the 15
new Amendment A9 probe evidence files), and the v0.4.0 freeze manifest
verifies green.

## 1. Witness the tool identity instead of asserting it

Both the Bifrost modeling adapter and the Bifrost tool-native adapter
published a hardcoded "Bifrost v0.10.6" identity they never read — the
native adapter never invoked the binary at all (its row declines all six
templates). Any re-pin would have silently published a false version
into every declined cell's retained rationale.

Now both adapters witness identity from the pinned binary once per run,
**before** the population is walked, so a run whose every cell is
declined still records a real identity. Retained decision documents
rename `pinned_tool_identity` → `witnessed_tool_identity`; the native
report carries the witnessed build identity; tests assert every retained
rationale names a measured identity. Per-cell decline semantics are
unchanged (no cell ever reaches the analyzer).

> **Deliberate reversal of a rule #86 shipped:** #86's prose said a 0/6
native row is "never invoked at all — not even for its version banner",
and `docs/python-native.md` verified that by running against a
nonexistent binary path. This PR overturns the run-level half of that
rule (the cell-level half stands): the run-level identity must be real,
so a 0/6 run now *fails* against a nonexistent binary.
`docs/native-profile.md` gains an outcome-honesty subsection stating
this; `docs/python-native.md` is corrected where it stood.

## 2. Amendment A9 — Bifrost's modeling category Z promoted to scored

The Bifrost partition marked category Z (sanitizer declarations)
unsupported on the adapter README's claim that sanitizer lowering was a
future CLI capability. Measured false on Bifrost v0.10.7 (build
`44d9a5be`): the RQLP `analysis` grammar accepts a `(sanitizer :id …
:selector … :input … :output … :removes […])` stanza, and a load-bearing
probe on the committed fixtures in **all three modeling languages**
shows

- the declared sanitizer suppresses the kill-negative (run completes,
not vacuous),
- removing the stanza restores the flow with a full witness,
- an undeclared sanitizer-shaped call does not suppress (selectivity,
both directions).

Evidence retained under `reports/raw/amendment-a9-bifrost-sanitizer/`
(15 files), reproducible via
`scripts/probe-bifrost-sanitizer-lowering.sh`. Bifrost moves from 2 to 4
scored modeling templates; P/O/E/B stay unsupported — with the record
corrected to note `:transforms`/`:external-models` are
grammar-*accepted* but their lowering is unshown, and acceptance is not
lowering per the preregistration's own rule. `MODELING_PARTITION`, the
count tests, the artifact-declares-only-scored-categories invariant (now
*requires* `:sanitizers`), and the three `model-*.rqlp` artifacts
updated accordingly. The JS/Java modeling rows' quoted configuration
hashes are labelled as describing the pre-A9 run, re-run pending at the
v0.10.8 re-pin.

Amendment sequence stays monotonic and cross-document: A1 (challenge
tier), A2–A5 + A9 (modeling matrix), A6–A8 (native profile); both
amendment preambles now state the interleaving.

## Deferred to the v0.10.8 re-pin PR

- The doc re-pin itself and the full 66-report re-run (the v0.5.0 freeze
gates on Bifrost v0.10.8).
- `docs/native-profile.md`'s category-Z rationale still quotes the
README sentence A9 retired; the cell's outcome is unaffected (the CLI
ships no endpoint catalog), and correcting the wording is a dated
tool-native amendment left for the re-pin PR.

## Verification

`cargo fmt --check` clean · `cargo test` 162 passed · `validate` 852
cases · `validate-reports` 66 reports · `generate-results --check`
current · `validate-freeze` green (v0.4.0 manifest untouched). The A9
probe re-run post-rebase is byte-identical to the retained evidence.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Define and implement separately scored tool-native profiles

1 participant