Commit Graph
511 Commits
Author SHA1 Message Date
morluto b1f1b3eff3 fix(javascript): recognize inherited environment for NaN 2026-11-10 17:58:04 -05:00
morluto 42f61205c3 test: strengthen authority parser and result oracles 2026-11-10 16:32:53 -05:00
morluto 3c4be703cd test: consolidate JavaScript and provider fixture coverage 2026-11-10 16:32:53 -05:00
morluto cb4e5cfbff feat(analysis): make resource budgets recoverable across CLI and MCP (#9706)
* refactor(analysis): centralize resource budgets and recovery facts

* feat(analysis): expose ASAR and Ghidra runtime admission controls

* feat(artifacts): expose reader admission and preserve limited recognition

* feat(javascript): expose recoverable semantic precision budgets

* feat(web): add recoverable source-map budgets and compact retained views

* feat(web): make historical capture decoding budgets recoverable

* fix(desktop): enforce selected view policies across capture and export

* feat(runtime): make Frida and LLDB retention budgets selectable

* feat(native): expose diagnostic budgets and preserve exhaustion causes

* feat(managed): expose recoverable snapshot and representation budgets

* feat(metadata): make Go and Rust inspection budgets recoverable

* feat(wasm): expose representation and producer admissions

* feat(evm): expose per-call worker resource budgets

* feat(android): make resource and string-pool omissions recoverable

* feat(producers): expose command and recorded-capture budgets

* docs(analysis): record recovery contracts and integrated validation

* fix(docs): link calibration oracle from published verification pages

* fix(docs): restore desktop task anchors and receipt links

* test(analysis): align fixtures with selectable budget contracts

* fix(analysis): remove source-location projector dependency cycle

* test(analysis): refresh budget recovery and verifier regressions

* test(analysis): preserve format and publication recovery expectations

* test(windows): align conformance with owned runtime admission

* fix(desktop): preserve selected alias kinds in exported state

* test(analysis): align provider fixtures with recoverable budgets

* test(desktop): validate emitted alias metadata structurally

* fix(ghidra): classify bounded Windows endpoint refusals

* test(cli): assert selectable artifact admission recovery

* fix(cli): preserve capture provenance in environment builds
2026-11-10 05:11:20 -05:00
吴佳浩Albenandmorluto 3b2522bd3b fix(javascript): reject a tls protocol version Node refuses at the call site (#9557)
* fix(javascript): reject a tls protocol version Node refuses at the call site

`tls.connect` resolves `minVersion`/`maxVersion` before it opens a socket, so a bad literal
throws from the call itself and no socket exists. The acquisition filter decided the port
only, so it reported a complete built-in resource for a call that throws.

Measured on Node 24.22.1:

| `minVersion` / `maxVersion` | outcome |
| --- | --- |
| `"TLSv1"`, `"TLSv1.1"`, `"TLSv1.2"`, `"TLSv1.3"` | socket created |
| `null`, `undefined`, absent | socket created |
| `"TLSv1.0"`, `"tlsv1"`, `"tlsv1.2"`, `"TLSv1.4"`, `"nope"`, `""` | THREW `ERR_TLS_INVALID_PROTOCOL_VERSION` |
| `5`, `true`, `{}`, `[]` | THREW `ERR_TLS_INVALID_PROTOCOL_VERSION` |
| `1n` | THREW `TypeError` |

The accepted set is exact and case-sensitive, so it is a closed list rather than a pattern.
`net` ignores these members, so only `tls` is consulted. The member is read in write order,
which is why `{minVersion: "nope", minVersion: "TLSv1.2"}` keeps its acquisition; a spread,
a computed key or an accessor leaves the answer undecided rather than decided against.

22 cases, 11 failing before this commit. `src/domain/javascript`: 5040 to 5062 passing.
`tsc` 0; `build` 0; `npm run format:check` 0; `npm run lint` 0 errors; `knip` 0.

* fix(javascript): read every tls options slot and check both version members

Four review findings, each measured on Node 24.22.1. All four were real gaps in the first
version of this check.

**An accepted `minVersion` returned before `maxVersion` was read.** `tls.connect({port: 80,
minVersion: "TLSv1.2", maxVersion: "nope"})` throws, because both members are resolved
before the socket opens. The loop now continues past an accepted version and returns only
for a rejected one.

**Only argument zero was read.** `tls` also accepts `(port, options)`, `(port, host,
options)` and `(path, options)`, and all three throw on a bad version:
`tls.connect(443, {minVersion: "nope"})`, `tls.connect(443, "127.0.0.1", {minVersion:
"nope"})` and `tls.connect("/tmp/x.sock", {minVersion: "nope"})`. Every object literal in
the argument list is now read; a spread argument shifts those positions, so it leaves the
answer undecided instead of picking a wrong slot.

**A signed number was left unknown.** `minVersion: -1`, `+1` and `-0` all throw, but they
arrive as a `UnaryExpression` and matched none of the literal arms. `readExactJavaScriptLiteral`
already normalises `-1` and `+1`; `-0` is the one case it excludes, so a sign over a numeric
literal is decided explicitly here rather than treated as unknown.

13 cases added; reverting the slot and loop changes fails 4 of them.
`src/domain/javascript`: 5062 to 5075 passing. `tsc` 0; `build` 0;
`npm run format:check` 0; `npm run lint` 0 errors; `knip` 0.

* fix(javascript): merge the tls arguments and honour a supplied secure context

Four review findings, each measured on Node 24.22.1. All four were real: the previous
reader checked each object argument independently and ignored the context.

**Several object arguments merge in order, so the later one wins.** The reader rejected the
first object and stopped:

| call | Node 24.22.1 |
| --- | --- |
| `tls.connect({port: 443, minVersion: "nope"}, {minVersion: "TLSv1.2"})` | SOCKET |
| `tls.connect({port: 443, minVersion: "TLSv1.2"}, {minVersion: "nope"})` | THREW |
| `tls.connect({port: 443}, {minVersion: "nope"})` | THREW |

The members are now resolved across the whole argument list in write order, so a later
argument replaces an earlier one the way Node's merge does.

**A supplied `secureContext` bypasses version parsing entirely.** Node uses
`options.secureContext || tls.createSecureContext(options)`, so
`tls.connect({port: 80, secureContext: ctx, minVersion: "nope"})` connects while a `null`
context throws. An unreadable context leaves the answer undecided.

**An ordinary method member cannot install a version.** The reader skipped any object
containing a method, so `{checkServerIdentity() {}, minVersion: "nope"}` kept a socket
Node never creates. Only an accessor runs a body and leaves the member open; a plain method
is a value Node never reads as a version name. A `minVersion() {}` member is itself such a
value, so it rejects.

**Signed BigInt and function-shaped versions were left unknown.** `-1n`, `() => {}`,
`function () {}` and `class {}` are all truthy non-strings that Node refuses; none matched
an existing arm. They are now decided.

12 cases added; reverting the merge and context changes fails 2 of them and reverting the
method arm fails 6. `src/domain/javascript`: 5075 to 5087 passing. `tsc` 0; `build` 0;
`npm run format:check` 0; `npm run lint` 0 errors; `knip` 0.

* fix(javascript): read only the options slot tls.connect copies, and its accessors

Six review findings, each measured on Node 24.22.1. All six were real.

**A falsy `secureContext` does not bypass version parsing.** Node tests
`options.secureContext` for truth, so `{secureContext: false, minVersion: "nope"}`, and `0`
and `""` with it, all throw. The guard only recognised nullish syntax, so it dropped real
throws. `staticTruthiness` now decides the context: a known-falsy value keeps checking, a
known-truthy one ends the check, and an unreadable one stays undecided.

**Node copies at most one options object.** Measured:

| call | result |
| --- | --- |
| `(443, {minVersion: "TLSv1.2"}, {minVersion: "nope"})` | SOCKET — the third argument is ignored |
| `(443, {minVersion: "nope"}, {minVersion: "TLSv1.2"})` | THREW |
| `(443, "127.0.0.1", {minVersion: "nope"})` | THREW — the host fills slot 1 |
| `({port: 443, minVersion: "nope"}, {minVersion: "TLSv1.2"})` | SOCKET — the second object wins |

The reader treated every object argument as merged in order, which let an ignored object
decide. `tlsOptionsObjects` now models the two overloads: an object in slot 0 is merged with
slot 1, and a port or path in slot 0 makes the first object among slots 1 and 2 the only one
read.

**An exact computed key names the member.** `{["minVersion"]: "nope"}` throws, but the
reader marked every computed key unknown before the shared name resolver saw it. Only an
unresolvable key is unknown now, and the case that asserted the old behaviour uses a
binding key so it still covers the unknown path.

**An accessor can write the member first.** `{get side() { this.minVersion = "TLSv1.2"; },
minVersion: "nope"}` connects, because Node's own copy runs the getter before it reads
`minVersion`; and a `set minVersion` in an earlier argument absorbs the later write. Both
are bodies this reader does not run, so an accessor anywhere in the merged objects now
leaves every version member undecided. A plain method still rejects, since Node never reads
it as a version name.

**An unshadowed `NaN` or `Infinity` is a non-string version.** Both throw, and neither
matched an existing arm. They are decided the same way the adjacent port filter decides
them, through `isUnmodifiedGlobalReference`, so a shadowed binding stays unknown.

16 cases added; reverting the truthiness, accessor and computed-key changes fails 2 of them.
`src/domain/javascript`: 5075 to 5188 passing. `tsc` 0; `build` 0;
`npm run format:check` 0; `npm run lint` 0 errors.

* fix(javascript): use the nullable key owner and settle unary versions

Six review findings, each measured on Node 24.22.1. All six were real, and three of them
were removing real sockets.

**`semanticStaticPropertyName` encodes an unresolved key as `""`, never `undefined`.** The
guard tested `undefined`, so it never fired and an unresolved computed key was read as a
member literally named empty. `const k = "minVersion";
tls.connect({port: 443, minVersion: "nope", [k]: "TLSv1.2"})` connects on Node 24.22.1
because the later computed member wins, while the analysis filtered on the stale `"nope"`
and dropped the socket. `semanticStaticPropertyKey` is the nullable owner and is now used,
with `null` treated as unknown.

**A getter can replace a plain version method before Node copies it.** The method branch
ignored `accessorWritten`, so `{get side() { this.minVersion = "TLSv1.2"; }, minVersion()
{}}` still returned `"rejects"` although it connects. The same unknown result now applies to
methods when an accessor is present.

**A non-literal second argument is a merged options object, not an absent one.**
`const o = {minVersion: "TLSv1.2"}; tls.connect({port: 443, minVersion: "nope"}, o)`
connects. The slot reader mapped every non-object to `undefined`, so only the stale first
object was examined. A non-object in a copied slot now stays unknown.

**`-0` is falsy, so a signed-zero context does not bypass parsing.** The local truthiness
helper did not fold a unary `-`, so `{secureContext: -0, minVersion: "nope"}` was treated as
a possible bypass although it throws. The shared `staticJavaScriptTruthiness` owner already
handles signed zero and replaces the local helper, which is deleted.

**A unary operator over a settled operand is a settled non-string version.** `!0`, `!1`,
`~~0`, `!!0` and `typeof 1` all throw `ERR_TLS_INVALID_PROTOCOL_VERSION`; the previous arm
recognised only `+` and `-`, and the exact-literal and shape arms did not reach them.
`void <expr>` stays accepted, because it is `undefined`.

The slot reader also had to distinguish a host string in slot 1 from an options object, since
`tls.connect(443, "127.0.0.1", {minVersion: "nope"})` throws while
`tls.connect(443, {minVersion: "nope"})` is the two-slot form.

`src/domain/javascript`: 5188 to 5197 passing. Reverting the key owner fails 1 case and
reverting the unary arms fails 2. `tsc` 0; `build` 0; `npm run format:check` 0;
`npm run lint` 0 errors.

* test(javascript): distinguish package scope in route fixtures

* test(apple): separate identity and refusal fixture groups

* fix(javascript): share TLS option facts and preserve merge uncertainty

* fix(javascript): separate TLS assignment authority from copy facts

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 22:09:29 +08:00
吴佳浩Albenandmorluto 7a1d89a801 fix(javascript): reject child-process option literals Node refuses at the call site (#9594)
* fix(javascript): reject a spawn stdio string Node refuses at the call site

`node:child_process` validates a literal `stdio` string before it creates anything, so a
bad value throws from the call itself and no child process exists. The analysis reported the
raw string as `stdioMode` and kept the spawn.

Measured on Node 24.22.1:

| `stdio` | outcome |
| --- | --- |
| `"pipe"`, `"overlapped"`, `"inherit"`, `"ignore"` | child starts |
| `"ipc"`, `"PIPE"`, `"pipe "`, `"pipeline"`, `"stream"`, `"stdio"` | THREW `ERR_INVALID_ARG_VALUE` |

The accepted set is exact and case-sensitive, so it is a closed list rather than a pattern.
`"ipc"` is the interesting one: it is valid as an array element but refused as the scalar
shorthand, which is the shape a caller reaches for when they mean the array form.

`fork` differs on one value because it appends `"ipc"` to a string and splits it, so `""`
becomes the lone element `"ipc"` and is refused:

| call | outcome |
| --- | --- |
| `spawn(..., {stdio: ""})` | child starts |
| `fork(..., {stdio: ""})` | THREW `ERR_INVALID_ARG_VALUE` |

`exec` is left alone, because it spreads its options and validates none of them. A number,
boolean or object `stdio` is also refused by Node, but `stdioMode` already reports those as
`"dynamic"`; deciding them here would claim more than the reported mode explains, so only
the string case is decided.

18 cases, 9 failing before this commit. `src/domain/javascript`: 5106 to 5124 passing.
`tsc` 0; `build` 0; `npm run format:check` 0; `npm run lint` 0 errors.

* fix(javascript): reject a non-string cwd or argv0 Node refuses at the call site

The same call-site validation the `stdio` string gets applies to `cwd` and `argv0`:
Node checks them before the child exists, so a non-string literal throws
`ERR_INVALID_ARG_TYPE` and the analysis still reported a spawn.

Measured on Node 24.22.1, identical on `spawn`, `fork`, `execFile` and `exec`:

| `cwd` / `argv0` | outcome |
| --- | --- |
| `"/tmp"` / `"x"` | child starts |
| `null` | child starts |
| `5`, `0`, `true`, `{}`, `[]` | THREW `ERR_INVALID_ARG_TYPE` |

`env` is deliberately not in the set: Node accepts any `env` value, including `5` and
`"x"`, so a non-object `env` is not a call-site failure.

Only an exactly readable literal is decided; a binding, a call or a member read stays
undecided, because its value is not settled by this expression.

The test file is renamed from `spawn_stdio_literal` to `spawn_option_literals` because it
now covers all three members. 16 cases added; reverting this check fails 9 of them.
`src/domain/javascript`: 5124 to 5140 passing. `tsc` 0; `build` 0;
`npm run format:check` 0; `npm run lint` 0 errors.

* fix(javascript): read the stdio and path options by value, not by AST kind

Seven review findings, each measured on Node 24.22.1. All seven were real, and two of them
were removing real children.

**`execFile` does not forward `stdio`.** Async `execFile` never passes `options.stdio` to its
underlying `spawn`, so `execFile(process.execPath, ["-e", ""], {stdio: "bad"})` returns a
real child — `stdioMode` already forces it to `"default"`. The new filter rejected it anyway
and dropped the spawn plus its linked interactions. `execFile` is now exempt, like `exec`.

**A no-substitution template literal is the same string to Node.** `readExactJavaScriptLiteral`
already resolves it, but the filter tested `t.isStringLiteral`, so ``{stdio: `ipc`}`` kept a
spawn Node refuses while ``{cwd: `/tmp`}`` — a valid path — was rejected because the same
branch called `readExactJavaScriptLiteral` and treated any hit as a non-string. Both sides
now read the value and test its type:

| call | Node 24.22.1 |
| --- | --- |
| ``spawn(..., {stdio: `ipc`})`` | THREW `ERR_INVALID_ARG_VALUE` |
| ``spawn(..., {stdio: `pipe`})`` | child starts |
| ``spawn(..., {cwd: `/tmp`})`` | child starts |
| ``spawn(..., {argv0: `x`})`` | child starts |

**A shape the literal owner does not price still has a settled type.** `{cwd: 1n}`,
`{cwd: /x/}`, `{cwd: () => {}}`, `{cwd: class {}}` and `{cwd: new String("x")}` all throw
`ERR_INVALID_ARG_TYPE`, and none matched the previous object/array/boolean arms.

**The `fork` explanation was wrong.** Node's `stdioStringToArray` rejects `stdio: ""` with an
explicit check before anything is appended; it does not split the string and it never builds
an IPC-only array. The rejection was right, the reason was not, so the comment now states the
real rule.

44 cases; reverting the `execFile` exemption fails 1, reverting the value-based read fails 1,
and reverting the extended non-string arms fails 6.
`src/domain/javascript`: 5158 to 5168 passing. `tsc` 0; `build` 0;
`npm run format:check` 0; `npm run lint` 0 errors.

* fix(javascript): keep a path-shaped cwd that Node accepts

`cwd` is a path, so Node takes a byte array or a file `URL` there, not only a string. The
blanket `NewExpression` arm rejected every constructor for both members, which dropped real
children.

Measured on Node 24.22.1, identical on `spawn`, `fork`, `execFile` and `exec`:

| `cwd` | outcome |
| --- | --- |
| `new Uint8Array([47, 116, 109, 112])` | child starts |
| `new Buffer("/tmp")` | child starts |
| `new URL("file:///C:/tmp/")` | child starts |
| `new String("/tmp")`, `new Number(1)`, `new Foo("/tmp")` | THREW `ERR_INVALID_ARG_TYPE` |

`argv0` is different: it is an argument string only, so `new Uint8Array([120])` and
`new URL(...)` both throw there. `isPathConstructor` names the constructors that can produce
a path, and only `cwd` consults it.

`URL` stays undecided rather than accepted, because whether a file URL resolves is a
platform property: `new URL("file:///tmp/")` throws `ERR_INVALID_FILE_URL_PATH` on Windows
while a drive-qualified path does not. Syntax alone cannot settle that, so the answer is
left open instead of guessed.

8 cases added; reverting the arm fails 3 of them.
`src/domain/javascript`: 5168 to 5176 passing. `tsc` 0; `build` 0;
`npm run format:check` 0; `npm run lint` 0 errors.

* test(javascript): distinguish package scope in route fixtures

* test(apple): separate identity and refusal fixture groups

* fix(javascript): keep unresolved cwd constructors unknown

* refactor(javascript): name process option admission by its contract

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 21:46:57 +08:00
Nianze Wuandmorluto 759817ccbe fix(javascript): distinguish Promise and FileHandle releases (#9584)
* fix(javascript): distinguish Promise and FileHandle releases

* fix(javascript): preserve reassigned handle candidates

* test(javascript): select unisolated route fixtures explicitly

* test(javascript): distinguish package scope in route fixtures

* test(apple): separate identity and refusal fixture groups

* fix(javascript): retain uncertain releases after opaque receiver writes

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 21:46:51 +08:00
Chiragandmorluto 0a15246d32 docs(mcp): name the accepted seed kinds on trace_application_feature (#8220)
* docs(mcp): name the accepted seed kinds on trace_application_feature

The compact input profile reduces this schema to shallow properties, which
drops the seed union and its kind enums, so agents could not see which
kinds a seed accepts without reading the source. Describe the seed field
with its kinds, match defaults and one example; shallow reduction keeps
per-property descriptions, so the guidance survives the compact profile.

Closes #8205

* test(mcp): isolate seed guidance catalog configuration

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 21:01:52 +08:00
9108d9a627 fix(javascript): keep strict module checks unproven when alias walks exhaust their budget (#9387)
* fix(javascript): keep strict module checks unproven when alias walks exhaust their budget

Fixes #8201.

* fix(javascript): examine queued module sources after a strict walk exhausts its budget

A strict check that exhausts the alias budget stopped the whole walk, so a module branch queued beside the growing alias was never examined. Stop expanding instead, and drain the queued sources.

* fix(javascript): drain exhausted strict walks by selected keys and finite expressions

After exhaustion the limiter widens every key, so draining by normalized paths could match process.foo as mainModule. Keep each queued source's selected keys, expand finite expressions such as sequences and conditionals, and stop only at bindings and calls.

* fix(javascript): follow bindings and calls breadth first in exhausted strict walks

An exhausted strict walk skipped every binding and call, so (flag ? m : r).require with const m = module lost the module branch when r was explored first. Drain the queue breadth first after exhaustion and follow bindings and calls under a second alias selection budget, so a growing alias cannot starve a sibling.

* fix(javascript): bound every state a strict walk creates after exhaustion

After the alias budget is exhausted, paths are no longer normalized, so a
cycle through containers or getters can keep growing its selected path
without crossing a binding or call. Count each new state against the
drain budget instead of only binding and call follow-ups.

* fix(javascript): retain uncertainty at alias cutoff

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 07:59:15 -05:00
2c27c2a43b feat(electron): report V8 cachedData compilation boundaries (#9501)
* feat(electron): report V8 cachedData execution boundaries

analyze-javascript-application treated new vm.Script(source, {cachedData}) and vm.compileFunction(..., {cachedData}) as ordinary source. V8 deserializes previously compiled bytecode for those calls, so an Electron preload wrapper that delegates its whole contextBridge and IPC surface to a .cache/.jsc snapshot appeared to have been fully analyzed with zero exposed APIs.

Record each vm compilation with its inert cache path when the cachedData value traces to a readFileSync/readFile of path syntax, and project a cached-data execution unknown plus a loads edge from the owning source. Compilations without a cache stay recorded as no-cached-data so absence remains distinguishable from an unparsed snapshot. Count cachedData executions in the Electron summary and state the coverage limit in the graph limitations.

* style(cli): wrap the native data type path description

The `inspect-native-data-type` argument description line is 82 columns, over the repository's 80-column `.oxfmtrc.json` limit, so `npm run format:check` fails on main and on every branch merged from it. It came in with #9465 and the following commits to this file did not touch it. Reformat the one call the formatter objects to; no behaviour change.

* style(domain): reformat the pending dynamic-code accumulator field

`npm run format:check` fails on this branch, which is what `Static checks` reports: oxfmt wants the `pendingDynamicCode` union on one line. Applying it is the whole change; no behaviour is touched.

* fix(electron): preserve VM callable and cache source uncertainty

* docs(javascript): specify cached-data compilation boundaries

* fix(electron): exclude disabled caches from recovered boundaries

---------

Co-authored-by: 2SinceJuly <202121119+2SinceJuly@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 20:16:18 +08:00
0b1a945d69 fix(electron): model view constructors as window-like surfaces (#9571)
* fix(electron): model view constructors as window-like surfaces

Electron's static pass recognized a window only through the literal
`BrowserWindow` constructor, so `BaseWindow`, `BrowserView` and
`WebContentsView` produced no node and their literal `webPreferences` were
invisible. IPC registrations reached through such a view's own
`webContents.ipc` were dropped too, because the receiver resolver had the
same single-constructor gate -- VS Code alone registers seven channels this
way, and a shipped sample of four applications put 29 percent of window
construction sites on an unmodeled constructor.

Read webPreferences through one constructor set shared by the construction
inspector and the IPC receiver resolver. The finding only carries the new
`window_constructor` field when the site is not `BrowserWindow`, so existing
projections and their evidence stay byte-identical. A non-BrowserWindow
owner is not admitted for `loadFile`/`loadURL` on itself: only BrowserWindow
exposes those directly.

* fix(electron): keep the view constructor label lookup nullable

Observation labels are nullable in the application graph schema, so the
byLabel lookup failed tsc with TS18047 and took the Static checks job
down with it.

---------

Co-authored-by: 2SinceJuly <202121119+2SinceJuly@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 19:53:11 +08:00
908d9c0726 fix(javascript): keep export bindings out of module views (#9486)
* fix(javascript): keep export bindings out of module views

Exported symbol bindings reuse the javascript-module node kind, so the modules collection counted one row per export alongside the owning module. On a CommonJS or ESM file with N exports, inspect_analysis_view reported N+1 module units, module pages listed indistinguishable rows sharing the module path, and item selection by selector.path failed with "Multiple modules have path".

Treat a node whose observations carry semantic_role export-binding as an export of its owning module, not a module unit, and exclude it from module entries. Export bindings remain distinct graph entities for export-shape comparison and version matching.

Validation: a domain regression in analysisView.test.ts and a CLI acceptance regression over a real CommonJS analysis both fail on main and pass with this change. Also ran the analysis view, MCP boundary, CLI boundary and JavaScript acceptance suites (125 tests), npm run check:fast, and the MCP catalog schema test after regenerating .cache/mcp-tool-catalog.json.

* test(javascript): run the export-binding view case on the shared CLI

The case starts a compiled CLI process for each of its three commands,
paying startup and tool registration three times per run. The fixture
already provides a file-scoped process for calls with no stdin or explicit
timeout, so use it.

* style(javascript): format the constructed-evidence type in the module-view case

The acceptance case's inline graph type did not match what oxfmt emits for the
80-column limit, so `format:check` refused the tree. Reflow only; the assertions
are unchanged.

* style(cli): wrap the native data type path description

The `inspect-native-data-type` argument description line is 82 columns, over the repository's 80-column `.oxfmtrc.json` limit, so `npm run format:check` fails on main and on every branch merged from it. It came in with #9465 and the following commits to this file did not touch it. Reformat the one call the formatter objects to; no behaviour change.

---------

Co-authored-by: 2SinceJuly <202121119+2SinceJuly@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 19:42:47 +08:00
512bcf5cdb fix(javascript): analyze first-party sources before dependencies (#9341)
* fix(javascript): analyze first-party sources before dependencies

Every source in one application analysis charges the same parent result
budget, and sources were offered in lexical inventory order. On an Electron
app.asar, node_modules/ sorts before an electron-vite out-*/ directory, so
dependency files spent the budget and every first-party main, preload, and
renderer file had its semantic analysis not started and its application
projection rejected. The summary then reported zero windows, preloads, and
bridges while electron_semantics still said "recovered".

Offer application JavaScript before JavaScript under node_modules; reported
files keep inventory order. Count Electron boundary facts only from files
whose projection was admitted, and report electron_semantics "partial" (with
a limitation naming the count) when an Electron-loading source was not fully
examined or reported.

Fixes #9321

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(javascript): treat limited Electron examination as partial coverage

A static pass can stop early at the Electron wrapper expansion budget or
another resource limit without failing the worker. Such a source carries no
analysis or projection failure, so its retained IPC or bridge facts still
reported electron_semantics "recovered". Read the owned examination status
and count a limited Electron-loading source as incomplete.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 19:42:19 +08:00
morlutoandhhang e66e92904e fix(desktop): preserve trailing spaces in desktop entry values (#9612)
* fix(desktop): preserve trailing spaces in desktop entry values

* test(mcp): cover source fitting and omitted delivery budgets

---------

Co-authored-by: hhang <169027046+xu91102@users.noreply.github.com>
2026-11-09 19:41:54 +08:00
gongfpp 4df630e16a fix(javascript): keep unisolated program-var route values unknown (#6778)
* fix(javascript): mark unisolated program-var route values unproven

`JavaScriptRouteScan.#resolve` now records the identifier whenever a value
proof depends on a program-scope `var` in a script without a proven isolated
runtime. That binding is a global-object property, so `globalThis.NAME` or an
alias can replace it after the source initializer.

The mark lives in the shared resolver, so every consumer degrades from one
fact instead of each one opting in:

- path segments and navigation destinations stop asserting the source value
  and report an unknown interpretation;
- an index flag stays unproven, as before;
- a route container keeps its source route set and reports an incomplete
  interpretation, so `var routes=[...]` no longer loses every route.

The source initializer is still followed, which is what lets a container
consumer keep its facts while disclosing the gap.

* fix(javascript): keep literal child routes behind an unisolated children array

Rejecting the whole children configuration marked its records as claimed
by the interpretation, so the automatic-root loop skipped them and the
literal child paths disappeared even though their absolute paths need no
parent proof. Keep the source records reachable and let the walker report
the unproven ancestry as an interpretation gap.

* test(javascript): bound the index-route CLI acceptance matrix

The flag matrix runs sixteen CLI invocations in one test and passed at 20.7 s
against a 30 s default timeout, so it timed out on a loaded runner. The same
matrix is already asserted per case in javascriptRouteScan.test.ts, where each
case is an in-process parse; this file keeps one resolved case and one
unresolved case to prove both branches of the public CLI view.

* fix(javascript): retain ownership of unproven route literals

Keep source ownership separate from runtime value proof so rejected program-var route segments cannot reappear through the generic constant pass. Cover path, route, typed and alias forms, retain navigation controls and independent literals, and verify inventory and public CLI projections.

Run the existing CLI index cases independently so repeated process startup does not consume one combined test deadline. Preserve both cases and all assertions.

* fix(javascript): keep unisolated navigation data out of route literals

An unisolated program-var slot holds data, not a route. Refusing its
runtime identity released the object back to the independent route scan,
which then reported a query payload as a nested route. Report the object
as consumed navigation data and stop following it as a path.

* fix(javascript): follow rejected navigation data without trusting it

Claiming the whole object for a rejected value also claimed its sibling
properties, so an independent route in the same object disappeared.
Keep the property selection: ownership follows the slot, and only the
route consumers refuse an unproven runtime identity.
2026-11-09 17:40:57 +08:00
morlutoandShuaixinHuang b46f4fcebd fix(javascript): preserve loader authority for exact suffixes (#9477)
* fix(javascript): require exact loader suffix case

* fix(javascript): share exact loader mode across import resolution

* test(process): align symlink fixture with filesystem checkpoints

* test(javascript): normalize the module resolution oracle path separators

* test(frida): assert the CLI spawn Evidence contract

* test(mcp): use an unsupported analysis-view parent

* test(browser): separate source-location line bounds from parity

* fix(javascript): preserve uncertain loader provenance

* test(javascript): cover uncertain require resolution

* fix(javascript): retain conditional Electron evidence

* fix(javascript): preserve timed semantic provenance

* fix(javascript): retain conditional builtin effects

* test(javascript): distinguish bundled host authority

* fix(javascript): preserve nested loader invocation identity

* test(javascript): qualify Electron fixture authority

* fix(javascript): preserve loader invocation authority

* fix(javascript): respect source order for resolver escapes

* fix(javascript): check nested loader callable mutations

* fix(javascript): retain conditional constructor requests

* fix(reference): retain constructor loader relationships

* fix(reference): follow nested loader callable paths

* refactor(javascript): share callable mutation interpretation

* fix(javascript): require exact package loader suffixes

* refactor(javascript): unify conditional relation evidence

* refactor(javascript): unify conditional node evidence

* fix(javascript): retain unknown authority for mixed case sources

* fix(javascript): preserve loader uncertainty for dotfiles

* fix(reference): classify javascript suffix dotfiles

* test(reference): cover suffix dotfiles through cli

* fix(javascript): retain equivalent conditional built-in origins

* fix(javascript): preserve wrapped resolver operation

* fix(javascript): retain nested conditional built-in receivers

* test(javascript): declare CommonJS in native addon fixtures

* fix(javascript): retain wrapped resolver candidates

* style: format merged native command schema

* fix(javascript): preserve uncertain loader authority

* test(javascript): require candidate preload evidence

* test(javascript): cover reference loader member writes

---------

Co-authored-by: ShuaixinHuang <1836656514@qq.com>
2026-11-09 13:21:24 +08:00
morlutoandFelix 52fe342c73 fix(evidence): preserve subjects in derived workflows (#9492)
* fix(javascript): preserve verified trace evidence subjects

Co-authored-by: Felix <3937061056@qq.com>

* fix(evidence): preserve selected subject names in views

* fix(evidence): preserve subjects across related workflows

* fix(evidence): retain inventory projection subjects

* fix(managed): validate projected graph root identity

* style: format evidence and native command code

* fix(javascript): validate static graph root identity

* test(javascript): update feature trace graph expectations

---------

Co-authored-by: Felix <3937061056@qq.com>
2026-11-09 08:12:17 +08:00
morluto 9e78606e2b fix(inspector)!: retain context lifetimes across protocol ID reuse (#9471) 2026-11-08 22:04:35 +00:00
morluto 5044e20171 test: focus diagnostics and evidence checks on behavior (#9303)
* test: focus configuration diagnostics on their owners

Preserve shape errors, selected paths, recovery guidance, and unchanged-file behavior across parser, setup, and uninstall boundaries.

* test: focus artifact and evidence checks on behavior

Retain format, identity, malformed-input, and cleanup coverage while removing fixed prose and closing the admitted test source.

* refactor(debugger): use configured session admission limits

* test: focus evidence and diagnostics on behavior

* fix(test): restore verifier export and formatting

* test(config): verify debugger limit ranges

* test(rizin): keep fixture executable authoritative

* test(rizin): isolate debugger fixture environment
2026-11-08 16:39:20 +00:00
morluto 352a846127 test: reduce brittle assertions and test-only runtime paths (#9289)
* test: focus assertions on behavioral evidence

Prune redundant and wording-coupled checks while retaining diagnostic, parser, recovery, and cleanup coverage.

* refactor: remove test-only wrappers and duplicate checks

Exercise production operations through test fixtures, remove unused adapters, and keep managed-member uncertainty guidance under its existing owner.

* test: focus process validation on diagnostic facts

* refactor: remove test-only runtime helpers

Move semantic evidence fixture setup out of runtime and remove unused Windows settle recovery helpers while checking configured defaults independently.

* test: focus artifact diagnostics on format facts

* test: verify retained cleanup guidance delivery
2026-11-08 02:02:31 +08:00
morluto 1596d6a275 test: reduce wording coupling and prune redundant coverage 2026-11-08 00:01:18 +08:00
morlutoandCursor Agent 562779bd71 fix(mcp): advertise absolute paths for tools that reject relative inputs (#9179)
* fix(mcp): advertise absolute paths for tools that reject relative inputs

The domain schemas accepted relative paths, then handlers rejected them
with isAbsolute. Those inputs now use absoluteLocalPathStringSchema, and
the duplicate handler checks are gone.

* fix(javascript): own recovery path admission in the schema

Relative recovery paths now fail in absoluteLocalPathStringSchema, including
the additional_paths index. Drop the duplicate handler check, and make the
CLI, MCP, and tools/list tests assert that schema fact.

* test: assert absolute-path facts instead of error prose

Recovery and web-script tests keep the field, format code, and regex
expectation. They no longer freeze the schema's explanatory sentence.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-11-07 15:49:50 +08:00
吴佳浩Alben 923e6f77e3 fix(javascript): keep timer cancellation within the Node handle family (#7712)
* fix(javascript): keep timer cancellation within Node's handle family

Node keeps timeouts and immediates in disjoint handle families. clearTimeout
and clearInterval are aliases that act only on a Timeout from setTimeout or
setInterval, while clearImmediate acts only on an Immediate from setImmediate.
Crossing the families returns without doing anything, so the scheduled
callback still runs.

REA linked a cancellation whenever the cleared binding traced back to any timer
schedule, so clearImmediate(setTimeout(work, 10)) reported a complete
cancellation and the graph emitted a resolved cancels-timer relation. It also
flagged no uncertainty for it.

Require the cancel and the schedule to share a handle family before linking
them. A cross-family clear now keeps linkedTimerId null and resolution
unresolved, and the graph reports the cancellation as a candidate with an
ambiguous-target unknown instead of a resolved fact.

Verified against Node v24.22.1 over all nine schedule/cancel pairs: setTimeout
and setInterval with clearTimeout or clearInterval cancel, and setImmediate
with clearImmediate cancels; the other five combinations leave the callback
running. Node's own lib/timers.js shows why: clearTimeout requires
timer?._onTimeout, clearImmediate requires immediate?._onImmediate and returns
early otherwise, and clearInterval delegates to clearTimeout.

* chore: retrigger title and readiness checks after the title was corrected

* fix(javascript): say cross-family timer clearing is runtime-dependent

Review feedback from @morluto: the previous wording claimed clearing across the
timer handle families "returns without doing anything, so the timer keeps
running". That does not hold on every supported Node version. Measured on Node
24.22.1, all three cross-family pairs fire:

| scheduled with | cancelled with | Node 24.22.1 |
| --- | --- | --- |
| `setTimeout` | `clearImmediate` | fires |
| `setImmediate` | `clearTimeout` | fires |
| `setImmediate` | `clearInterval` | fires |

`clearImmediate` cancelling a `Timeout` on Node 22 is the case the old claim
missed. Within a family the clear always cancels, which is why the analysis
links only same-family pairs; across families the outcome depends on the
runtime, which is exactly why the analysis reports `unresolved` rather than
`complete` in either direction.

The behavior and the tests are unchanged. The `timerHandleFamily` doc comment,
the linked-origin comment, and the test comment now state the runtime
dependence and why it justifies an unresolved result.
2026-11-07 15:17:47 +08:00
Saurabh Sungh abfccc0683 fix(javascript): bound symbolic property and member queries (#8876)
Fixes #8838.

Admit all symbolic query entry points through the existing resolver budget, preserve cache-hit depth and restore entered frames in finally. Retain independent facts with partial source coverage instead of losing entire scripts to recursive spreads.

Validated 273 focused tests, check:fast/docs:check, real CLI/stdio-MCP projections and complete parent reads, independent public-source oracle, and unchanged full results for the official Electron control and Bitwarden selected preload.

Full technical CI 42308068679 passes, including all eight shards and required aggregates. Greptile reviews this head at 5/5; no completed Codex review of this head is available. The Author readiness-only exception is explicitly authorized; no human-review attestation is added.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
2026-11-06 23:13:15 +00:00
RRFZ 55ee36809d fix(javascript): apply package type scope to application sources (#7883)
* fix(javascript): apply package type scope to application sources

Application analysis parsed .js sources without their package scope, so an unbound require in a type-module package became a resolved CommonJS dependency. The parent now observes each source's nearest same-container package.json, stopping at node_modules, and the worker parses with that package type as reference imports already do.

Fixes #7323

* fix(javascript): keep package type unknown behind unavailable manifests

The package-type lookup indexed only readable files, so a nearer
package.json whose bytes were unavailable was skipped and an ancestor's
"type" applied to sources it does not own. Index unavailable manifests
by container and directory too, and stop the walk with "unknown" when
one is the nearest owner, as the Next app-context lookup already does.

* fix(javascript): keep Electron preloads outside module package scope

Electron's preload loader ignores a package's "type": "module" and gives
a .js preload `require`; ESM preloads need the .mjs suffix. Applying the
package type to every .js source marked such a preload as an ES module,
so its `require("electron")` edges and IPC pairing disappeared.

A source that requires an Electron module now keeps the context REA
inferred before package scope was applied. Node-loaded sources in a
module scope still drop unbound `require`.

* fix(javascript): admit Electron script loaders only on renderer evidence

The previous commit dropped a module package type whenever a source
required an Electron module. A Node-loaded ES module that requires
Electron, or one whose Electron call goes through a local `require`,
then regained the false require edges the package scope removes.

The parser applies the package type unconditionally again. The worker
re-reads a package-module script that requires an Electron module once
outside the package scope. The source keeps that context only when the
static pass there directly observes a contextBridge exposure or a
renderer IPC operation, which only Electron's renderer-side loaders
provide.

* fix(javascript): check Electron renderer evidence without a second static pass

b54845a6b ran the full static pass on an unscoped parse of every
package-module script that requires Electron, then discarded it when no
renderer operation turned up, so such main-process sources paid for two
full passes.

A candidate must now also name contextBridge or ipcRenderer, which any
renderer evidence needs. The evidence check runs only the static pass's
binding and Electron identity preamble with its exposure and IPC
descriptors. The full static pass runs once, on the chosen parse.

* fix(javascript): confirm Electron renderer evidence with the static pass

The focused evidence check from 4a86bca9b also counted renderer calls in
bundler factory defaults that the bundler convention never runs, which
the static pass skips. Such a default switched a type-module file to
script context, so its unrelated unbound requires became imports again.

The focused check now only screens. It follows the static pass's own
traversal, so a negative result still rules evidence out. A positive
result is confirmed by the static pass's direct findings on the unscoped
parse, and those findings become the file's facts when they hold.

* fix(javascript): confirm renderer evidence before wrapper substitution

f71236400 confirmed a screened source from the static pass's final
contextBridge and IPC findings, without wrapper substitutions. The
wrapper pass replaces a directly observed exposure inside a helper with
a substituted finding, so such a preload lost its imports again.

Before its wrapper pass, the static pass now records whether its source
traversal directly observed a contextBridge exposure or a renderer IPC
operation, and observedElectronRendererCall exposes that. The traversal
still skips bundler factory defaults that never run, and the reported
findings are unchanged.

* fix(javascript): resolve declared preload loaders before source analysis

* fix(javascript): leave preload discovery failures to source analysis

A source that failed during preload discovery threw out of the whole
application analysis, so one oversized source or a per-source timeout in a
module package returned no Evidence. Skip a source that exceeds its worker
limits and end discovery on any other failure except cancellation; source
analysis then meets the same source, records its failure and keeps partial
Evidence as it does without discovery.

* fix(javascript): report preload discovery gaps in Evidence limitations

When preload discovery skipped a source or stopped early, source analysis
could still read that source, so the result looked complete while a .js
preload declared there kept its package scope. Return each discovery gap
with the discovered paths and add it to the analysis limitations, which
both complete and partial Evidence carry.
2026-11-06 20:49:41 +08:00
Rishikesh Gowda K K 9044b6fb36 fix(kuna): mark project-dependent operations unavailable on export failure (#8748)
* fix(kuna): mark project-dependent operations unavailable on export failure

* fix(providers): preserve sibling operations in capability error copies

* fix(kuna): propagate sibling unavailability on export fallback

* fix(binary): preserve observed sibling recovery instructions across fallback observations

* fix(binary): build sibling unavailability from structured reasons without rewriting display text

* test(kuna): synchronize fallback test on settled export workspace retirement

* test(kuna): release and join export synchronization barrier across exit paths

* test(kuna): synchronize fallback test on observed export process exit

* test(kuna): gate fallback completion on settled export cache build

* test(kuna): preserve build promise chain and restore spy after teardown
2026-11-06 17:22:02 +08:00
gensokyo-dev 3e0599a949 fix(javascript): preserve loader identity across terminal origins (#9107)
Exclude only proven separate values from Webpack loader alias walks; preserve unsupported, projected and unresolved origins as unknown and retain the real alias-state bound.

Refs #7253. The full Bitwarden precision regression remains open.

Validation: 336 focused tests and check:fast passed; real Bitwarden preload identity retained; full main/preload completed with disclosed partial coverage.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
2026-11-06 07:59:03 +00:00
吴佳浩Alben 99bcd3e4e0 fix(javascript): report empty Electron coverage instead of silent zeroes (#8332)
* fix(javascript): report empty Electron coverage instead of silent zeroes

`analyze_javascript_application` reported `browser_windows: 0` and
`ipc.operations: 0` with only generic partial/unknown flags, so a bundle that
imports `electron` but whose Electron facts were not recovered read exactly like
an application with no IPC at all. Issue #8201 is that false negative on a real
Rolldown bundle.

Two additions, both scoped to "Electron is referenced but nothing was recovered":

**A machine-readable indicator.** `electron_summary` gains
`electron_semantics: "recovered" | "empty"`. It is absent when no analyzed source
links an Electron module, so its presence distinguishes "not an Electron
application" from "Electron coverage is empty". It follows the existing
`protocol_registrations.exactOptional()` precedent, so previously captured
evidence stays valid.

**A graph limitation.** The limitation list already carried a note when at least
one Electron fact was recovered but said nothing when zero were. It now states
that zero IPC operations and zero BrowserWindows mean empty coverage rather than
absence.

`electronModuleReferenced` is the single owner of the module test. It reuses
`ELECTRON_MODULE`, which matches exactly the supported entry points (`electron`,
`electron/common`, `/main`, `/renderer`, `/utility`), so `electron-helper` does
not count, and it ignores type-only links because importing Electron types
promises no boundary fact.

Five tests cover recovered, empty, not-referenced, type-only, and supported
subpath versus unrelated package. The empty, recovered and type-only cases fail
on unmodified `main`.

* chore: retrigger title and readiness checks after the title was corrected

* fix(javascript): report Electron coverage from retained facts

Four review findings on the coverage indicator.

**A rejected application projection hid the reference.** A file whose expanded
relationships exceed the result budget is replaced by a placeholder with no
module links before the summary runs, while `graphLimitations` reads the raw
analysis. The same response could then warn in prose and omit the field. The
summary now takes the retained analysis for coverage only, and both outputs
read one `electronSemanticsState`, so they cannot disagree.

**A skipped semantic analysis hid the reference too.** `electronModuleReferenced`
read only `semantic.ir.moduleLinks`, which is absent when semantic analysis is
skipped for a budget and drops `import("electron")` or `module.require("electron")`
forms. It now falls back to the retained `javascript.references`, which the static
producer records for those inputs, while still excluding `type-import` links when
the richer owner is available.

`electronBoundaryFactCount` is now the single owner of the fact total, replacing a
duplicate inline reduce in the graph builder.

**Omission no longer claims absence.** The schema comment said a missing
`electron_semantics` means the application does not use Electron. The predicate
only establishes that no retained fact names one, and unread sources, failed
analysis, and dynamic module names can hide its use, so the contract now says
unknown.

Two regressions cover a rejected projection and a skipped semantic analysis; both
fail on the previous commit.

* fix(javascript): count a dynamic Electron import as coverage

Review finding: `electronModuleReferenced` returned as soon as module links
existed, so a literal dynamic import was ignored. Verified against the two fact
owners:

    import("electron")   -> references: [dynamic-import electron]
                            moduleLinks: []
    require("electron")  -> references: [require electron]
                            moduleLinks: [require electron]

An application that only loads Electron through `import("electron")` therefore
got neither `electron_semantics: "empty"` nor the empty-coverage warning, and the
zero counts stayed unexplained.

The two owners now answer their own half instead of one short-circuiting the
other. Module links answer a runtime static import or require and keep excluding
`type-import`; the retained reference is read for every kind except
`static-import`, which is exactly the type-only risk. A dynamic specifier is
still never counted by either owner, so this remains an observed-reference claim
rather than an absence claim.

The regression uses `import("electron")` and fails on the previous commit.

* fix(javascript): read only module-loading Electron references

Two review findings, both verified against the static reference producer.

**A retained runtime static import was skipped.** The excluded kind was meant to
protect the type-only distinction, but the static analyzer records a whole
`import type { IpcMain } from "electron"` declaration as no reference at all:

    import type { IpcMain } from "electron"   -> references: []
    import { app } from "electron"            -> references: [static-import electron]
    require("electron")                       -> references: [require electron]
    import("electron")                        -> references: [dynamic-import electron]
    new Worker(new URL("electron", m))        -> references: [worker electron]

When semantic analysis is skipped for a budget the module links are absent, so
that exclusion dropped a real runtime ESM import and left the zero counts
unexplained.

**A worker URL path is not an Electron module.** `new Worker(new
URL("electron", import.meta.url))` records a `worker` reference whose specifier is
a path relative to the importing source, so counting it reported an ordinary
worker file as `electron_semantics: "empty"`.

`ELECTRON_REFERENCE_KINDS` is now an explicit allowlist of the kinds that load a
module by package specifier — `static-import`, `dynamic-import` and `require` —
so `worker` and `service-worker` URL paths are excluded. Module links are still
read first for the type-only distinction.

Three regressions cover a skipped-semantics ESM import, a skipped-semantics whole
type-only import, and a worker URL path; two of them fail on the previous commit.

* fix(javascript): own Electron applicability in the static producer

Review finding. A `worker` reference records a URL relative to the importing
source, so `new Worker(new URL("electron", import.meta.url))` names a local file
and is not an Electron module. The boundary owner already read only module-loading
reference kinds, but `finalizeStaticAnalysis` still treated every matching static
reference as `analysis_coverage.applicability.electron: "observed"`, which
`javascriptStaticSourceCoverageSummary` publishes. One response could therefore
say Electron was observed in its coverage view while the Electron summary omitted
`electron_semantics`.

`ELECTRON_REFERENCE_KINDS` now lives next to `ELECTRON_MODULE` in the domain owner
and is consumed by both the static producer and the Electron boundary owner, so the
two outputs cannot disagree. A worker URL path is `unknown` for both, while a
`require("electron")` is `observed` for both.

The regression asserts the domain applicability fact and the boundary summary agree
for a worker URL path; it fails when the producer stops filtering by kind.

* fix(javascript): require a loaded Electron module for applicability

Review finding. A CommonJS `require.resolve("electron")` records a `require`
reference with `operation: "resolve"`: it locates the module without loading it,
so no Electron boundary fact is expected. Both predicates dropped the operation,
so such a source reported `electron_semantics: "empty"` and the missing-coverage
warning even though Electron was never loaded.

Measured on the static producer:

    require.resolve("electron")  -> references: [require resolve electron]
                                    applicability.electron: unknown
    require("electron")          -> references: [require load electron]
                                    applicability.electron: observed
    import("electron")           -> references: [dynamic-import load electron]
                                    applicability.electron: observed

`electronModuleReferenced` and `finalizeStaticAnalysis` now both require
`operation === "load"`, so the boundary summary and the coverage view stay aligned
and reachability-only references no longer claim missing coverage.

The regression asserts a resolve-only source produces neither the field nor the
warning, and it fails when the operation check is removed.

* fix(javascript): classify Electron loads in the static fact owner

Review finding, and the correct home for the previous two rounds of the same
complaint. A Webpack/Rspack factory may name a local module `"electron"`, and the
static producer then records a loaded `require` reference whose specifier is that
bundle module key. Treating the key as a package specifier reported
`electron_semantics: "empty"` plus the missing-coverage warning for a bundle that
never loads Electron.

Three changes, all in the domain owner, so no consumer re-derives the question:

- `JavaScriptModuleRangeIndex.hasKey` answers whether a specifier is a recovered
  bundle module key. A `require` whose specifier is such a key loads a module
  inside the artifact.
- `electronObserved` in `finalizeStaticAnalysis` now requires a loaded module-load
  reference whose specifier is not a bundle key. The published
  `analysis_coverage.applicability.electron` therefore owns every exclusion:
  `type-import`, `require.resolve`, `worker`/`service-worker` URL paths, and
  bundle-local keys.
- `electronModuleReferenced` consumes that owned fact instead of re-deriving it,
  keeping the boundary summary and the coverage view from disagreeing. Historical
  evidence without the field still falls back to the retained references.

Measured:

    bundle-local module ID "electron"  -> applicability unknown
    require("electron")                -> applicability observed
    webpack external n(45840)          -> applicability observed

The regression covers both the local key and the real external in the same
factory shape; it fails without the bundle-key exclusion.

* fix(javascript): identify a bundle module by its loader, not its key

Both review findings were right, and they show why the previous key-set approach
was wrong: bundle-key membership is a property of the file, but whether a
specifier names a module inside the artifact is a property of the reference.

`JavaScriptStaticReference` now records the loader that produced it, and
`electronObserved` excludes `factory-loader` references instead of every specifier
that happens to match a recovered module key. Measured on Node 24.22.1:

    local module keyed "electron"                 -> unknown  (factory-loader)
    external 45840 = require("electron")          -> observed (commonjs-loader)
    factory keyed "electron" that loads the pkg   -> observed (commonjs-loader)

The third case is the regression: a bundle whose factory is *named* `"electron"`
but whose body does `e.exports = require("electron")` is a real Electron load, and
the old key-set check suppressed it. The real load keeps its `commonjs-loader`
identity even when the same specifier also names a factory, so one response can
rely on the loader while the other relies on the key without either lying.

`JavaScriptModuleRangeIndex.hasKey` is removed; it answered the wrong question.

One regression covers all three shapes and fails when the `factory-loader`
exclusion is dropped.

* fix(javascript): carry the reference loader across the worker boundary

The worker schema is strict, so the new `loader` field broke the worker handshake:
`unrecognized_keys` on every `references` entry. That surfaced in CI as both a
worker-schema test failure and a package-baseline failure reporting
`worker_failure_reason: "io"` for a one-file Electron app.

`referenceShape` now accepts the same three loader identities as the domain type, so
the fact survives the worker boundary instead of collapsing every analysis to an
execution failure.

Verified: `JavaScriptAnalysisWorkerSchemas.test.ts` 10 passed, and the full
domain/application/javascript suite is 5484 passed | 10 skipped. The remaining
`SourceMapCommand` and `DarwinHelperCache` findings are present on unmodified
`main` and are not from this change.

* fix(javascript): remove the superseded key set and forward a deferred loader

Two review findings.

**The `keys` set is dead.** `JavaScriptModuleRangeIndex.keys` is left over from the
abandoned key-membership approach: the loader-identity refactor removed `hasKey` and
the write in `add`, so nothing reads it. Removed.

**The deferred require path dropped the loader identity.** `requireCallSpecifier`
returns a `loader` for `commonjs-loader`, `factory-loader` and `create-require`, and the
direct `addReference` call forwards it, but `finishModuleRequireCalls` did not. A
`createRequire` loader saved under a member literally named `require` therefore lost
its identity:

    import { createRequire } from "node:module";
    const holder = { require: createRequire(import.meta.url) };
    holder.require("x");

    before: require "x" with loader undefined
    after:  require "x" with loader "create-require"

Three regressions cover it; one fails without the change.

I did **not** change the inline-type-only import case. `isTypeOnlyImportDeclaration`
deliberately keeps a declaration whose bindings are all inline type-only, because
`hasUncertainTypeOnlyModuleSideEffect` reports that decision as
`type-only-module-side-effect` uncertainty: whether TypeScript emits the import depends
on the consumer's compiler options. Narrowing it to erased broke
`javascriptStaticCoverage.test.ts` and `javascriptSemanticAnalysis.require_links.test.ts`,
which assert exactly that contract. If Electron applicability should ignore that
reference, the decision belongs where applicability is derived, not in the shared
declaration predicate — say the word and I will do it there instead.

* fix(javascript): keep a commonjs-loader reference out of the bundle module index

Review finding. `finalizeLocatedFindings` attaches `module_runtime` to every finding
that sits inside a factory module range, whoever produced it. `resolveReference` then
sent every reference carrying `module_runtime` through the bundle-module lookup, so a
real CommonJS load inside a factory body resolved to the local module key that shared
its name:

    "electron": (e, t, n) => { e.exports = require("electron") }

    fact:  specifier "electron", loader "commonjs-loader", module_key "electron"
    before: resolved through the bundle module index to `#module:electron`
    after:  resolves as an external package specifier

Only a `factory-loader` specifier names a recovered bundle module key, which is what
`loader` already records after the loader-identity change. The module lookup is now
gated on it, and an absent `loader` keeps the previous behaviour for older findings.
2026-11-06 15:15:43 +08:00
吴佳浩Alben 172f248884 fix(javascript): emit no fs acquisition for a rejected literal mode (#8624)
* fix(javascript): emit no fs acquisition for a rejected literal mode

`fs.open`, `fs.openSync` and `fs/promises.open` validate the `mode` in argument 2 before
touching the filesystem, so a mode Node rejects throws synchronously and no descriptor
exists. The guard read only the flag, so a rejected mode kept its acquisition and any
linked release.

Measured on Node 24.22.1:

    fs.openSync(p, "r", -1)          -> THREW ERR_OUT_OF_RANGE
    fs.openSync(p, "r", 1.5)         -> THREW ERR_OUT_OF_RANGE
    fs.openSync(p, "r", NaN)         -> THREW ERR_OUT_OF_RANGE
    fs.openSync(p, "r", 1e21)        -> THREW ERR_OUT_OF_RANGE
    fs.openSync(p, "r", true)        -> THREW ERR_INVALID_ARG_TYPE
    fs.openSync(p, "r", 0n)          -> THREW ERR_INVALID_ARG_TYPE
    fs.openSync(p, "r", {})          -> THREW ERR_INVALID_ARG_TYPE
    fs.openSync(p, "r", "8")         -> THREW ERR_INVALID_ARG_VALUE
    fs.openSync(p, "r", "")          -> THREW ERR_INVALID_ARG_VALUE
    fs.openSync(p, "r", "0o644")     -> THREW ERR_INVALID_ARG_VALUE
    --- accepted ---
    fs.openSync(p, "r", 0o644)       -> ok
    fs.openSync(p, "r", 2147483647)  -> ok
    fs.openSync(p, "r", -0)          -> ok
    fs.openSync(p, "r", "0644")      -> ok
    fs.openSync(p, "r", null)        -> ok
    fs.openSync(p, "r", void 0)      -> ok

The guard decides only exactly-readable literals. A numeric mode is validated as an
unsigned 32-bit integer; a string mode must be a canonical octal digit string, padded
leading zeros allowed, and its value must stay inside uint32; `null`, `undefined` and
`void <expr>` default the mode and are kept, as is a computed value.

Three boundaries are deliberate:

- Values in `[2^31, 2^32-1]` **abort the Node process** rather than throwing
  (`Assertion failed: args[2]->IsInt32()`), so they stay undecided instead of being
  claimed as a synchronous throw.
- A spread before the mode slot shifts the runtime position.
- A unary chain is resolved through its innermost readable operand, and a nested
  boolean or string result is left undecided rather than applied as a number.

Regressions cover both directions. Full suite here: 5466 passed | 7 skipped.

* fix(javascript): remove the superseded string mode check

Review finding. `rejectsOpenModeLiteral` builds `octal` from a string literal or a
no-expression template and returns inside that branch for every value it matched. A
`StringLiteral` therefore never reaches the later `isStringLiteral` check, which cannot
run and is a second copy of the canonical-octal rule to maintain.

* test(javascript): cover the spread and boundary paths of the mode guard

Review feedback from @greptile-apps: the new mode tests skipped both paths the
guard deliberately leaves undecided. Two spread cases are added:

- `openSync(...args, "r", "0600")`, where a spread in the flag slot shifts the
  runtime position of the mode;
- `openSync(p, "r", ...modes)`, where a spread occupies the mode slot itself.

Both must keep the acquisition, because neither establishes the mode value. The
boundary cases were already present: `2147483647` and `2147483648` are keep
cases (`2147483648` aborts the process rather than throwing) and `4294967296` is
the reject case, alongside the octal `37777777777` keep and `40000000000` reject
pair.

127 cases pass in this file.
2026-11-06 15:14:03 +08:00
gensokyo-dev 3708538e85 feat(javascript): compare static endpoints with HAR captures (#9090)
* feat(javascript): compare static endpoints with HAR captures

* test(javascript): preserve original HAR fixture bytes during formatting

* test(javascript): load the byte-preserved HAR fixture
2026-11-06 13:34:26 +08:00
highmachs 7b6fc6f13d fix(javascript): resolve electron findings through aliases of interop results (#8914)
* fix(javascript): resolve electron findings through aliases of interop results

* fix(javascript): resolve interop aliases iteratively and check target mutations

* fix(javascript): retain possible writes through uncertain aliases and guard positive reads

* perf(javascript): cache alias change status across semantic interop lookups
2026-11-05 18:31:52 +08:00
ShuaixinHuang a2134b3a74 fix(javascript): resolve module-level Electron options in functions (#8960)
* fix(javascript): resolve module-level Electron options in functions

* fix(javascript): limit module option ancestry walk

* fix(javascript): preserve uncertain captured argument ordering

* fix(javascript): retain hoisted activation uncertainty

* fix(javascript): retain function ownership through loops

* fix(javascript): account for classic global activations

* fix(javascript): bind first activations to lexical identities

* fix(javascript): retain uncertain global activation timing

* fix(javascript): isolate receiver facts and share computed reads

* fix(javascript): retain projected and opaque global activations

* fix(javascript): refresh activation bounds as opaque effects grow

* fix(ci): format website watch directories without changing configuration

* fix(javascript): share source reads across opaque-effect updates

* fix(javascript): retain constructed-code global activation bounds

* fix(javascript): share runtime activation and receiver facts

* fix(javascript): reuse pattern reads and function positions

* fix(javascript): isolate copied paths and preserve runtime reads

* style(javascript): format migrated reference imports

* fix(javascript): normalize constructor activation and receiver effects

* fix(javascript): retain aliased receiver timing and lexical global this

* fix(javascript): preserve effect-free receiver identity across activations

* fix(javascript): retain outer activation and prototype effects

* fix(javascript): index implicit reads without deep path rescans

* fix(javascript): retain aliased prototype and parameter read uncertainty

* fix(javascript): index reflected reads through intrinsic invocation facts

* fix(javascript): retain unresolved global activation reads

* chore: refresh PR branch after review fix publication

* fix(javascript): retain projected receiver reads and cache method facts

* fix(javascript): retain inherited and implicit activation reads

* fix(javascript): retain caller-owned this uncertainty

* fix(javascript): retain reflected reads with unresolved spread arguments

* fix(javascript): preserve enclosing activation for decorators

* fix(javascript): track intrinsic literal prototype effects

* fix(javascript): retain catch activation from implicit reads

* perf(javascript): reuse effective keys for projected reads

* fix(javascript): retain possible caller-owned ambient effects
2026-11-05 16:52:19 +08:00
morlutoandCursor Agent 89317a39fa fix(javascript): remove duplicate childNodes import (#8886)
* fix(javascript): remove duplicate childNodes import

The squash of #7967 re-added the import already introduced in afc18131f,
so typecheck failed with TS2300.

* test(gdb): expect the command-output cap limitation

The retained-record limitation now names the command-output setting.
The coverage assertion still required the previous fixed-cap sentence.

* test(cli): keep rejected mcp add values off Amp settings

mcp add now rejects a flag in the --command value position. The Amp
registration test still treated that invocation as a successful selection.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-11-04 20:43:04 +08:00
Mohamed Ayoub Eleuch 09c889b3b5 test(javascript): cover Request endpoint identity and provenance (#6365)
fix(javascript): resolve inline Request URLs in fetch calls
2026-11-04 19:48:56 +08:00
吴佳浩Alben 54d819f382 fix(javascript): emit no timer for a literal non-function callback (#8044)
* fix(javascript): emit no timer for a literal non-function callback

Node validates the timer callback before creating a handle:

    if (typeof callback !== 'function')
      throw new ERR_INVALID_ARG_TYPE('callback', 'function', callback);

The throw is synchronous, before any timer exists, but the analysis reported a
`schedule` operation for such calls.

Measured on Node 24.22.1:

    setTimeout("code", 0)    -> THROWS ERR_INVALID_ARG_TYPE
    setTimeout(`code`, 0)    -> THROWS ERR_INVALID_ARG_TYPE
    setTimeout(1, 0)         -> THROWS ERR_INVALID_ARG_TYPE
    setTimeout(1n, 0)        -> THROWS ERR_INVALID_ARG_TYPE
    setTimeout(true, 0)      -> THROWS ERR_INVALID_ARG_TYPE
    setTimeout(null, 0)      -> THROWS ERR_INVALID_ARG_TYPE
    setTimeout(void 0, 0)    -> THROWS ERR_INVALID_ARG_TYPE
    setTimeout([], 0)        -> THROWS ERR_INVALID_ARG_TYPE
    setTimeout({}, 0)        -> THROWS ERR_INVALID_ARG_TYPE
    setTimeout()             -> THROWS ERR_INVALID_ARG_TYPE
    setImmediate("code")     -> THROWS ERR_INVALID_ARG_TYPE
    setInterval("code", 0)   -> THROWS ERR_INVALID_ARG_TYPE
    --- accepted by Node ---
    setTimeout(function(){}, 0) -> OK
    setTimeout(() => {}, 0)     -> OK
    setTimeout(fn, 0)           -> OK
    setTimeout(class {}, 0)     -> OK (fails only when the class is invoked)

So a schedule is dropped only for an exactly readable literal that is not a
function, or for a missing argument. An identifier, a call result or a spread may
still be a function at runtime and keeps its schedule.

* chore: retrigger title and readiness checks after the title was corrected

* fix(javascript): scope the timer callback rule to Node's timers module

greptile flagged two problems with the first version.

P1 - browser timers disappeared. The rule was applied to bare globals, but
browser and Electron renderer sources reach this analysis without a Node/browser
distinction, and there `setTimeout("refresh()", 0)` is a real timer: the host
evaluates the string. The rule now applies only when the timer is imported from
`node:timers`, where that form cannot exist. A bare global schedule keeps its
fact whatever argument 0 holds.

P2 - unused import. `readExactJavaScriptLiteral` was imported but never used; the
helper reads shapes through `unwrapJavaScriptExpression`. Removed.

Measured on Node 24.22.1 (the throw is what makes a node:timers schedule
impossible; the bare-global case is intentionally left alone):

    import { setTimeout } from "node:timers";
    setTimeout("code", 0)  -> THROWS ERR_INVALID_ARG_TYPE
    setTimeout(null, 0)    -> THROWS ERR_INVALID_ARG_TYPE
    setTimeout(fn, 0)      -> OK
    --- bare global, host may be a browser ---
    setTimeout("code", 0)  -> kept as a schedule

24 cases cover both sides plus the cancellation link.

* fix(javascript): reject every non-function global and unary timer callback

Reviewing the timer callback guard against Node 24.22.1 found two gaps. Both are
in the same "a call that always throws must not emit a resolved fact" family.

**An unmodified global that is not a function was accepted.** The guard read
literal nodes only, so a bare global identifier passed through even though Node
rejects it before creating a handle:

    setTimeout(undefined)  -> THREW ERR_INVALID_ARG_TYPE
    setTimeout(NaN)        -> THREW ERR_INVALID_ARG_TYPE
    setTimeout(Infinity)   -> THREW ERR_INVALID_ARG_TYPE

`isUnmodifiedGlobalReference` keeps a shadowed binding out of the rule, so a
local `const NaN = () => {}` keeps its schedule.

**Only `void` was treated as a non-function unary operator.** Every unary operator
yields a primitive, and Node rejects a primitive callback:

    setTimeout(!0)         -> THREW ERR_INVALID_ARG_TYPE
    setTimeout(~0)         -> THREW ERR_INVALID_ARG_TYPE
    setTimeout(-1)         -> THREW ERR_INVALID_ARG_TYPE
    setTimeout(+"1")       -> THREW ERR_INVALID_ARG_TYPE
    setTimeout(typeof 1)   -> THREW ERR_INVALID_ARG_TYPE

A class expression is still kept, because it passes Node's own `typeof` check and
only fails when invoked.

Fifteen regressions cover the rejected forms plus arrow, identifier, class,
spread and shadowed-global cases; ten fail on the previous commit.

* fix(javascript): let sloppy eval shadow the non-function global callback

Review finding. The new global check read `undefined`, `NaN` and `Infinity` as
unmodified globals, but sloppy `eval` can introduce a local function under one of
those names, which then shadows the global. Measured on Node 24.22.1 in script
scope:

    function g() { eval("var undefined = () => {}"); setTimeout(undefined, 0); }
    -> schedules a timer
    function g() { eval("var NaN = () => {}"); setTimeout(NaN, 0); }
    -> schedules a timer

`semanticOpaqueNameMayHaveChanged` now has to agree that no visible activation can
introduce such a name before the global reading is conclusive. In a module the
same `eval` is strict and introduces no outer binding, so the global reading still
holds and the timer is still dropped.

Three regressions cover the sloppy forms, and a fourth pins the no-eval cases as
still dropped. The three sloppy cases fail without the guard.
2026-11-04 19:45:02 +08:00
吴佳浩Alben b668cea2af fix(javascript): emit no fs acquisition for a literal non-flag string (#8125)
* fix(javascript): emit no fs acquisition for a literal non-flag string

`fs.open` and `fs.openSync` validate a literal flag before touching the
filesystem:

    if (!isFd(flags) && !FLAGS_SET.has(flags))
      throw new ERR_INVALID_ARG_VALUE('flags', flags, ...);

The throw is synchronous, before a descriptor exists, but the analysis reported
an `acquire` operation for those calls.

Measured on Node 24.22.1 against a file that does not exist, so each valid flag
is exercised without an EEXIST masking it:

    ACCEPTED as a flags string:
      a a+ as as+ ax ax+ r r+ rs rs+ sa sa+ sr sr+ w w+ wx wx+ xa xa+ xw xw+
    REJECTED with ERR_INVALID_ARG_VALUE:
      "" A R W rb wb s s+ x r,x

A Python-style `"rb"`/`"wb"` is the common case: those are valid in Python and
fatal at the Node call site.

`FS_OPEN_FLAGS` in `javascriptStaticAnalysisHelpers.ts` already holds exactly
those 22 accepted flags. It is now exported and reused so the accepted set has
one owner. A numeric flag is a platform bitmask and a computed value may still be
valid, so only an exactly readable non-flag string is dropped.

`fs.createReadStream` keeps its acquisition: it does not validate the flag
synchronously, it returns a stream and reports the failure on the error event.

* chore: retrigger title and readiness checks after the title was corrected

* fix(javascript): reject every fs flag literal Node rejects

The first version only decided string flags. Node's check covers every flag
shape, and each one below throws `ERR_INVALID_ARG_VALUE` or `ERR_OUT_OF_RANGE`
synchronously, before a descriptor exists:

    openSync(p, 1.5)      -> THREW ERR_OUT_OF_RANGE
    openSync(p, -1)       -> THREW EINVAL
    openSync(p, -1.5)     -> THREW ERR_OUT_OF_RANGE
    openSync(p, 1e400)    -> THREW ERR_OUT_OF_RANGE
    openSync(p, NaN)      -> THREW ERR_OUT_OF_RANGE
    openSync(p, Infinity) -> THREW ERR_OUT_OF_RANGE
    openSync(p, 0n)       -> THREW ERR_INVALID_ARG_VALUE
    openSync(p, true)     -> THREW ERR_INVALID_ARG_VALUE
    openSync(p, false)    -> THREW ERR_INVALID_ARG_VALUE
    openSync(p, {})       -> THREW ERR_INVALID_ARG_VALUE
    openSync(p, [])       -> THREW ERR_INVALID_ARG_VALUE
    --- accepted ---
    openSync(p, 0) / 1 / 65536 / +5   -> ok
    openSync(p) / undefined / null    -> ok
    openSync(p, "r") / "wx" / "sr"    -> ok

`NaN` and `Infinity` are identifiers rather than literals, so they are decided
only through `isUnmodifiedGlobalReference`; a shadowed binding keeps the
acquisition.

* fix(javascript): judge an fs flag by its value, and ignore a shifting spread

Two P1 findings on the first version.

**A unary operator was judged by its operand.** `openSync(p, void 1.5)` passes
`undefined`, so Node applies the default read flag, but the branch saw a
fractional operand and dropped a real acquisition. `openSync(p, -0)` is the valid
zero bitmask and was also dropped. `staticFlagBits` now evaluates the operator:

    openSync(p, void 1.5)   -> ok      (keeps the acquisition)
    openSync(p, void 0)     -> ok
    openSync(p, -0)         -> ok
    openSync(p, -0.0)       -> ok
    openSync(p, +0)         -> ok
    openSync(p, ~-1)        -> ok
    openSync(p, -1)         -> THREW EINVAL
    openSync(p, -1.5)       -> THREW ERR_OUT_OF_RANGE
    openSync(p, +1.5)       -> THREW ERR_OUT_OF_RANGE
    openSync(p, !0)         -> THREW ERR_INVALID_ARG_VALUE
    openSync(p, ~0)         -> THREW EINVAL
    openSync(p, typeof 1)   -> THREW ERR_INVALID_ARG_VALUE

**A spread before the flag slot shifts the runtime position.** With
`args = [path, "w"]`, `openSync(...args, "0600")` opens with mode `"0600"`, but
argument 1 is then the mode rather than the flag. The check now keeps the
acquisition whenever argument 0 is a spread, because the flag it inspects is not
provably the flag at runtime.

* fix(javascript): leave a signed fs bitmask undecided

greptile caught that the flag check rejected every negative integer, but Node
accepts some signed bitmasks. Measured on Node 24.22.1 against an existing file:

    openSync(p, -2147483648)   -> ok
    openSync(p, -1073741824)   -> ok
    openSync(p, -1)            -> THREW EINVAL
    openSync(p, -2)            -> THREW EINVAL
    openSync(p, 2147483647)    -> THREW EINVAL
    openSync(p, 2147483648)    -> THREW ERR_OUT_OF_RANGE
    openSync(p, -2147483649)   -> THREW ERR_OUT_OF_RANGE

So the sign does not decide: some negative values open a descriptor and others do
not, and which is which is a platform bitmask question this analysis cannot
settle. The check now rejects only a non-integer or a value outside the signed
32-bit range, and leaves an in-range negative undecided — the same choice
`greptile` asked for, on the principle that a call which may succeed must keep
its acquisition.

`!`, `typeof` and `delete` are still rejected, because those yield a boolean or a
string regardless of the operand.

* fix(javascript): read a no-expression template literal flag

Review finding. A no-expression template literal is exactly readable, so it is the
same flag string Node receives, but the guard only read `StringLiteral`:

    fs.openSync(p, ` + "`rb`" + `)   -> THREW ERR_INVALID_ARG_VALUE
    fs.openSync(p, ` + "`r`" + `)    -> ok

The flag slot now accepts an interpolation-free template; an interpolated template
stays undecided.

Four regressions cover both directions; two fail without the change.
2026-11-04 19:43:33 +08:00
吴佳浩Alben f285fd4f82 fix(javascript): emit no child process for a literal non-object options slot (#8019)
* fix(javascript): emit no child process for a literal non-object options slot

Node validates the child_process options object before creating anything, so a
literal primitive or a literal array in that slot throws ERR_INVALID_ARG_TYPE
synchronously and no child process exists. The analysis still reported a complete
spawn for them.

Measured on Node 24.22.1:

    spawn(cmd, "status")        -> THROWS ERR_INVALID_ARG_TYPE
    spawn(cmd, [], "pipe")      -> THROWS ERR_INVALID_ARG_TYPE
    spawn(cmd, [], null)        -> THROWS ERR_INVALID_ARG_TYPE
    spawn(cmd, [], [])          -> THROWS ERR_INVALID_ARG_TYPE
    execFile(cmd, "status")     -> THROWS ERR_INVALID_ARG_TYPE
    fork(worker, "status")      -> THROWS ERR_INVALID_ARG_TYPE
    fork(worker, [], "pipe")    -> THROWS ERR_INVALID_ARG_TYPE
    --- accepted by Node ---
    spawn(cmd, args, undefined) -> SPAWNED
    spawn(cmd, args, {})        -> SPAWNED
    execFile(cmd, [], null)     -> SPAWNED
    fork(worker, [], null)      -> SPAWNED
    exec(cmd, "inherit")        -> SPAWNED

The asymmetries matter: `spawn` rejects `null` and exempts only `undefined`,
while `fork` and `execFile` skip any nullish options, and `exec` spreads its
options and validates none of them. Drop a candidate only when the options slot
is a provable literal primitive or array, so an unresolved or non-literal value
stays a candidate.

`spawn(cmd, [], null)` is the sharpest case: it was indistinguishable from the
valid `undefined` form, including a resolved connects-stdio relation, for a call
that dies before any child exists.

* chore: retrigger title and readiness checks after the title was corrected

* fix(javascript): reject literal options Node shall reject, and drop a stray file

Two review findings on #8019.

greptile: `rejectsOptionsShape` still kept `spawn("tool", [], 0n)`, `-0` and
`1e400`, which Node also rejects before creating a child
(`ERR_INVALID_ARG_TYPE: The "options" argument must be of type object`). Those
forms are exactly the ones `readExactJavaScriptLiteral` refuses to price — a
BigInt, a non-finite numeric literal and a negated numeric literal — so read the
shape directly instead of widening the shared exact-value reader. `NaN` is an
identifier rather than a literal, so it still needs scope resolution and is left
as a documented gap.

Also remove `.rea-hopper-stage-YOryZ6/`, a local Hopper installation stub that an
`add -A` swept into the commit. It is unrelated to this change.

* fix(javascript): keep a void options slot, reject ! and ~

greptile flagged that `isUnpricedPrimitiveLiteral` dropped `spawn("tool", [], void 0)`,
`fork(..., void 0)` and `execFile(..., void 0)`. It checked the unary operand without
the operator, but `void <expr>` evaluates to `undefined`, which is the one options
value Node accepts in place of an object.

Measured on Node 24.22.1:

    spawn(cmd, [], void 0) -> SPAWNED
    spawn(cmd, [], void 5) -> SPAWNED
    spawn(cmd, [], -0)     -> THROWS ERR_INVALID_ARG_TYPE
    spawn(cmd, [], -5)     -> THROWS ERR_INVALID_ARG_TYPE
    spawn(cmd, [], +5)     -> THROWS ERR_INVALID_ARG_TYPE
    spawn(cmd, [], !0)     -> THROWS ERR_INVALID_ARG_TYPE
    spawn(cmd, [], ~0)     -> THROWS ERR_INVALID_ARG_TYPE

So the operator now decides: `void` keeps the spawn, `!` and `~` reject whatever
their operand, and `+`/`-` reject only when the operand is a numeric literal.

* fix(javascript): reject a non-finite global in the child-process options slot

An unshadowed `NaN` or `Infinity` in the options slot is a number, and Node's
options check rejects a number exactly as it rejects any other primitive. The
guard read only literal nodes, so the global identifier passed through. Measured
on Node 24.22.1:

    spawn("e", NaN)        -> THREW ERR_INVALID_ARG_TYPE
    spawn("e", Infinity)   -> THREW ERR_INVALID_ARG_TYPE
    execFile("e", NaN)     -> THREW ERR_INVALID_ARG_TYPE
    fork("x.js", NaN)      -> THREW ERR_INVALID_ARG_TYPE
    --- accepted ---
    exec("e", NaN)         -> ok, because exec validates no options
    spawn("e", undefined)  -> ok, the one accepted global
    spawn("e", null)       -> ok
    spawn("e", []) / ({})  -> ok

`isUnshadowedGlobal` keeps a local binding named `NaN` out of the rule, so a
shadowed value keeps its fact. Seven regressions cover the rejected forms plus
`exec`, `undefined`, `null`, an array, an object and a shadowed `NaN`; all seven
fail on the previous commit.
2026-11-04 19:39:43 +08:00
吴佳浩Alben 3640f01512 fix(javascript): emit no promise for a literal non-callable executor (#8264)
* fix(javascript): emit no promise for a literal non-callable executor

Node's `Promise` constructor calls the executor synchronously and throws a
`TypeError` when it is not callable, before any promise object exists. The
analysis reported a `constructor` promise operation with a hardcoded
`sourceResolution: "complete"`, which the graph projects as
`creates-promise` `resolved`.

Measured on Node 24.22.1:

    new Promise()                        -> THREW TypeError
    new Promise(1) / (-1)                -> THREW TypeError
    new Promise("work") / (`work`)       -> THREW TypeError
    new Promise(true) / (!0)             -> THREW TypeError
    new Promise(null) / (void 1)         -> THREW TypeError
    new Promise(1n) / (/re/)             -> THREW TypeError
    new Promise([]) / ({})               -> THREW TypeError
    --- accepted ---
    new Promise(() => {})                -> OK Promise
    new Promise(async () => {})          -> OK Promise
    new Promise(function () {})          -> OK Promise
    new Promise(class {})                -> OK Promise

A class passes Node's own `typeof` check, so it keeps its operation. An
identifier, a call result or a spread may still supply a function and also keeps
its operation.

The filter gates the candidate push rather than `promiseCandidateDetails`, which
also decides whether an `await` becomes an `awaited-expression`: gating there
made `await new Promise(1)` emit a spurious task fact, whereas gating at the push
site makes it emit nothing, which is the honest answer.

* chore: retrigger title and readiness checks after the title was corrected

* fix(javascript): reject a non-finite executor, not only a literal

greptile caught that `new Promise(1e309)` still emitted a promise. Babel stores
that literal as `Infinity`, and `readExactJavaScriptLiteral` refuses to price a
non-finite number, so the guard missed it.

Measured on Node 24.22.1 — every one throws before a promise exists:

    new Promise(1e309)     -> THREW TypeError
    new Promise(1e400)     -> THREW TypeError
    new Promise(Infinity)  -> THREW TypeError
    new Promise(-Infinity) -> THREW TypeError
    new Promise(NaN)       -> THREW TypeError

A numeric literal is now read directly rather than through the exact-value
reader. `Infinity` and `NaN` are identifiers rather than literals, so they are
decided through `isUnmodifiedGlobalReference`; a shadowed binding keeps its
promise, which is the same scoping rule the timer fix uses.
2026-11-04 19:36:45 +08:00
Wenyu Chiou 6aeaa04e54 fix(javascript): reuse shared AST child traversal (#7967) 2026-11-04 19:36:00 +08:00
吴佳浩Alben d5ba7cec60 fix(javascript): link a response body reader only to a settled fetch (#8028)
* fix(javascript): link a response body reader only to a settled fetch

`collectResponseConsumers` treated any `.json()`/`.text()`/`.arrayBuffer()` on a
binding holding a request result as a response consumer, and gave it the
request's endpoint at `resolution: "complete"`. That holds only for a settled
`fetch`.

Measured on Node 24.22.1:

    await fetch(u)               -> Response      json/text/arrayBuffer = function
    fetch(u) unawaited           -> Promise       json = undefined; pending.json() THROWS TypeError
    http.get(u)                  -> ClientRequest json/text/arrayBuffer = undefined
    http.request(u)              -> ClientRequest json = undefined; request.text() THROWS TypeError
    new WebSocket(u)             -> WebSocket     socket.json() THROWS TypeError
    await http.get(u)            -> ClientRequest (await does not help)

So four shapes emitted a resolved "this endpoint's body is read here" fact for a
call that throws before any body is read: an un-awaited fetch, `http.get`,
`http.request`, and a `WebSocket` handshake.

Link only requests that settle to a `Response`: a `fetch` call whose value is
awaited through expression wrappers. A binding that also holds a request kind
without a body reader keeps the consumer a candidate (`partial`) rather than a
proven one, and a binding with no settled fetch emits no consumer at all, which
matches the `collectReleases` / `forkRejectsStdioArray` precedent — the receiver
provably has no such member, so there is no fact to report.

* chore: retrigger title and readiness checks after the title was corrected
2026-11-04 19:35:16 +08:00
Tawhidul IslamandTawhidul Islam 703c11c90f fix(javascript): recover configuration file reads from fs/promises (#8446)
* fix(javascript): recover configuration file reads from fs/promises

* fix(javascript): disallow nonexistent readFileSync on fs/promises

---------

Co-authored-by: Tawhidul Islam <276017696+FhDBro@users.noreply.github.com>
2026-11-04 19:23:52 +08:00
吴佳浩Alben 007e181f68 fix(javascript): require exactly one fork IPC channel (#8017)
* fix(javascript): require exactly one fork IPC channel

Node's fork() rejects an explicit stdio array that carries more than one IPC
channel, and the throw is synchronous, before any child process exists:

    if (ArrayPrototypeFilter(options.stdio, ...'ipc').length > 1)
      throw new ERR_IPC_ONE_PIPE()

The analysis already dropped an array with no "ipc" (#7992) but still reported a
resolved fork for an array carrying it twice.

Measured on Node 24.22.1:

    fork({stdio: ["ipc", "ipc"]})                -> THROWS ERR_IPC_ONE_PIPE
    fork({stdio: ["pipe", "ipc", "ipc"]})        -> THROWS ERR_IPC_ONE_PIPE
    fork({stdio: ["ipc"]})                       -> SPAWNED connected=true
    fork({stdio: ["pipe", "ipc"]})               -> SPAWNED connected=true
    fork({stdio: ["pipe", "pipe", "pipe", "ipc"]})-> SPAWNED connected=true

So the required count is exactly one, not at least one. Count the resolved "ipc"
literals and reject the candidate unless the count is one; a spread or an
unreadable element may still change the count and is left alone, as before.

* chore: retrigger title and readiness checks after the title was corrected
2026-11-04 19:11:35 +08:00
ShuaixinHuang 19082acddb test(electron): consolidate handler presence coverage in public workflows 2026-11-03 23:11:45 +08:00
ShuaixinHuang c28d47bf13 fix(electron): retain wrapped protocol handler registrations 2026-11-03 23:11:45 +08:00
Lewis WangandClaude Fable 5.1 b912ec184c fix(javascript): accept retained empty endpoint seeds (#6462)
Application analysis preserves the complete literal request endpoint,
including the empty string, and the event seed already accepts a
retained empty name. The endpoint seed still required a non-empty
value, so trace-javascript-semantics refused to select a retained ""
endpoint with invalid_request.

The endpoint seed now accepts any string and keeps exact matching.
Boundary-field seeds keep their minimum because boundary nodes are only
produced from method names.

Found while fixing #6458; the event half of that report landed in
cf5e4f6fa.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 10:10:33 +08:00
2d11fd92d2 fix(javascript): reconcile Node targets with entry assets (#8670)
* fix(javascript): reconcile Node targets with entry assets

* test(inspector): read reconciled evidence payload

* Update tests/boundary/inspector/javascriptRuntimeNodeTarget.test.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* test(inspector): read the workflow evidence payload

* Update tests/boundary/inspector/javascriptRuntimeNodeTarget.test.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* test(javascript): restore Evidence workflow result access

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-11-03 08:54:39 +08:00
吴佳浩Alben 59f5502a40 fix(javascript): emit no event operation for a literal non-function listener (#8621)
* fix(javascript): emit no event operation for a literal non-function listener

`EventEmitter` validates a listener before registering or removing anything:

    if (typeof listener !== 'function')
      throw new ERR_INVALID_ARG_TYPE('listener', 'function', listener);

The throw is synchronous, before any listener exists, but the analysis reported a
`register` or `remove` operation for those calls.

Measured on Node 24.22.1:

    emitter.on(1)                      -> THREW ERR_INVALID_ARG_TYPE
    emitter.on("x", 1)                 -> THREW ERR_INVALID_ARG_TYPE
    emitter.on("x", null)              -> THREW ERR_INVALID_ARG_TYPE
    emitter.on("x", true)              -> THREW ERR_INVALID_ARG_TYPE
    emitter.on("x", {}) / ([])         -> THREW ERR_INVALID_ARG_TYPE
    emitter.once / addListener / prependListener / prependOnceListener /
    off / removeListener with a literal -> THREW ERR_INVALID_ARG_TYPE
    --- accepted ---
    emitter.on("x", () => {})          -> ok
    emitter.on("x", function () {})    -> ok

`rejectsEventListener` drops the operation only for an exactly readable literal
that cannot be a function. An identifier, a call result or a spread may still
supply a function and keeps its operation, and a class expression passes Node's
own `typeof` check.

`emit` and `removeAllListeners` are untouched: `emit` passes its remaining
arguments as data to listeners that already exist, and `removeAllListeners` takes
only an optional event name.

* fix(javascript): price a non-finite listener and keep a shifted spread

Two review findings on the event-listener guard.

**A non-finite numeric literal was missed.** Babel stores `1e999` as a
`NumericLiteral` holding `Infinity`, and the exact-value reader refuses to price a
non-finite number, so the guard never saw it:

    e.on("x", 1e999)  -> THREW ERR_INVALID_ARG_TYPE

A numeric literal is always a number and therefore never callable, so the guard now
decides on the literal node directly.

**A leading spread shifts the listener position.** `e.on(...args, 1)` does not pass
`1` as the listener: Node spreads the array and ignores the trailing argument once
the listener is satisfied. The guard read `arguments[1]` regardless, so it dropped a
real registration. It now leaves the position undecided when argument 0 is a spread.

Five regressions cover both forms; all five fail without the fixes.
2026-11-02 16:47:22 +08:00
hhangandhhang 20a282a181 fix(javascript): report SVG script references and skipped bodies (#8627)
Co-authored-by: hhang <169027046+xu91102@users.noreply.github.com>
2026-11-02 15:55:56 +08:00
morluto 60149282fc test: strengthen integration oracles and verification (#8295)
* test: strengthen integration oracles and verification

* test: bound lifecycle gates and strengthen workflow checks

* test: verify complete paged and extracted results

* test: preserve bytecode and cancellation evidence

* test: strengthen retained evidence and fixture cleanup

* test: require cross-projection bridge identities

* test: retain fixture owners through cleanup failures

* test: verify graph examples and project selectors

* test: tighten provider and lifecycle regressions

* test: consolidate hex coverage and bound cutter cleanup

* test: bound subprocess output waits and strengthen graph checks

* test: check mcp recovery and preserve cleanup owners

* test: verify structured mcp argument failures

* test: preserve artifact cleanup and gdb limit evidence

* test: preserve evidence identities and bound provider gates

* test: retain browser and comparison cleanup owners

* test: bound startup and strengthen fixture oracles

* test: bound lifecycle gates and verify recovered evidence

* test: verify public outcomes and lifecycle ownership

* test: assert precise evidence and release lifecycle gates

* test: verify selected facts and bound cleanup waits

* test: check retained evidence and decoded records

* test: verify final reports and retain cleanup ownership

* test: bound reviewed startup gates and retain cleanup

* test: verify PE errors and preserve MCP cleanup

* test: preserve MCP fixture cleanup ownership

* test: release provider gates before late startup
2026-11-01 00:15:28 +08:00
gensokyo-dev a989c0e458 refactor(evidence): unify retained inputs across domains (#8299)
Declare logical Evidence sources once and resolve exact retained inputs through
shared application workflows across 20 composition tools. Preserve domain-owned
authentication, source closure, caller choices, coverage and resource admission.

Validation includes public CLI/MCP regressions, local Codex terra/high trials
and fresh-import real-artifact workflows. Measurements, failed attempts and
coverage limits are recorded in docs/verification/evidence-input-composition-2026-10-31.md.

Fixes #8293.
Fixes #8281.

Assisted-by: Suika (gensokyo-skills:suika-scatter-gather-planning)
2026-10-31 14:45:21 +00:00