Record the admitted selected path separately from canonical artifact identity in successful and partial Evidence. Anchor relative inputs consistently across both CLI routes using the existing caller-path owner. Adapt shared client verifier assertions and boundary coverage from PR #5261, and the dedicated-route anchoring correction proposed in PR #5265. Historical parsing remains independent of live paths, including legacy records without the parameter. Validation: 118 focused tests in eight files, static and docs checks, discovery, and installed Gemini CLI 0.29.5 loopback verification of 207 schemas/241 examples passed. Synthetic static-analysis fixtures; no live Google API or native target execution. Fixes #5258 Co-authored-by: kwakhyun <khyun2348@naver.com> Co-authored-by: vishal-tambi <tambivishal3@gmail.com>
44 KiB
JavaScript artifact reconstruction
Inspect a local Electron/JavaScript directory or ASAR without executing its code.
analyze_javascript_application and the CLI routes below return an Evidence
record with the application graph, static
Electron observations, statistics, limitations, and coverage.
Start with the public workflow, select sources when useful, then read the analysis. Use application workflows for tracing and comparisons, or partial recovery when an operation is interrupted.
Public workflow
The tool reads the caller-selected local directory or ASAR path directly:
rea analyze /absolute/path/to/apps/app.asar --json
rea analyze-javascript-application /absolute/path/to/apps/app.asar \
--integrity-policy record-and-continue --json
Generic rea analyze selects this static provider for directories and .asar
paths when neither --provider nor --snapshot is supplied. The dedicated
command and the generic route return their complete results inline. Native
targets, single JavaScript files, .app bundles, and explicit deep-provider or
snapshot requests retain the native deep-analysis route.
Both CLI routes emit rea_progress JSON lines on stderr as inventory, source
parsing, graph construction, validation, and Evidence hashing begin. Stdout
remains the selected result document. Completion is reported after Evidence
creation; a failed analysis retains its typed diagnostic result.
Configure an MCP client with the ordinary REA setup command:
rea setup
The equivalent MCP input is:
{
"input_path": "/absolute/path/to/apps/app.asar",
"format": "auto",
"integrity_policy": "fail"
}
input_path must be absolute. format accepts auto, asar, or directory.
integrity_policy accepts fail or record-and-continue; both CLI routes and
the MCP tool default to fail.
Evidence parameters retain the caller-selected input_path, including a
symlink alias. Both CLI routes anchor relative inputs to their working
directory without resolving symlinks. The subject and normalized result use
the resolved path for artifact identity; the selected path remains a separate
request fact. Older Evidence can omit this parameter; retained records are
validated without resolving their paths against the live filesystem.
The result retains the canonical local path, root artifact digest, artifact
manifest and graph commitments, JavaScript Application Graph, static
Electron summary, reconstruction statistics, and explicit limitations. It does
not require a live Hopper, Ghidra, browser, or Electron process.
The application graph selects the shallowest package.json outside
node_modules as its package root, breaking equal-depth ties by Unicode
code-point order. Dependency manifests remain package observations but do not
declare Electron main or renderer entries. If only dependency manifests are
present, the inventory artifact remains the graph root.
Select sources before analysis
For a question about known entry sources, supply exact artifact-relative file
paths in source_paths, or repeat --source-path on either CLI route:
rea analyze /absolute/path/to/app.asar \
--source-path package.json --source-path main.js --json
{
"input_path": "/absolute/path/to/app.asar",
"source_paths": ["package.json", "main.js"],
"detail": "summary"
}
REA inventories and verifies the complete artifact, including unselected bytes,
but allocates source text and performs static analysis only for selected files.
Include supporting manifests, HTML or source maps explicitly when needed;
imports do not expand the selection. Paths must match relevant inventoried
files exactly, including nested ASAR prefixes. Unknown paths, aliases and globs
return an invalid_request correction. Selected files are analyzed in request
order before unselected scopes are projected. Repeated paths preserve the
caller's request and first-occurrence priority, analyzing the file once.
Omit the selection for the existing complete
application workflow.
The result and summary retain source_selection.requested_paths and
source_selection.unselected_files. Every relevant file keeps its original
inventory identity; unselected files report text_status: "outside-analysis-scope". Their relationships remain unknown, and application
coverage is partial while any file remains outside scope. Selection does not
claim whole-application completeness or change integrity policy. Summary detail
only reduces delivery; source selection reduces text acquisition and analysis.
Explicit native provider or snapshot routing rejects --source-path rather
than ignoring it.
Read the analysis
analyze_javascript_application returns the complete analysis Evidence by
default. Select "detail": "summary" to retain that complete Evidence in the
current session and receive only its inspect_analysis_view summary: artifact
identity, statistics, Electron surface counts, application and semantic
coverage, limitations, and normalized_result.parent_evidence_id. Pass that ID
to inspect_analysis_view for module pages or items, or to the application
workflows as a retained reference. The summary does not repeat or truncate the
analysis; a server without session retention refuses summary detail with
capability_unavailable. CLI output is unchanged and always complete.
With a connected MCP client and the caller-selected absolute inputPath, this
recipe analyzes once and pages module identities using the returned ID:
const analysis = await client.callTool({
name: "analyze_javascript_application",
arguments: { input_path: inputPath, detail: "summary" },
});
if (analysis.isError) throw new Error(JSON.stringify(analysis.content));
const parentId =
analysis.structuredContent.normalized_result.parent_evidence_id;
const modules = await client.callTool({
name: "inspect_analysis_view",
arguments: {
source: { kind: "retained-evidence", evidence_id: parentId },
view: { kind: "page", collection: "modules", offset: 0, limit: 16 },
},
});
if (modules.isError) throw new Error(JSON.stringify(modules.content));
Select a returned module's exact node_id for an item view. A path can identify
several graph roles; module items preserve their recorded properties and source
locations. Follow coverage.next_offset when another page matters. For feature
tracing, version comparison, and retained-input examples, use the
application workflows.
For supplied passive browser or Electron observations, use runtime reconciliation. It compares existing Evidence without rerunning static analysis or contacting CDP.
ASAR integrity
ASAR inventory checks Electron integrity metadata for embedded archive entries
and supplied .asar.unpacked companion files. An integrity failure identifies
the logical path, declared and calculated SHA-256 values, and whether the entry
was unpacked. By default, a mismatch is returned as a failure with its artifact
context. Application analysis can explicitly select record-and-continue to
analyze observed bytes while retaining each contradiction's declared and
observed hashes and observed-untrusted trust. Such results mark application
graph coverage partial and include every contradicted path in the limitations.
Mismatched nested ASARs stay opaque rather than being expanded.
An unpacked entry whose companion bytes were not supplied remains
unavailable. REA continues analyzing embedded JavaScript and records the
missing native/resource bytes as unknown. The occurrence keeps
hash_status: unavailable; no content-addressed child artifact is created for
those missing bytes. See MCP integrity handling
for the tool-result contract.
Large results
Use MCP detail: "summary" for a large application, then read selected views of
the retained analysis. Summary changes delivery only. To reduce source parsing,
select exact source paths.
If a complete response exceeds the transport budget, use its retained Evidence ID for a summary, module page, or item, or export the bundle. See MCP tool results for delivery limits, export, and matching server/client buffer settings. Follow-up results also need to fit that budget. Analyzer limits control analysis memory, source deadlines, and semantic capacity separately.
Recover partial analysis
A source timeout stops the remaining analysis and returns completed facts as partial Evidence. Cancellation after source analysis begins can also retain completed facts, including when interrupted during cleanup, graph construction, or Evidence sealing. Unexamined relationships remain unknown.
Per-file gaps identify the applicable processing phase: JavaScript source analysis
uses AST authority, while package/JSON, HTML-reference and source-map processing
use artifact-byte authority. File-analysis interruption gaps carry analysis_status:
interrupted means the phase was entered (including source-worker startup),
and not-started means interruption prevented that phase from beginning.
Neither state establishes that parsing completed. Remaining parsers are not run
after interruption just to populate a result.
Native add-ons remain metadata-only and acquire no JavaScript parse or analysis
gap. This also applies when their paths are outside source_paths; their inventory
and caller selection remain available without implying applicable AST work.
MCP returns a partial_observation retained reference after recording succeeds;
open it with inspect_analysis_view on the same connection. If the SDK stopped
waiting before receiving the reply, follow MCP cancellation recovery
to find the record. CLI failure output contains the complete partial Evidence.
Owned analyzer cleanup is verified before private files are released; uncertain
cleanup retains ownership for retry and is reported separately.
What is reconstructed
The projector reuses the content-addressed artifact inventory and safe artifact
readers. It retains canonical artifact-relative paths, exact byte counts,
SHA-256 digests, inventory IDs, ASAR container identity, and .asar.unpacked
status. Direct ASAR inputs and filesystem-backed ASAR files nested beneath a
directory are supported.
JavaScript sources (.js, .jsx, .mjs, and .cjs) and TypeScript sources
(.ts, .tsx, .mts, and .cts) are parsed as inert text; analysis does not
execute them.
JavaScript and HTML source ranges retain an initial UTF-8 BOM as one UTF-16 code unit, matching the original bytes identified by the artifact digest.
Plain .ts artifact sources use TypeScript syntax without JSX, including
angle-bracket type assertions. .tsx and .jsx retain JSX parsing.
JavaScript sources use JavaScript grammar with JSX, never TypeScript syntax,
so valid JavaScript that TypeScript reads differently keeps its JavaScript
meaning.
Relative TypeScript imports can name their emitted .js, .mjs, or .cjs
counterparts while resolving to inventoried source files. This substitution
requires import or require semantics. Literal Electron preload, renderer, and
utility-process runtime paths retain the runtime filename selected by the caller.
Selected bounded text is then parsed as inert data to recover:
package.jsonmetadata and declared main or renderer entrypoints;- Electron preload paths and renderer files visible in static syntax;
- local HTML script entrypoints;
- Webpack and Rspack chunk registrations and module factories represented as AST literals;
- static imports, dynamic imports, CommonJS
requirecalls, workers, and service workers (onlynew Worker(new URL(path, import.meta.url))resolves relative to the calling module; string worker and service-worker paths use the document base URL or Node's working directory, which static analysis does not establish, so they keep their expression and stay unresolved); - route, network endpoint, storage, and vendor-marker observations;
- local source-map declarations and original source names and content digests;
- explicit BrowserWindow options and
webPreferences, including statically resolvable preload entrypoints; contextBridge.exposeInMainWorldandexposeInIsolatedWorldAPI keys and bounded literal member paths;- renderer and main-process IPC operations, literal or dynamic channels, exact handler locations, and conservative pairing status;
- sender, frame, URL, and origin validation candidates without claiming that a visible check enforces a complete policy;
- utility-process entrypoints and native
.nodebinding requests without parsing or executing the add-on.
Overloaded .open calls use lexical receiver facts: ambient browser window and
document receivers are treated as browsing-context or document operations,
while locally shadowed receivers can still contribute network endpoint
candidates. A template recovered with a parser error and no cooked value stays
dynamic; its raw spelling is not treated as a valid JavaScript string.
RPC client syntax also contributes network endpoint candidates, in both
analyze_javascript_application and analyze_web_bundle, distinguished by
mechanism:
trpc:procedure:<method>records the static member path between the root expression and the method, such asinvoice.listinroot.invoice.list.useQuery(...). tRPC proxies keep these property names through identifier minification. React hooks and TanStack option factories (useQuery,useMutation,useInfiniteQuery,useSubscription, suspense and prefetch variants,queryOptions,infiniteQueryOptions,mutationOptions,subscriptionOptions) are recognized directly. The vanillaqueryandmutatemethods are recognized on a root bound tocreateTRPCClientorcreateTRPCProxyClientin the same lexical binding, or on a path of at least two segments in a source containingTRPCClientError,trpc-accept, or/trpc. Factory bindings must have one unconditional initializer and no binding assignments; shadowed locals do not inherit client identity.subscribeadditionally requires a tRPC observer argument (onData,onError,onStarted,onStopped,onComplete, oronConnectionStateChange).thisroots, browser globals, and any path containing an RTK Queryendpointssegment are never procedures; cache-key helpers such asqueryKeyissue no request and are excluded.trpc:link:<link>records the literalurloption ofhttpLink,httpBatchLink,httpBatchStreamLink, andhttpSubscriptionLinkwhen the link's callee name survives bundling, including(0, module.httpBatchLink)import-call wrappers.graphql:query,graphql:mutation, andgraphql:subscriptionrecord a named operation found at the start of a string or cooked template literal — the name must be followed by variable definitions (($), a directive, or a selection set starting with a field name — or in a precompiledOperationDefinitiondocument object. A standalone Source object'sbodyremains an operation candidate even when it haslocationOffsetmetadata. A graphql-tagloc.source.bodycopy is suppressed only when that source belongs to a recovered document with the same operation and its containing document, location and source have proven private metadata ownership. Writes, escapes and classic-script global bindings retain the source observation. Anonymous operations are not reported.
Literal link options and precompiled document fields follow object property order: the final definition owns the key. A later spread, dynamic computed key, method, or accessor can make a field unknown, so an overwritten literal is not reported as the endpoint or operation. A final explicit property after a spread or dynamic key remains usable.
A procedure path is the server-side procedure name, not a resolved URL; the
transport URL depends on the link configuration and batching. When the proxy is
reached through a member expression, such as a webpack module binding
(l.S.invoice.list) or a property holding the proxy (o.api.post.byId), the
recorded path keeps those leading carrier segments; the procedure path is its
suffix. Utility calls such as useUtils().invoice.list.fetch(), useQueries,
and links whose callee was renamed by a minifier are not recognized.
Each recovered bundle module retains the exact factory-source digest. A complete
bounded AST also receives a babel-ast-v3 structural fingerprint. It ignores
ordinary identifier spellings while retaining literal and directive values,
operators, fixed property names, and selected syntax flags such as computed
keys, declaration kind, function async/generator state, update-expression
prefix, method kind, and class static state. Local names used in computed keys
remain rename-resistant; private member names are also ignored while their
method kind and static state remain represented.
This fingerprint is structural evidence for candidate matching, not proof that two modules behave equivalently. Its scope follows the AST facts listed above; unrepresented syntax can still collide. The exact source digest remains available to show byte-level change, and comparison reports content changes even when structural matching succeeds.
Version comparison includes the algorithm in its structural matching key.
Retained babel-ast-v1 results can still match other v1 results, and v2 results
can still match v2 results, but v1/v2 results cannot structurally match v3
results. Other matching tiers remain available across algorithm versions.
Re-analyze both snapshots to compare using the v3 rules.
If fingerprint construction reaches its structural
bound, the fingerprint is unavailable and its status is truncated; a bounded
prefix is never presented as a complete stable fingerprint.
Authority and unknowns
Artifact bytes and AST syntax are observations. Entrypoint resolution, imports,
loads, calls, and persistence relationships are static inferences and explicitly
say that syntax does not prove runtime execution. A malformed or unavailable
JavaScript file, package record, or source map produces an unknown graph scope
with state: unavailable; it is not treated as evidence that modules or source
are absent.
Source-map contents are parsed as local input. The graph stores original source names and optional content digests, not raw sourcesContent text.
Only explicitly present BrowserWindow values are observations. REA does not
substitute version-dependent Electron defaults for omitted webPreferences.
Unresolved option objects, bridge keys, API objects, and IPC channel expressions
remain unknown and make coverage partial.
BrowserWindow options and webPreferences can follow local const aliases
whose initialization precedes the use. The shared lexical and mutation index
checks the selected webPreferences path, so an unrelated options.icon write
does not discard these facts. Reassignment, relevant writes, prior escapes,
ambiguous initialization, and unresolved spreads remain unknown. The binding
association is an inference: the window observation retains the constructor
location and resolution_sources identifies the original object literals in
the same artifact. Inline option syntax keeps its observed Evidence. This
does not execute code or establish runtime configuration; popup overrides from
setWindowOpenHandler are not covered by this constant-options resolution.
Recognized literal module-table bootstraps also support bounded lexical
substitution through module exports, import helper bodies, local wrapper calls,
returned closures, object spreads, and callback parameters. Recovered Electron
paths retain source ranges, module identities, literal channels and branch
conditions as static candidates. Unknown spreads, reassigned bindings, accessors,
dynamic channels and unsupported syntax do not become exact implementations.
This pass neither executes the bundle nor proves that runtime guards pass.
An ambiguous or unresolved handler retains its channel registration but does
not acquire a path to one arbitrarily selected callback. Unknown factory
conditions retain both return branches, including fallthrough. Short-circuit
selection distinguishes truthiness from nullishness; uncertain ?? paths
retain a nullish guard rather than a falsy guard.
Recognized module factories retain a separate initialization boundary for each
reachable return and for fallthrough. A conditional early return does not hide
later exports on the continuing path; exports and captured initializers after a
definite exit remain unavailable. Fallthrough candidates retain their branch
predicates. Exports shared by the complete set of proven exits also retain
unconditional facts. Their shared view excludes later overwrites and bounds
captured initializers by the earliest completion; branch-dependent facts keep
their predicates. Incomplete or additionally guarded exit sets cannot use that
proof. Completed factory control flow is reused within that analysis only.
Guarded main-process registrations remain candidate paths even with empty
callbacks; they do not become unconditional IPC summary facts.
Factory guards read export writes at their source position. Destructured and
spread snapshots retain their capture position rather than reading later writes.
An unproven namespace read boundary remains unknown.
Completed factory environments preserve their exports and module.exports
alias identity when a namespace escapes through an export or returned closure.
Synchronous helper calls retain the caller's factory activation and read point;
lexical captures and executing callers remain separate facts.
Async calls retain their truthy Promise identity until an explicit await;
generator calls produce iterators without evaluating their bodies. Iterator
advancement and Promise/iterator members remain explicitly unresolved. Awaited
function values retain their source origins and captured initialization bounds.
These paths appear as contextual javascript-expression nodes. An exact api
seed such as desktop.copy can trace an exposed member through a unique IPC
pairing to an external API call. addListener is normalized to on. Semantic
projection retains these boundary paths before bulk function definitions, within
the existing per-file node budget; omitted general semantics remain explicitly
partial. Wrapper expansion is bounded by one million visits/substitutions and
128 nested substitutions, with exhaustion reported as unknown coverage.
Electron API identity follows proven lexical module aliases. Namespace interop
also preserves identity when the complete Rolldown helper operations, lexical
roles, live getters, and captured intrinsic origins match, or when a stable
helper simply returns its module parameter. Harmless single-statement control
blocks preserve recognition; added operations still require proof. Literal
member and destructuring projections are retained from proven results.
Omitted or literal node-mode arguments preserve named exports; a module-valued
default projection requires a truthy literal node mode. Computed or extra
arguments remain unresolved.
Recognition uses structure rather than emitted names; other helper bodies and ambiguous
runtime fallbacks remain unresolved. Captured function identities survive later global-property
replacement, including copies through initialized aliases. Reads before an
alias initializer remain unresolved. Visible intrinsic writes before capture or
conversion are checked in their owning callable scope, including identifier leaves in nested/default/rest
destructuring and loop targets. Property keys and shadowed local bindings do not
overwrite the global intrinsic identity. Cross-call side effects, including
writes inside directly invoked local functions, are not evaluated. Other
reassignments, dynamic selections, and visibly overwritten namespace members
are unresolved.
Known Object/Reflect mutators remain recognizable through literal destructuring
of intrinsic objects and stable bound-function aliases. Calls retain bound
argument prefixes and the original bound receiver through call, apply, and
Reflect.apply, including generic Function.prototype invocation and binding
creation. Arguments follow each actual callable invocation rather than property
chain depth; rebinding retains the original bound receiver.
Visibly replaced invocation intrinsics produce unknown effects instead
of using their former call semantics; captured function aliases retain their
earlier identity. Unexamined targets remain unresolved. Reflect.set examines
its explicit receiver as well as its target, retaining unknown receiver effects.
Creating an unused binding does not itself mutate an intrinsic. Variable
redeclarations retain a single unconditional initializer
when they introduce no assignment or second initializer.
Stable identifier chains to fresh object, array, function,
undecorated class or RegExp syntax keep those targets separate from intrinsic
objects when initialized before use; opaque, decorated, reassigned or
not-yet-initialized targets remain unresolved. Writes during class initialization
are still retained. Function declarations are available before their textual
position; variable and class initializers must precede use.
Direct and destructuring assignment, loop, update, and delete targets are checked
through namespace aliases; unrelated member writes do not discard observed API
identity. This static check does
not evaluate arbitrary call side effects or runtime registration. Missing findings
do not establish that an application has no Electron bound