Issue #11 ports the sixteen scored Java propagation templates to JavaScript.
The JavaScript cases keep the Java template_id values, source-to-sink
polarity, and negative mechanism; only the smallest fixture construct is
adapted to JavaScript syntax. Every scored JavaScript template has exactly one
positive and one negative core case.
The kernel has since been expanded by the thirteen preregistered challenge templates, all of them directly applicable to JavaScript: the JavaScript core denominator is 29 templates / 58 assertions. See the challenge-tier expansion below for the adaptations, the per-adapter results, and the one adapter whose expanded evidence is deferred.
| Stratum | Template ID | JavaScript adaptation |
|---|---|---|
| Local | dfb-template-direct-propagation |
Direct function call, unchanged in meaning. |
| Local | dfb-template-local-overwrite-kill |
let reassignment replaces the Java local assignment. |
| Local | dfb-template-local-multi-step-chain |
const locals carry the value through the same chain. |
| Local | dfb-template-arithmetic-expression-propagation |
JavaScript number arithmetic preserves the expression-flow distinction. |
| Calls/returns | dfb-template-call-context-separation |
One relay is called with tainted and clean values; the selected call remains the distinction. |
| Calls/returns | dfb-template-argument-position-separation |
A helper returns its first parameter, so the second-argument negative remains non-flowing. |
| Calls/returns | dfb-template-return-relay-one-hop |
A one-hop helper return carries the value to the sink. |
| Calls/returns | dfb-template-return-relay-two-hop |
Two JavaScript helper returns preserve the two-hop depth. |
| Heap/separation | dfb-template-object-separation |
Separate object literals stand in for distinct Java objects. |
| Heap/separation | dfb-template-same-object-field-separation |
One object has separate tainted and clean properties. |
| Heap/separation | dfb-template-alias-propagation-separation |
Assignment of an object reference creates the alias; a second literal remains distinct. |
| Heap/separation | dfb-template-array-element-separation |
Distinct array indices stand in for Java array elements. |
| Control transfer | dfb-template-infeasible-branch |
Literal true/false conditions make the positive/negative path feasible or unreachable. |
| Control transfer | dfb-template-branch-join |
The negative overwrites the value on both branches; the positive leaves one path tainted. |
| Control transfer | dfb-template-loop-carried-kill |
A loop either overwrites the carried value or computes from it. |
| Control transfer | dfb-template-exception-catch |
A JavaScript Error object carries a property through throw/catch; this replaces Java's checked exception class. |
JavaScript additionally carries a taint-modeling population, on its own tier and with its own scorecards: twelve templates and twenty-four assertions that test whether an engine can be told things rather than whether it can follow flow it can see. It is never pooled with the core denominator above, and a score there is not a propagation score. See the JavaScript taint-modeling matrix.
A third JavaScript population, on the same modeling tier but a disjoint
model_profile, asks what each tool ships rather than what it can be told: six
templates and twelve assertions over real Node and ECMAScript APIs. See
the JavaScript tool-native probe set. It is pooled with
neither of the two above.
All JavaScript fixtures use the benchmark-controlled dfb_source and
dfb_sink function names. The Bifrost adapter may lower those endpoints through
its JavaScript kernel policy, but fixture metadata remains analyzer-neutral and
retains only observed evidence in reports.
The challenge tier preregistered thirteen further
propagation templates before any fixture existed. All thirteen are classified
directly applicable to JavaScript, so the JavaScript core denominator grows
from 16 templates / 32 assertions to 29 templates / 58 assertions. The
challenge cases carry score_tier: "core" — there is no separate tier — and
their fixture provenance revision is m3-challenge-javascript.
The v0.3.0 sixteen-template core and this expanded core are different populations and are never compared number to number.
Every cell is direct: JavaScript is the language the preregistration's stratum A and B sketches were written for. The realizations, recorded so a reader can check the fixture against the template rather than against a guess:
| Stratum | Template ID | JavaScript realization |
|---|---|---|
| A | dfb-template-chal-reflective-invocation |
handlers[name](dfb_source()) with name a string constant in a local, so the selected method is never a syntactic literal at the call site. The negative points name at the sibling method that drops its argument. |
| A | dfb-template-chal-computed-property |
holder[key] = dfb_source() written and read back through the same local key variable. The negative writes under one constant key and reads a provably distinct one. |
| A | dfb-template-chal-dispatch-table |
An object literal of two arrow functions; the entry is fetched as a first-class value (const selected = table[key]) and then invoked, which is what separates it from the reflective method call above. |
| B | dfb-template-chal-closure-capture |
A factory captures the tainted local and returns () => dfb_sink(captured), invoked by the caller after the local has left scope. The negative captures the clean local; the source call stays in place. |
| B | dfb-template-chal-function-field |
Two holder objects each carry a function-valued fn property; a separate invoke(target, value) reads the field and calls it. The negative hands invoke the second holder. |
| B | dfb-template-chal-callback-registration |
A plain object with a hooks array, a register function, and a fire driver that iterates and invokes. No framework, twenty lines of language. |
| B | dfb-template-chal-anonymous-implementation |
Two inline anonymous function expressions assigned to variables and invoked through the reference; neither captures anything, which is what keeps it distinct from closure capture. |
| C | dfb-template-chal-map-iteration |
for (const [key, value] of Object.entries(carrier)), never a keyed get. The negative iterates a second, disjoint object. |
| C | dfb-template-chal-nested-access-path |
a.b.c.value written and read at depth 3; the negative reads the sibling a.b.c.other. |
| C | dfb-template-chal-element-object |
An array of object literals; the negative reads items[1].value after items[0].value was written. |
| D | dfb-template-chal-deep-relay-chain |
relay1 … relay6, module-level, no branching or state, with the sink inside relay6. The negative feeds the identical chain a clean constant. |
| D | dfb-template-chal-recursive-carry |
carry(value, depth) recursing to depth === 0 from 5; the negative's base case returns a clean constant instead of the carried one. |
| D | dfb-template-chal-context-pair-depth2 |
One helper reached through two distinct two-deep paths, outerTainted -> wrapper -> helper and outerClean -> wrapper -> helper. Both paths are live in both fixtures and the value returns to the sink, which is the two-level extension of the classic dfb-template-call-context-separation fixture's own shape; only which returned value reaches dfb_sink differs. |
All twenty-six fixtures are standard-library-only — no dependency, no
framework, no build tooling — and each parses clean under the host toolchain
this kernel records, node --check.
Three adapters were re-run over the whole 58-assertion population for this expansion; one is deferred, and the deferral is a freeze rule rather than a gap in coverage.
| Adapter | Expanded run | Report |
|---|---|---|
| Bifrost v0.10.5 | Yes — first run of the dedicated JavaScript kernel | reports/bifrost-javascript-kernel.json |
| Joern 4.0.610 | Yes | reports/joern-javascript-kernel.json |
| Semgrep CE 1.174.0 | Yes | reports/semgrep-javascript-kernel.json |
| CodeQL 2.26.4 | Deferred | reports/codeql-javascript-kernel.json |
reports/codeql-javascript-kernel.json is one of the nineteen reports
reports/freeze.json digest-binds for v0.3.0, so this change must not
overwrite it: expanded CodeQL evidence is pending the v0.4.0 freeze-prep
re-run, on the repo's established re-run-at-freeze pattern. The retained
CodeQL JavaScript evidence remains the valid 32-assertion classic snapshot
described below. The same rule applies in reverse to the Bifrost smoke report:
its JavaScript slice stays the classic pairs at 118 cases, and the challenge
assertions are carried by the new dedicated kernel report instead.
Because the corpus-wide fixture_revision covers every case and fixture byte,
the three expanded JavaScript reports carry revisions no earlier retained
report carries: the Bifrost and Joern reports
sha256:64ef139f452fd296bb26463bc552e5e5998ca4bb4584d45565d858424814bde9, and
the Semgrep CE report
sha256:61c06a78b95b86764d3c220cfefd7af37373db64b15ae0b76c6ebf924217ab2e,
because it was re-run once more after the Python challenge wave merged so that
its capability rationales cite the preregistered per-template partition rather
than the generic tag rule. Reports at different fixture revisions are not
pooled; the JavaScript outcomes are identical across the two Semgrep runs,
which differ only in those reason clauses.
reports/bifrost-javascript-kernel.json, the first run of the dedicated
run-bifrost-javascript-kernel command, covers all 58 assertions under
adapters/bifrost/policies/core-javascript-kernel.rqlp (the frozen
direct-propagation pair still names the cross-language breadth policy, as in
every other language kernel).
| Stratum | Assertions | Polarity match | Outcome distribution |
|---|---|---|---|
| Classic (16 templates) | 32 | 32/32 | 16 reached, 16 not-reached |
| Challenge (13 templates) | 26 | 3/26 | 1 reached, 2 not-reached, 21 inconclusive, 2 runner-error |
The classic half reproduces the frozen smoke evidence exactly. On the challenge
half the engine decided only two of the thirteen pairs, and every decision it
made was correct: context-pair-depth2 both ways, and the
deep-relay-chain negative. There is not one false positive or false negative
in the stratum. The remaining twenty-one assertions are inconclusive with
retained partial_discovery evidence — "taint discovery is incomplete:
procedure value-flow snapshot for … is unknown" — across
reflective-invocation, computed-property, dispatch-table,
closure-capture, function-field, callback-registration,
anonymous-implementation, map-iteration, nested-access-path,
recursive-carry (both polarities each) and the deep-relay-chain positive.
The element-object pair is runner-error: the engine reports
internal_invariant with "invalid value-flow snapshot: oracle relation does
not belong to the required query arena and role" on an array of object
literals. None of these is a negative result, and none is counted as one.
Read together with the classic half, the honest reading is that this build decides local, field-at-depth-1, and short-relay JavaScript completely, and declines rather than guesses on higher-order code, computed access, container iteration, deep access paths, and recursion.
reports/joern-javascript-kernel.json, jssrc2cpg, the same unmodified
adapters/joern/queries/kernel.sc every other Joern kernel uses, at the
distribution's default maxCallDepth of 4. Nothing was configured up.
| Stratum | Assertions | Polarity match | Outcome distribution |
|---|---|---|---|
| Classic (16 templates) | 32 | 26/32 | 18 reached, 14 not-reached |
| Challenge (13 templates) | 26 | 18/26 | 9 reached, 17 not-reached |
Every one of the 58 assertions executed: zero inconclusive, zero
unsupported, zero runner-error. The classic half reproduces the retained
v0.3.0 Joern snapshot case for case, mismatch for mismatch, so the expansion
introduced no drift in the population it shares.
Challenge mismatches, verbatim:
dfb-taint-javascript-reflective-invocation-positive: false negative.dfb-taint-javascript-dispatch-table-positive: false negative.dfb-taint-javascript-function-field-positive: false negative.dfb-taint-javascript-callback-registration-positive: false negative.dfb-taint-javascript-map-iteration-positive: false negative.dfb-taint-javascript-deep-relay-chain-positive: false negative.dfb-taint-javascript-computed-property-negative: false positive.dfb-taint-javascript-nested-access-path-negative: false positive.
Three readings, stated as evidence rather than as a ranking:
- Stratum D confirms the preregistered prediction. The depth-6 relay
positive is missed while its negative is correct, and
context-pair-depth2— two deep — is correct both ways. The pair must be read together: the stratum-D negative here is a true negative reached partly because the engine cannot see that far, which is exactly why the preregistration called the positive cell the informative one.recursive-carryis correct both ways at constant depth 5. - The two false positives are the field-precision bound, not absence of
field sensitivity.
computed-propertyandnested-access-pathare the computed-key read and the depth-3 sibling read; the classicsame-object-field-negativeandarray-element-negativeare already false positives in this engine, so the challenge pair sharpens a known over-approximation rather than discovering one. - The higher-order misses are under-approximation, uniformly. Every
stratum-A and stratum-B negative is correct and five of those positives are
missed, which is the under-approximating half of the approximation character
the preregistration described.
closure-captureandanonymous-implementationare the two stratum-B pairs it does resolve.
reports/semgrep-javascript-kernel.json. The whole 58-case population is
selected and balance-checked, and the bounded profile then decides what is
scored, from case metadata, before Semgrep is invoked.
| Stratum | Assertions | Scored | unsupported |
Polarity match (scored) |
|---|---|---|---|---|
| Classic (16 templates) | 32 | 14 | 18 | 12/14 |
| Challenge (13 templates) | 26 | 0 | 26 | n/a |
All twenty-six challenge assertions take the preregistered unsupported
partition, exactly as the challenge tier predicted: no
challenge template carries the intraprocedural feature tag, so none is inside
the documented CE local-taint profile, and each retains its own
*-unsupported.json capability-decision document naming the declared
capability and the boundary it falls outside. The scored subset therefore stays
at 14 assertions and 12/14, unchanged from the classic run — the two
mismatches are still the infeasible-branch and loop-carried negatives, the
path sensitivity the pinned CLI sells as Pro. The partition was not adjusted
for this expansion, and twenty-six declined assertions are coverage, never
twenty-six false negatives.
The CodeQL JavaScript vertical slice is exactly the 32 taint/core cases
under cases/taint/javascript/: the 16 template rows above multiplied by one
positive and one negative assertion. Every selected manifest points to the
dedicated query:
adapters/codeql/javascript/queries/JavaScriptKernel.ql
The query is owned by the dedicated JavaScript CodeQL pack manifest at
adapters/codeql/javascript/qlpack.yml, even though CodeQL's standard library
can cover both JavaScript and TypeScript syntax.
The runner must not select TypeScript cases, calibration cases, Java cases, or the direct-flow breadth population. Run it with an already-installed CodeQL CLI and local pack directory:
cargo run -- run-codeql-javascript-kernel \
--codeql /path/to/codeql \
--codeql-packs /path/to/codeql-packsThe command creates one cold JavaScript CodeQL database per case from the
declared fixture files, runs the dedicated query, and cleans temporary
database/workspace artifacts after retaining evidence. It writes the
language-specific normalized report to
reports/codeql-javascript-kernel.json and raw SARIF/runner diagnostics to
reports/raw/codeql-javascript/. The retained run used CodeQL CLI 2.26.3,
build SHA 7d097a43199effe04ecd9c6bd3ad9bb02a45b3d7, with official
github/codeql tag codeql-cli/v2.26.3 at source commit
44a68d3a47fcbcd6a6a76ec7d1c1b3a1a28b201e, and
codeql/javascript-all@2.9.0 from the committed lock. Registry retrieval of
the 2.9.0 pack was unavailable in the test environment, so the tested command
used the matching official source workspace root via --codeql-packs (or an
equivalent matching bundle pack root):
CODEQL=/path/to/codeql-v2.26.3/codeql
CODEQL_SOURCE_ROOT=/private/tmp/codeql-source-v2.26.3
cargo run -- run-codeql-javascript-kernel \
--codeql "$CODEQL" \
--codeql-packs "$CODEQL_SOURCE_ROOT"The normalized report records the exact CLI version/build and configuration hash discovered during the run. A matching official source workspace or bundle is a valid reproduction input when registry retrieval is unavailable.
CodeQL query results are evidence, not ground truth by themselves. The runner
reconciles SARIF result locations with the case's DFB-SINK: anchor; that
marker identifies the anchored sink declaration/function. The SARIF result
must be in the same anchor file at the callsite to that sink identity, but it
need not be on the marker's exact line. Query path evidence identifies the
DFB-SOURCE: to sink flow, and normalized results retain both anchor sets. A
successful, anchor-backed finding is reached; a successful analysis with no
matching finding is not-reached. Missing or incomplete location evidence is
inconclusive, while an explicitly unsupported capability is unsupported
and a database, query, SARIF, or runner failure is runner-error.
None of inconclusive, unsupported, or runner-error may be normalized to
not-reached, and none may be counted as a semantic negative. This keeps
execution health separate from the polarity of the 16 balanced assertions.
The retained JavaScript snapshot has 32 results: 15 reached and 17
not-reached, with zero inconclusive, unsupported, or runner-error
outcomes. Twenty-nine of 32 match expected polarity. The false negatives are
dfb-taint-javascript-alias-propagation-positive and
dfb-taint-javascript-expression-positive; the false positive is
dfb-taint-javascript-loop-carried-negative. All 32 retained raw outputs are
SARIF files with zero error files, and normalized witness_checkpoints are
empty for every case. The configuration hash is
a038e39eb93d6fc674ab59cf2e4de5b3608f1d7b294c19da75ce1bd041c75ac5.
The Java kernel also has two calibration cases that are intentionally outside
this sixteen-template scored slice. dfb-template-one-hop-relay is a simpler
helper-flow calibration covered by the scored return-relay template, and
dfb-template-modeled-external-summary requires an explicitly activated
external semantic-model catalog. The standalone CLI cannot activate that
catalog, so it must remain unsupported rather than being translated into a
negative JavaScript result.