Files
reverse-engineering-anything/docs/guides/javascript/javascript-runtime-observation.md
T
morluto 15de1d4dce feat(evidence)!: require JavaScript, browser, managed and reference-source facts their producers always write (#10990)
* feat(javascript)!: require the runtime parse-failure count and active Electron execution paths

The V8 Inspector projection always writes scripts.parse_failed_total and the
active Electron service always commits parameters.resolved_paths, so both are
now required. Runtime reconciliation reads the admitted execution paths only
and no longer falls back to request-spelled paths in parameters, and checks
the observed executable path against them on every capture.

JavaScriptStaticAnalysis.routes and module_runtime keep their omission, which
current producers still make (a checkpoint that cannot retain route facts; no
recovered factory runtime); their comments now say so instead of naming
historical analyses.

BREAKING CHANGE: JavaScript runtime observations without
scripts.parse_failed_total, and active Electron Evidence without
parameters.resolved_paths, are refused as malformed; recreate the observation.

* test(javascript): give the shared Node reconciliation fixture the required parse-failure count

* feat(browser)!: require browser observation facts their producers always write (#10991)

* feat(browser)!: require browser observation facts their producers always write

The source-map fetcher always writes effective_url (string or null), the web
bundle analyzer always writes schema_property_names_coverage, and the CDP
storage fingerprinter always marks IndexedDB schema fingerprints with
projection_version 2. Each is now required. The IndexedDB marker is tied to
its scope by a discriminated union and derived from the scope in the
producer, so compare_web_captures no longer carries the store-only
projection branch.

response_identity and the WebMCP consequential hint keep their omission,
which current producers still make (a body not fully read; an invalid
annotation); the guide no longer attributes it to historical captures.

BREAKING CHANGE: source-map items without effective_url, WebMCP declarations
without schema_property_names_coverage, and IndexedDB schema fingerprints
without projection_version are refused as malformed; recreate the capture.

* feat(managed)!: refuse members beside a null signature resolution (#10992)

* feat(managed)!: refuse members beside a null signature resolution

signature_resolution is null only when member decoding exceeded capacity,
and the capacity-recovery producer then examines no members. The managed
member inspection schema now enforces that: a null resolution with any
field, method, member reference, call edge or field access is malformed.

With that rule, every admitted inspection with members attests type-name
resolution, so the raw-digest fallback is unreachable and is deleted:
comparableSides, hasSignatureResolution, the comparison's
signatureResolutionExamined flag and the "does not attest" limitation. The
shared workflow example that carried resolved digests beside a null
attestation now attests resolution.

BREAKING CHANGE: managed member Evidence with a null signature_resolution
and any examined member is refused as malformed; recreate the observation.

* docs: describe historical Evidence by what the format revision now refuses (#10993)

SQLite worker_timeout_ms is required, so the SQLite guide no longer reads a
missing value as unknown settings. The IL2CPP, ESP image and process capture
guides now say that bundles exported without evidence_format are refused,
while a standalone record from an earlier release is still read with the
stated omission semantics (records carry no format marker yet). The route
inventory guide describes a parent without route_inventory by its meaning
rather than by its age.

* refactor(javascript): require static analysis coverage from the JavaScript worker (#11001)

* refactor(javascript): require static analysis coverage from the JavaScript worker

Both JavaScript static producers always write analysis_coverage: the
analyzer measures it on every parsed source and the failed-parse result
carries an unexamined coverage. The worker schema and the static analysis
type therefore require it, the preload-discovery checkpoint copies it
unconditionally, and ElectronBoundaryAnalysis reads Electron applicability
and examination limits from it alone. The fallback that re-derived Electron
use from semantic module links and retained references for analyses
without coverage is deleted, as are the graph and builder branches for a
missing coverage.

Retained Evidence graph observations are unchanged: the analysis view still
counts a stored JavaScript observation without analysis_coverage as
unmeasured, since standalone records carry no format marker yet.

The worker runs from the same build as its parent, so no external caller
sees the stricter reply schema.

* feat(browser)!: require storage fingerprint, Link context, mapping index and invalid-map identity facts (#11002)

* feat(browser)!: require storage fingerprint, Link context, mapping index and invalid-map identity facts

Each field is written by its only producer on every capture:

- The CDP storage capture always writes content_fingerprints,
  fingerprint_algorithm and fingerprints_complete, so their schema defaults
  are removed.
- The CDP Link header projection always writes context_url and
  context_scope, so they are required.
- The source-map fetcher writes source_index and declaration_index on every
  mapping row, so they are required.
- The fetcher judges a map invalid only after reading its complete body. A
  separate invalidSourceMapItem builder now takes the response identity as a
  required argument, and the schema's invalid member requires
  response_identity; fetch_failed keeps it optional because those rows can
  come from a body that was not fully read.

The browser and source-location guides no longer describe these fields as
optional for older saved captures.

BREAKING CHANGE: page inspections without the storage fingerprint fields or
Link context fields, source-map mapping rows without their indexes, and
invalid source-map items without response_identity are refused as malformed;
recreate the capture.

* test(browser): keep source-omission coverage on a still-defaulted field and refuse a missing fingerprint algorithm

* feat(browser)!: require web-bundle target origin, inspection producer facts, scenario redirect state and exclusion kind (#11014)

feat(browser)!: require web-bundle target origin, page inspection producer facts, scenario redirect state and exclusion kind

Each field is written by its only producer on every record:

- buildCaptureObservation always writes capture.target_origin (the validated
  origin or null), so analyze_web_bundle Evidence and both web-bundle view
  results require it; the view's conditional spread is gone.
- The CDP page capture always writes accessibility node states, script
  frame_id (null when unattributed) and worker opener_target_id and
  parent_frame_id, so their schema defaults are removed.
- PlaywrightScenarioNetwork writes redirected_from_transaction_state exactly
  when the predecessor ID is null, so request events now require it there and
  still refuse it on linked events.
- The reference-source importer writes entry_kind on every exclusion
  (unknown when the kind was not observed), so it is required.

The browser scenario contract and CLI guide no longer describe older
captures or exclusions without these fields.

BREAKING CHANGE: web-bundle analyses without capture.target_origin, page
inspections without these script, accessibility or worker fields, scenario
network events with a null predecessor and no redirect state, and reference
source graphs whose exclusions lack entry_kind are refused as malformed;
recreate the capture or re-import the source.
2026-11-15 19:37:10 +08:00

204 lines
11 KiB
Markdown

# Passive Node and Electron runtime observation
REA can attach to an already-running Node.js or Electron V8 Inspector target
and retain script-load and execution-context metadata. The
`list_javascript_runtime_targets` and `observe_javascript_runtime` MCP tools
have equivalent `rea list-javascript-runtime-targets` and
`rea observe-javascript-runtime` commands. Successful calls return
deterministic Evidence.
Each request supplies the loopback Inspector endpoint and, for observation, the
target ID. This surface is separate from browser CDP, Electron file-page
inspection, and Process Capture:
```bash
rea list-javascript-runtime-targets http://127.0.0.1:9229 \
--json
rea observe-javascript-runtime http://127.0.0.1:9229 TARGET_ID \
--runtime-kind node --json
```
## Start a caller-owned Node target
Run your selected application with Inspector bound to loopback:
```bash
node --inspect=127.0.0.1:9229 ./app.mjs
```
Keep it running while listing targets and observing the selected ID. Use a free
port and the same literal endpoint in both REA calls. Stop the application
through its normal lifecycle when finished; REA disconnects but does not own or
terminate it. Node.js must already be installed.
Use `--inspect` for this passive workflow. `--inspect-brk` pauses before the
application starts, and REA never resumes it or sends
`Runtime.runIfWaitingForDebugger`. Resume a paused target with a separate
caller-owned debugger before requesting application runtime observations.
For a packaged Electron app, read
[`inspect_electron_fuses`](../../electron-fuses.md) before starting an `electron-main`
target. When `EnableNodeCliInspectArguments` is disabled or removed, Electron
ignores `--inspect` and `--inspect-brk`. A missing main-process target after
launch does not, by itself, show that fuse.
The caller supplies the literal-loopback HTTP endpoint directly. Selecting an endpoint exposes all targets it serves and their local script locations or renderer origins. A target must match the exact requested target ID and expose a same-port WebSocket reported by that endpoint. Targets explicitly marked attached are rejected rather than displacing another debugger. Node's `/json/list` omits attachment state, so REA preserves it as `null` (unknown), reports that limitation, and permits observation unless the endpoint explicitly reports `attached: true`. Local paths are canonicalized after symlink resolution.
The `attached` field in JavaScript runtime and browser target results is nullable: `true` and `false` are reported only when discovery supplied a boolean; `null` means the endpoint omitted or could not report the state. HTTP `/json/list` discovery parses target rows independently. Malformed optional attachment metadata becomes `null` with a row and field diagnostic in `limitations`; usable siblings remain available. Rows with malformed target identity are excluded with diagnostics, and socket authority is still validated before connection. CDP `TargetInfo` worker records require an `attached` boolean; REA excludes a worker record with a missing or malformed value as an invalid protocol value.
`list_javascript_runtime_targets` returns every available target in one inline
array.
Attachment refusals identify `attached: true` and ask the caller to disconnect
the other debugger or select another target. A declared-role mismatch names
`runtime_kind` and the target protocol type; correct the role or omit it to
retain an unknown role. A role-only disagreement in supplied runtime Evidence
is attributed to `parameters.runtime_kind`, separately from a target-ID
disagreement. Partial-capture warnings count retained distinct script records;
`observed_total` retains the raw script-event count before deduplication.
For the MCP follow-up call, pass the same literal-loopback endpoint and the selected target ID to `observe_javascript_runtime`. REA rediscovers targets and validates the ID against the live endpoint before observation.
### Discovery file locations
Node's `/json/list` reports a best-effort pathname prefixed with `file://`,
without URI encoding. On POSIX, REA interprets unambiguous paths literally,
including `#` and `%` in filenames. Node also replaces backslashes and double
quotes with underscores. That transformation cannot be reversed reliably:
an underscore may already be part of the original filename.
Windows paths, ambiguous underscore paths, and local discovery paths that
cannot be verified remain eligible by their endpoint and exact target ID.
Their target location preserves the reported value:
```json
{
"kind": "unresolved",
"reported_url": "file://C:_tools_entry.js",
"reason": "unverifiable-file-location"
}
```
This location does not claim that a file exists or authorize reading its
contents. Listing remains HTTP-only. Observation resolves loaded script
locations independently from `Debugger.scriptParsed` file URLs, preserving
the existing checks for local files and encoded separators. Unsupported
schemes, remote file hosts, and a bare `file://` remain excluded. These Node
discovery rules do not apply to page targets or other runtime products.
Reconciliation retains an unresolved `target_location` object and reports
`runtime-location-unresolved` with unknown confidence for that target. It
does not derive a static match from the reported pathname; verified loaded
script locations remain available for independent matching.
## Passive protocol boundary
The provider sends exactly two protocol commands:
- `Runtime.enable`
- `Debugger.enable`
It never sends `Runtime.evaluate`, reads script source, installs breakpoints,
sends pause/resume commands, calls `Runtime.runIfWaitingForDebugger`, launches
the target, instruments JavaScript, or invokes Electron APIs. Enabling
`Debugger` can make target-authored `debugger` statements pause execution.
REA does not send `Debugger.setSkipAllPauses` or resume these pauses; passive
observation is not a guarantee of uninterrupted target execution. Closing a
call closes only REA's WebSocket.
Each WebSocket handshake and command response has its own 20-second deadline,
separate from the caller-selected observation window. A stalled handshake or
command reports `provider_timeout` and retains observations already received;
caller cancellation still reports cancellation.
The direct observations are:
- `Debugger.scriptParsed`, establishing that a bounded script location was
parsed with the reported execution-context ID; association with a particular
context lifetime is known only when its creation was observed;
- `Debugger.scriptFailedToParse`, retained only as a bounded invalid-script
count in `scripts.parse_failed_total`. A well-formed parse-failure
event is distinct from malformed protocol data and does not make loaded
script coverage unavailable;
- `Runtime.executionContextCreated`,
`Runtime.executionContextDestroyed`, and
`Runtime.executionContextsCleared`, establishing context lifecycle within
the capture window. A created or destroyed event whose context ID is
missing or not a safe integer adds no context record; it is retained only
as a count reported in `unknowns`.
The Inspector protocol does not directly expose require/import caller edges,
EventEmitter emissions or listener invocation, Electron IPC messages or
handlers, or script-unload events. Those facts remain explicit unknowns.
`scriptParsed` proves script presence, not the importing module, feature
execution, initialization order, or causality.
For an authorized file script, `location.file_path` is the verified filesystem
identity and optional script-item `reported_url` preserves the exact Inspector
URL. Script identity includes that reported URL, so query and fragment
variants of one file remain separate observations. Older Evidence may omit
`reported_url` and remains readable.
## Node and Electron roles
The caller may declare one role: `node`, `electron-main`, `electron-preload`, or
`electron-renderer`. Node and Electron-main declarations require a protocol
target of type `node`; preload and renderer declarations require `page`.
Inspector does not authenticate the operating-system PID or distinguish an
Electron role, so Evidence records the role authority as
`caller-declared-unverified`.
If final script-location authorization fails, collected script and context facts
remain available in the typed failure with explicitly unverified locations.
Descriptor cleanup status and its retry owner remain independent of that capture.
Electron main, preload, and renderer behavior can therefore be observed only
as separate Inspector targets. The provider does not infer that two
targets belong to the same Electron application.
## Capture and determinism
Capture retains valid script and execution-context metadata within an 8 MiB
aggregate budget, including conservative estimates for UTF-16 strings, record
and collection overhead, and projected URL fields. Each CDP message also has a
16 MiB transport limit. A message over that limit fails with `payload_limit`;
when the aggregate budget is reached, later metadata events are dropped and
the partial result reports `events_dropped`, `truncated: true`, and
`retained_metadata_budget_exceeded`. Context records retain each observed lifetime, and scripts refer to the context
lifetime active when parsed. The reported numeric execution-context ID is
retained separately on both records; a script with no observed context creation
has an unknown context key. Repeated transitions within one lifetime update its
record; the state records the last observed lifecycle event and does not prove
that the context is still live. This changes context keys and Evidence identities from older captures;
recapture observations to obtain lifetime associations. Scripts are
validated after capture, deduplicated by stable metadata, and canonically
sorted. Wall-clock timestamps and protocol script IDs are excluded from the
durable result, so identical inputs and captured metadata produce the same
Evidence ID.
Inspector attach is inherently incomplete. Enabling `Debugger` reports known
and uncollected scripts, but scripts collected before attachment may be
missing. Absence from the observation window never proves that a script or
behavior does not occur.
Cancellation stops new target-file authorization after the current filesystem
operation settles. Cancellation diagnostics retain the examined target prefix;
unexamined targets remain unknown. Observation cancellation keeps its original
reason and any completed Evidence. Finalization waits for REA's bounded socket
close (up to two seconds for graceful and forced confirmation). A failed close
retains its exact transport owner and blocks replacement connections until
cleanup succeeds; CLI finalization and MCP shutdown also retry retained cleanup.
Closing REA's connection does not stop the caller-owned runtime.
## Static correlation
Pass the resulting observation Evidence and an
`analyze_javascript_application` Evidence record to
`reconcile_javascript_runtime`. The reconciliation accepts this provider
alongside passive web and Electron page captures. Exact file or URL mappings
supplied with the request can correlate script presence with JavaScript Application Graph
assets. Because this provider never reads source bytes, matches normally use a
unique authorized location and remain weaker than captured-byte identity.
Builtin `node:` scripts remain runtime-only nodes unless a future explicit
static authority models them.