Skip to content

Land the JavaScript tool-native probe row - #84

Merged
DavidBakerEffendi merged 5 commits into
mainfrom
dave/native-javascript
Aug 27, 2026
Merged

Land the JavaScript tool-native probe row#84
DavidBakerEffendi merged 5 commits into
mainfrom
dave/native-javascript

Conversation

@DavidBakerEffendi

Copy link
Copy Markdown
Collaborator

Summary

Wave N1's first language: JavaScript's 12-assertion tool-native population over real platform APIs, the vendored Semgrep snapshot, the CodeQL native execution arm, and four runs.

Adapter Scored Result
CodeQL 2.26.3 (security-extended + threat-model local) 12 (6/6) 10/12 — both misses preregistered: the encodeURIComponent sanitizer-scoping FP (barrier for XSS, taint-preserving for command injection — confirmed exactly) and the persistence pair (unlinked env write/read, keyless source)
Bifrost 0.10.6 / Joern 4.0.610 / Semgrep CE 0 12 preregistered unsupported each, verifiably emitted without invocation
  • Amendment (Semgrep vendored-rule evaluation, from rule text before any run): 30 rules vendored at semgrep-rules@40b8c63f with derived provenance; all fifteen taint-mode rules root their sources in function parameters or framework request objects — process.env, process.argv, encodeURIComponent, Buffer.from appear nowhere — so all six cells stay unsupported, zero promotions, and the preregistered sink-existence hazard is recorded as not materializing for JS.
  • CodeQL native arm implemented (the infra's staged stub): whole-suite analysis with markers anchoring the platform callsite; away-from-anchor findings are diagnostics by rule id, never flow evidence; ambiguity stays inconclusive.
  • Provenance revision follows the doc's pinned n1-native-javascript spelling.

Validation

cargo fmt --check · cargo test (159) · validate (828 cases) · validate-reports (58) · validate-freeze · generate-results --check.

🤖 Generated with Claude Code

DavidBakerEffendi and others added 5 commits August 27, 2026 15:32
Adds docs/native-profile.md, the preregistration artifact for the
`tool-native` model profile, plus the runner infrastructure. No fixture,
no vendored ruleset, and no run lands here; the corpus is unchanged.

The profile measures the product rather than the engine: the sources,
sinks, sanitizers, summaries, and entry points each tool ships and
activates by default or by its own documented native configuration. Six
templates over real platform APIs, one per category of the
benchmark-controlled matrix, so the two can be read side by side. It
shares the `modeling` score tier and is separated by `model_profile`
alone, with a corpus-wide check that the two populations never
cross-select — the fault it guards is a selector that filters on the tier
and forgets the profile, which no assertion about a case's own fields
would catch.

The document records the native-binding trap (fixtures must name real
platform APIs by their real identities, or model binding is silently
defeated), why the canonical kernels are not run natively, and how a
finding that fires on sink existence alone is scored: on the cell it
lands in, false positives included.

Preregistered activation partition, with its *to be verified* cells
recorded as unsupported per its own rule: CodeQL 6 of 6, pinned to the
shipped `security-extended` suites of codeql/{java,javascript,python}-queries
with `--threat-model local`; Bifrost, Joern, and Semgrep CE 0 of 6, each
with a verified rationale — no shipped endpoint catalog in the standalone
policy CLI (bifrost-dev #2620, #2691), `DefaultSemantics` shipping flow
constraints but no source or sink catalog with `joern-scan`'s query
database downloaded rather than shipped, and a Semgrep ruleset that is
unpinnable at run time until a snapshot is vendored.

Infrastructure: NATIVE_TEMPLATE_IDS and the category mapping,
NATIVE_PARTITION's twenty-four cells consulted by template identity
before any invocation, profile-disjoint validators, four
`run-<tool>-native --language` commands, fail-fast on an empty population
or a missing pinned activation artifact, and the no-benchmark-models gate
over activation shapes pinned literally by tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Semgrep registry configurations are network-fetched and unpinnable at run
time: two runs a week apart are two different rulesets under one name, which
is not a benchmark. docs/native-profile.md#semgrep-ce--11740---oss-only
therefore requires a vendored snapshot, and this commit takes JavaScript's.

Thirty `.yaml` rule files copied byte for byte from
`javascript/lang/security/` at semgrep/semgrep-rules
`40b8c63f75dc7c22c8a77482d73bfb864b146f7e` — the commit the preregistration
names — with the upstream directory structure preserved and nothing filtered,
edited, reordered, or added. The co-located `.js`/`.ts` rule-test fixtures are
not part of the ruleset and are not vendored.

`provenance.json` records `kind: derived` with the upstream repository, the
source commit and its date, the upstream paths, the license, the retrieval
date, and the activation shape — per
docs/native-profile.md#provenance-for-vendored-activation-artifacts, which
makes a snapshot with no recorded source commit not a snapshot and has the
runner refuse a native run over one. A snapshot is an activation artifact, not
a fixture: it never claims `authored` provenance.

No cell is promoted, no fixture lands, and no run is recorded here. The
per-cell evaluation of this snapshot is a separate commit, as the amendment
contract requires.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…orted

Resolves the six *to be verified at vendoring* cells for JavaScript against
the snapshot vendored in the previous commit. All six are retained as
unsupported; no decision flips and the partition summary is unchanged. What
changes is the rationale: it is now settled evidence about a pinned snapshot
rather than an open question about one that did not exist.

One finding decides all six. Fifteen of the thirty vendored rules are
`mode: taint`, and every one of them roots its `pattern-sources` in a function
parameter or a framework request object. The two rules whose sinks are exactly
this profile's `child_process.execSync` — `detect-child-process.yaml` and
`audit/dangerous-spawn-shell.yaml` — both take an enclosing function's
parameter as their only source; `audit/code-string-concat.yaml`, same sink
family, takes Express `$REQ.*` and the Next.js router; the `path.join` rule
takes a function parameter. `process.env`, `process.argv`,
`encodeURIComponent`, and `Buffer.from` appear nowhere in the snapshot. A rule
with a correct sink and no applicable source cannot fire on a fixture whose
source is a platform API.

The evaluation is a reading of the vendored rule text, made before Semgrep was
invoked over any native fixture, which is the order the vendoring rule
requires. It invalidates no freeze: no published freeze contains a tool-native
report.

Also recorded: the preregistered sink-existence hazard does not materialize for
JavaScript. The two rules matching `execSync` are taint rules, and the fifteen
pattern rules match other constructs entirely, so a negative cell passing a
clean local to `execSync` is flagged by none of them. The hazard stays
preregistered for the other two languages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wave N1's first row. Twelve fixtures over real Node and ECMAScript APIs, the
CodeQL native execution arm, and all four `run-<tool>-native --language
javascript` runs.

| Adapter | Scored | Correct | unsupported |
|---|---|---|---|
| Bifrost v0.10.6 | 0 | — | 12 |
| CodeQL 2.26.3 | 12 (all six) | **10 / 12** | 0 |
| Joern 4.0.610 | 0 | — | 12 |
| Semgrep CE 1.174.0 | 0 | — | 12 (Amendment N1) |

This profile measures the product, not the engine. A miss says the shipped
model set does not cover an API; it says nothing about whether the engine could
have followed the flow had it been told, which is the opposite of what a miss
means in the benchmark-controlled matrix. Read 10/12 against the 50% blind
baseline, never against zero, and never pooled with the JavaScript modeling
row's 24/24 — the two even run on deliberately different CodeQL library
resolutions, `javascript-all@2.10.0` here against the adapter's `2.9.0` there.

**Fixtures.** One `probe.js` each, `node --check` clean, Node and the standard
library only — no dependency, no manifest, no build step. Real platform
identities per the preregistration, with the pinned bare module specifiers
`require("child_process")` and `require("path")`: `process.env.X` →
`child_process.execSync`, `path.join`, `encodeURIComponent`, the
`Buffer.from(...).toString` base64 round trip, `process.argv[2]`, and a
`process.env` write/read store pair. `tool_model_references` is empty on all
twelve, and the no-benchmark-models gate enforces that no run can load one.

Every negative keeps the sink present and identical, so a rule that fires on
sink existence alone cannot bank a true negative it did not earn — and, where
the shape allows, keeps the source read present and unused too.

**Anchors sit on the platform callsite.** This profile has no declared entity by
construction: the sink's body is inside Node. `native_sink_anchor_locations`
resolves the marker line itself as the callsite, and a test asserts all twelve
resolve to a line containing `execSync(`. An anchor still decides only which
finding belongs to which assertion.

**CodeQL's arm.** `run_codeql_native_case` builds the database exactly as the
benchmark-controlled runner does — extraction is a property of the language,
not of the model profile — then analyzes with the pinned activation arguments
passed verbatim, in the order `native_activation` pins and
`native_configuration_hash` hashes, so invocation and retained provenance
cannot drift apart. A native run analyzes a whole shipped suite (103 rules
resolved) rather than one adapter query, so `native_sarif_outcome` retains a
finding away from the anchor as a diagnostic and never lets it become evidence
of a flow; ambiguity stays `inconclusive`. In this run no such finding arose.

Bifrost, Joern, and Semgrep have no arm and were never launched: their
partitions decline all six templates, the partition is consulted first from the
template identity, and each of their thirty-six assertions retains the
preregistration's rationale verbatim as
`"evidence_kind": "retained-capability-decision"`.

**The two CodeQL mismatches, both preregistered as expectations before the
run.**

*Template 3 negative, false positive.* `encodeURIComponent` is a barrier for
XSS (`Xss.qll`) and request forgery, while `TaintTracking.qll` lists it among
the taint-preserving steps for everything else — and command injection is
everything else. A publishable product fact about where a sanitizer's credit is
scoped, and a defensible product position on the merits: a URL-component
encoder is not a shell quoter.

*Template 6, both cells wrong, one cause.* The positive misses entirely: a
write to `process.env.DFB_STORED` followed by a read of the same key produces
zero findings from all 103 rules, even though `process.argv`, `process.env`,
and `execSync` are each individually modeled — the store boundary is not linked
and also suppresses the source the read would otherwise be. The negative fires:
its read of an unwritten distinct key is a plain shipped environment source,
and the model has no key discrimination because it is not looking at keys. The
preregistration named this hazard for this template, down to its wording; the
run adds only the positive-side half.

No tuning: no rule edited, no fixture adjusted to make a shipped model fire.

Docs: docs/javascript-native.md, native sections in all four adapter READMEs,
docs/adapters.md's rollout mechanics updated for the landed arm and the anchor
rule, milestones, and the kernel and README cross-references.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Content-identical bases diverged only by squash identity; the wave's side
carries the same infra plus the JavaScript row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@DavidBakerEffendi
DavidBakerEffendi merged commit 11064e8 into main Aug 27, 2026
2 checks passed
@DavidBakerEffendi
DavidBakerEffendi deleted the dave/native-javascript branch August 27, 2026 14:10
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.

1 participant