* feat(audit): report what a shipped app exposes to static analysis
Adds `audit_release_exposure` and `rea audit-release-exposure` to report
source maps, Electron fuse implications, extractable payloads, debug
information, build metadata, and credential markers. Checks carry stable
IDs, observations, statuses, and fixed remediation, while incomplete and
unassessed checks remain visible without implying a security verdict.
Credential findings include only rule IDs and locations. The CLI
supports `--fail_on` severity thresholds and exits 3 when an exposed
check meets the threshold.
* fix(audit): keep incomplete coverage unknown across text, fuses, Go flags and cancellation
Probe files with unrecognized names as text, keep undecided fuse slices
unknown, forward opaque container coverage, read paths from Go build flags,
and compare inventoried digests before accepting owner facts.
Keep typed cleanup failures and attach the partial report when a later owner
is cancelled. Pin the verifier's @electron/fuses version and move the tests
to the named contract family.
* fix(audit): match package members by source spelling and close remaining coverage gaps
Reopen package members by their source spelling and keep caller path
traversal in directory reads. Parse quoted and attached Go build flags, bind a
native image fuse read to its own digest, and keep reader resources and the
audit's partial report when cleanup or cancellation combine failures.
* fix(release-exposure): bind package members by ordinal and follow named map references
Package members were matched to container entries by sourcePath, which is
not unique in a PyInstaller executable, through a queue that only moved when
a member was reopened. Bind by container-relative path and duplicate ordinal,
the numbering inventory uses, so read order cannot change which bytes a
member gets.
A sourceMappingURL naming a file without a map extension was ignored. Local
targets are now examined as maps whatever their name, and a target already
scanned as plain text leaves coverage unknown.
* fix(release-exposure): match data: source map scheme case-insensitively
URL schemes are case-insensitive, so an inline declaration such as
//# sourceMappingURL=DATA:application/json;base64,... was routed to the
local-reference path, which ignores scheme URLs. Its embedded sources were
silently dropped and source_map_sources reported not_observed.
* fix(release-exposure): normalize declared map targets and count unreached ones
A sourceMappingURL that decodes to a decomposed name (cafe%CC%81.json)
never matched the inventoried member, whose logical path is NFC, so the
map was scanned as ordinary text and its sourcesContent went uncounted.
Map-target lookups now use an NFC key on both the record and lookup
side; the declared URL is left as written.
A declared map target that is never reached is now counted as a skipped
map, so the source-map checks read as unknown coverage instead of
not_observed.
* fix(release-exposure): recover indexed leaves after any map format failure
An indexed map whose one leaf has malformed mappings (such as AA) threw a
format failure that only recovered leaves for unsupported sections, so a
valid sibling leaf with sourcesContent was dropped and
javascript.source_map_sources read unknown instead of exposed. Leaves are
now recovered for any format failure, with the envelope parse guarded so
invalid JSON still falls through to the unparsed count.
A nested package that inventory recognized but did not expand was probed
as a plain binary and left the source-map checks at not_observed. It now
marks coverage unknown and records a limitation.
Reader tests assert the javascript.source_map_sources status for the NFC
map-name cases as well.
* fix(release-exposure): count an unexpanded nested package as a readable member
An unencrypted nested package that inventory recognized but did not
expand is still a member its parent package already read. Count it as
readable so packaging.extractable_payload reports exposed, while its
own contents keep coverage unknown.
* fix(release-exposure): bound the indexed leaf fallback by decoder limits
The fallback walk after a map format failure expanded every section and
built a full ancestor path per leaf, so a map refused for nesting depth
could still allocate far past the decoder budgets. Walk only to the
64-level section depth and the shared record budget, keeping leaf maps
without materializing section paths.
* fix(release-exposure): collect UNC roots from Go flag paths
A flag such as -I\\buildhost\sdk\include matched none of the path roots
in GO_FLAG_PATH, so a network path that was the only one in complete
build info left go.build_paths at not_observed. Accept \\host\share
roots alongside the slash-rooted, dot-relative and drive-letter forms.
* fix(release-exposure): keep Electron undecided without positive proof
The desktop owner names Electron only from an ASAR layout, so an asar: false
build has no candidate. A complete scan with no candidate was reported as
not_electron, which outranks unresolved and safe fuse outcomes and hid them
as not_applicable.
Leave the family unknown unless the fuse holder rules Electron out: an app
bundle with no Electron Framework, or a directory with complete coverage
whose native images were all read and hold no sentinel. A directory with no
native image is not proof, so its fuse checks now report unknown.
* fix(release-exposure): decode wide text members before scanning
A member's kind bypassed the NUL guard, so a UTF-16 plist was decoded as
UTF-8, matched nothing and still counted as scanned. A directory holding
only that member reported text.credential_markers as not_observed.
Decode by byte order mark at one boundary instead. UTF-16 is scanned in
either byte order, and inline maps read the same text. UTF-32 and NULs no
mark explains count as skipped, whatever the member's kind. A binary
plist keeps its raw scan. Unnamed members with a mark now reach the
scanner, and the plist decoder shares the same mark detection.
* fix(release-exposure): follow file: URL source map declarations
The scheme guard in noteLocalMapReference returned on every URL scheme, so
a declaration such as file:///release/debug.json was dropped with the remote
ones. A member without a .map name was then scanned as ordinary text and
javascript.source_map_sources reported not_observed.
Convert file: URLs with fileURLToPath and record them in mapTargets through
the same step as relative declarations. A target that is never reached, or
that was already scanned as text, counts as skipped, and so does a malformed
file: URL. A file: URL names one absolute location, so a .map target outside
the inventory stays unassessed rather than being trusted by its name. Remote
schemes stay ignored.
* fix(release-exposure): match backslash-relative Go flag paths
GO_FLAG_PATH accepted dot-relative roots only with a forward slash, so
`-I..\sdk\include` and `-I.\sdk` produced no path and go.build_paths came
back not_observed. Accept either separator after the dots.
The CLI acceptance test now expects unknown for electron.run_as_node on a
directory with no native images, matching the service.
* fix(release-exposure): keep fuses unknown on incomplete inventory
A directory inventory that stops at an acquisition limit never reports
the images it did not reach, so the native image sample has no omission
and safe fuses on an included helper settled to not_observed even though
scan coverage was unknown.
Treat unknown coverage like a truncated sample until the Electron
executable is accounted for. A selected native file is that executable,
so it keeps its decided fuse result.
* fix(release-exposure): decode binary plists before scanning
A binary plist stores each string as ASCII or UTF-16BE. Scanning the raw
bytes as UTF-8 missed any string the encoder kept in UTF-16, such as one
with a non-ASCII character, and still counted the member as fully
scanned, so text.credential_markers read not_observed.
Decode through the plist owner and scan its strings, dictionary keys and
data. A plist that cannot be decoded keeps its raw bytes in the scan and
counts as skipped, so coverage stays unknown.
* fix(release-exposure): keep UTF-8 members that hold a NUL
Any NUL in a member's first 512 bytes made the decoder give up, so a script
with a literal NUL in a string never reached the credential or inline-map
checks. An exposed marker in it read as unknown, and --fail_on high exited 0.
Validity alone cannot tell wide text from UTF-8, since ASCII in UTF-16 is
valid UTF-8. Treat a header that is an eighth or more NUL bytes as wide text
and anything not UTF-8 as undecoded, and scan the rest.
The docs now separate BOM-marked UTF-16, which is scanned, from wide text
with no byte order mark, which is not.
* fix(audit): scan every string a binary plist stores
The plist scan read only the decoded value, which drops objects the root no
longer references and entries a later duplicate key replaced. Those bytes
still ship, but the audit marked the scan complete, so a credential stored
there read as not observed and --fail_on high exited 0.
Read strings, keys and data from the object table instead. A stored object
that cannot be read leaves the member unassessed, and a total byte budget
bounds table entries that overlap to repeat the same text.
* fix(audit): keep packaged native images from ruling out Electron
The applicability check only looked at the "native images" omission. An
Electron executable inside a compressed .NET or PyInstaller member is recorded
under "packaged native images", so a directory whose on-disk hosts had no fuse
sentinel read as not Electron and all four fuse checks as not applicable.
Treat that omission as an unread image too.
* fix(audit): keep readable plist text when one object is unreadable
Throwing on one unreadable stored object discarded every string already
decoded, so a UTF-16BE credential beside a truncated unused object was lost:
the raw bytes that replaced it cannot match NUL-separated text.
Return the decoded text with complete false instead, and keep the raw bytes
in the scan beside it so ASCII in the unread object is still seen. The member
stays unassessed. Text past the budget ends the same way.
* fix(audit): read escaped inline maps and extensionless SEA entries
A data URL may percent-escape its base64 payload, so a map such as one padded
with %3D did not decode and the audit counted no embedded source map. Decode
the escapes before reading the base64.
A directory walk classed a SEA entry by its code path, so a main script whose
code path names no script was never scanned as JavaScript and its inline map
went unseen. Class it by the role the SEA metadata gives it.
* fix(audit): check plist text against the budget before decoding it
decodeBinaryPlistText decoded each stored object before comparing its size
with the remaining budget, and it keeps going after an object is rejected so
later text is still read. A plist under the input limit could repeat a large
range through overlapping table entries and force gigabytes of decoding that
the audit then discarded.
Separate finding a stored payload from decoding it, so a range past the
remaining budget is rejected without allocating its text. Earlier text and
the incomplete result stay as they were.
* fix(audit): count a packaged PE with an unread header as a native image
A package member whose PE signature lies beyond the 4 KiB probe recorded only
a PDB failure. The image was neither read nor listed under packaged native
images, so a directory whose on-disk hosts had no fuse sentinel read as not
Electron and all four fuse checks as not applicable.
Count it as a packaged native image, the omission that keeps the question
open, and keep probing it for text as before.
* fix(audit): keep cleanup-incomplete errors from composed readers
A desktop, fuse, Go or Rust failure became a string on that check, so an
error with unfinished cleanup lost its resources and live bindings while the
audit reported success, and the MCP handler does not close the service after
each request.
Treat unfinished cleanup like cancellation: stop the remaining owners and
return the error with the partial audit. When the owner attached its own
partial, keep the error and replace the partial, so cleanup stays attributed.
* fix(audit): keep safe fuses unknown past a packaged native image
The fuse check counted sampled-out images as possibly unread but not native
images inside a package. A complete directory scan with a safe-fuse image on
disk, no ASAR hint, and an unopened packaged image reported the four Electron
checks as not_observed, although the Electron executable could be that image.
The fuse check, the Go and Rust checks, and the Electron rule-out each decided
this for themselves and had drifted apart. Move the decision to one domain
predicate, nativeImagesMayBeUnread, and use it in all three. A hinted
executable that was read still settles the check.
* fix(audit): settle fuses only on an identified host, and read Bun and HTML maps
A retained helper set electron_executable_included because the rank marks
every .exe or extensionless sibling of resources/app.asar. With more than four
siblings, or an inventory that stopped early, safe fuses on a helper reported
not_observed while the host went unread. Rank stays a sampling hint. The check
now settles only on an executable named as the host, or when inventory was
complete and every executable that could be the host was sampled.
A Bun sourcemap region is named .bunmap, so its role was lost and a complete
audit reported no source maps. Carry the region role from the reader's metadata
into the member view, in the expanded and unexpanded paths, and treat it as a
JavaScript map so an unparsable region stays unresolved, not a debug companion.
An HTML member was scanned as text but its inline scripts were never checked
for source maps. The HTML owner now returns each body, the reader examines it
like a script file, and bodies past the owner's limit count as skipped.
* fix(audit): charge map reads to the count before opening the member
An oversized or unreadable .map returned before readSourceMap counted it, so
the allowance of 32 never ran out. An unexpanded .NET or PyInstaller package
could then decompress every listed map member, about 1 MiB each, up to the
4,096 entry cap.
The count bounds reads, so examineMap now reserves the slot before it opens the
member. A map past the count is skipped unopened, and a linker map past it is
still settled from its bounded prefix. An inline data: map is already in memory
and keeps its charge at the point it is parsed.
* fix(audit): examine scripts HTML loads whatever they are called
isJavaScriptMember knows a script by its suffix or an assigned kind, and the
reader dropped the script references the HTML owner parses. An extensionless
main loaded by <script src="main"> was scanned as ordinary text, so a complete
inventory reported javascript.source_map_sources not_observed with shipped
sources in its inline map.
The reader now resolves each JavaScript script reference against the page and
its base href, and a local target without a JavaScript name is examined as a
script when reached. A target read before its HTML file, never reached, or not
placeable on disk counts as a skipped map, so the check stays unknown.
* fix(audit): charge sibling map reads to the map count
Every generic .map beside a selected file got a full 1 MiB read and a
parse, with no cumulative bound, so the 4,096-entry listing allowed about
4 GiB of reading for one small selection.
Move the slot rule into chargeMapRead, which examineMap and sibling
classification now share. Past the count a sibling reads only the header:
a linker map is still identified, and one that may be a source map stays
a skipped companion.
* fix(audit): report cleanup failures raised by member reads
A close failure during an embedded-package probe, member read or inventory
reaches the shared read filter as an ordinary reader failure. The filter kept
only cancellation and integrity, so the member became a skipped read, inspect
succeeded, and the retained descriptor was reported nowhere. MCP does not close
the service after each request, so no caller saw it.
Propagate any failure that carries cleanup, whatever its reason, so it reaches
releaseExposureFailure and the audit reports its retained resources.
* fix(audit): charge unsuccessful text reads to the byte allowance
An oversized, undecodable, or failed text read left textBytes unchanged, so
every member got the full per-member budget and the cumulative allowance did
not bound what a package decompressed. Count the bytes member reads produce
and charge them whatever the read yields. An oversized file on disk still
costs only its probe.
* fix(audit): bound the paths kept for HTML script targets
A base href is copied into every script target it resolves, so a page under
the text member allowance could retain hundreds of megabytes of paths. Charge
each new target's resolved path to a byte allowance and leave targets past it
unassessed.
* fix(audit): keep examining a page loaded as a script as HTML
A script element naming an .html member promoted that member to a
script, and the text pass then chose one reading. The page's own script
elements were never read, so an inline map in it was lost and a shipped
sourcesContent went unreported.
Run the script reading and the HTML reading independently, and list the
script target allowance among the cases that leave source map coverage
undecided.
* fix(audit): detect credential markers that end in a hyphen
The Google, GitLab, and Slack rules allow a hyphen in the token but ended
on a word boundary, so a token ending in a hyphen at the rule's minimum
length was missed and the audit reported the check as not observed.
Open-ended rules now take the whole token, and the fixed-length Google
rule checks the full token alphabet after its last character.
* fix(audit): confirm FAT identity for probed package members
The package probe treated a decoded FAT table as a universal Mach-O. A
JVM class past the 4 KiB probe shares the FAT magic, so its truncated
prefix decoded as a table, the member counted as an unread native image,
and Electron could not be ruled out of a complete scan.
Pass the member's declared size to the sniff and take identity from
classifyFatContainerEvidence. A member with no usable size, or with
slices past the probe, stays counted as possibly native.
* fix(audit): admit binary plists in package member probes
A package member's probe judged text by its own NUL test, which accepted
byte order marks but not the binary plist signature decodeMemberText also
reads. A PyInstaller executable shipping a binary Localizable.strings was
rejected without a scan or a skipped count, so a UTF-16BE private key
marker inside it could report not_observed.
Name the encodings that explain their own NULs in one predicate, and move
the plist fixture builders to module scope.
* fix(audit): locate Go findings only in images that retain build paths
The exposed decision required non-empty paths, but the locations took
every image whose build info read. An image with no paths was named as a
source of the leak beside the one that had them.
One predicate now decides both.
* fix(audit): keep completed findings when member cleanup fails
A descriptor close failure aborts the member scan, and the scan's state
was dropped with it, so the caller got a typed cleanup failure and no
report. Source maps already counted and credential hits already collected
were lost.
The reader now hands the service what it assembled, with coverage unknown
and a limitation saying so, alongside the failure. The service runs that
scan through its existing stopped-audit path: no later owner runs, and the
report goes on the error as its partial observation.
* fix(audit): keep coverage unknown for nested ASARs in package hosts
A member of a PyInstaller or .NET host reaches the binary probe as a
plain file. An ASAR among them has a NUL-bearing header, so the probe
dropped it with no traversal and no omission, and source map checks could
read not_observed with complete coverage.
Ask the ASAR owner's signature probe, and record a match as a nested
package that was not expanded, as inventory-classified ones already are.
* fix(audit): let native format facts outrank member names
examineMember tried the name-based text and map routes first, so a PE an
owner identified inside an ASAR, named payload.json, was read as text.
Its CodeView path and the packaged-native omission were both lost, and
native.pdb_path read not_applicable.
Route an owner-established native format to the native probe first, and
count an encrypted one as a packaged native image for the same reason.
* fix(audit): report relative paths given to compiler path flags
go.build_paths only kept rooted paths, so retained build settings like
CGO_CFLAGS=-Iprivate-sdk read not_observed even though the setting names
a directory.
Take the relative argument of a C compiler path flag, attached or in the
next field. Scope this to compiler settings, because Go's own tools use
the same letters for non-paths such as -B gobuildid and -T 0x1000.
* test(javascript): expect retained text on SVG inline scripts
The SVG test from main predates this branch's inlineScripts text field,
so each side passed alone and only their merge failed.
* fix(audit): keep an unresolved Electron host's fuses unknown
fuseCheck decided from whatever outcomes the images reported, so a host
with no fuse answer read not_observed as soon as any other image in the
directory reported safe fuses.
Pass the images named as the host into the report and leave a check
unknown when one of them has no answer for it. Other images stay free to
have no fuses, because helpers in a real Electron install have none.
* fix(audit): bound the paths kept for local source map targets
noteMapTarget kept every declared target resolved against its member's
directory, so a short URL under a long directory could retain far more
than the script's own size, with no cumulative limit.
Add the allowance noteScriptTarget already has. A target past it stays
unassessed and counts as a skipped map, and is never read.
* fix(audit): borrow the audit's scope admission for inventory
inspect holds resources.run for the whole audit, and inventoryArtifact
entered the same scope again. With failed cleanup pending from another
audit, the inner run waits for a drain that waits for the outer run.
Add inventoryArtifactInScope, which takes the caller's admission.
* fix(audit): run the exposure verifier's commands under supervision
The verifier launched npm and the CLI with raw execFile and then removed
its temporary tree, so a timed-out child could outlive the tree it was
using.
Use the shared verifier commands and close them before removing the tree.
Their exit error carries stdout on its snapshot, so the audit's exit-code
branch reads it from there.
* fix(audit): read relative -L paths and split flag arguments
The relative-path reader handled C compiler settings only, so a directory
kept through Go's own linker flag, such as -ldflags='-L private-sdk -s -w',
reported go.build_paths as not_observed.
A quoted flag and argument also reached it as one field. The separating
space stayed on the argument, which reported rooted paths twice and kept a
leading space on relative ones.
Give -ldflags its own pattern that matches only -L, so -B gobuildid and
-T 0x1000 stay ignored, and consume the separator in the flag patterns.
* fix(audit): keep embedded leaves beside schema-invalid map fields
The reader recovers independent indexed leaves only after a
SourceMapFormatFailure, but inspectSourceMap let the schema's ZodError
through. A sibling with mappings "!", a section with a bad offset or a
root with a bad field skipped recovery, so a valid leaf with
sourcesContent reported javascript.source_map_sources as unknown.
Normalize schema rejection in SourceMapFormat, which owns these parses,
so every structural failure reaches consumers as a format failure. The
message is unchanged, and the codec and web fetcher already map both
cases to the same result.
* fix(audit): keep packaged executables' own packages unknown
examinePackagedBinary counted a native image inside a package and moved
on, while only on-disk images reached the embedded-package probes. An
ASAR carrying a PyInstaller, Bun, Node SEA or .NET executable with a
source map therefore scanned complete and reported
javascript.source_map_sources as not_observed.
Run the same classifier on packaged images. The probes read carrier
offsets from a file, so the member's bytes stay in a temporary file for
the probe alone. A detected package keeps member coverage unknown, as a
nested ASAR does, and an image past one file's total byte allowance
stays unprobed and unknown. A plain native member leaves coverage intact.
* fix(audit): read an app's sampled images for fuses
A .app target sent only the bundle candidate to the fuse reader, which
resolves to the outer Electron Framework. A sidecar with RunAsNode enabled
was never read, so the check reported not_observed in the app view while
the directory view reported exposed.
Send the bundle candidate first and then the sampled native images. An
image whose digest matches the bundle's read is skipped, so the framework
is not read twice. A sidecar's safe fuses cannot settle the outer framework,
so a bundle read with no answer for a fuse keeps that check unknown.
* fix(audit): read a Node shebang as script identity
The path classifier gives an extensionless executable a resource kind,
so a script such as bin/runner with a `#!/usr/bin/env node` header was
scanned as text without its inline maps being examined or counted. A
complete inventory then reported no shipped sources for it.
The probe holds the first bytes a member reader sees, so establish the
identity there. The script keeps the usual outcomes: a map that is read
counts, and one past an allowance or undecodable stays unknown.
* fix(audit): keep linker strings out of Go build paths
Reading `-X 'main.usage=-L level'` as a library directory made
go.build_paths report `level`, so a plain string could fail an audit
under --fail_on low.
Read the optional package selector once, before the setting's first
flag, and keep the argument of -X attached to it, so a string it sets
is never scanned as another option.
* fix(audit): keep a failed probe directory removal owned
A packaged image's probe copies the member's full bytes into a temporary
directory. When removing it failed, the error became an ordinary
unreadable probe: the audit could still succeed, nothing could retry the
removal, and the client heard nothing about the leftover bytes.
Hold the directory in the reader's resource scope, so close retries it,
and report the unfinished cleanup without replacing a primary failure.
* fix(audit): keep a declared .map from reading as a linker map
A script's sourceMappingURL=assets.map was dropped because the name
already looked like a map. A malformed assets.map then settled as a
linker map, so both source-map checks read not_observed and
native.debug_companions reported an exposure the release does not ship.
Keep a generic .map name as a declared target and read it as a
JavaScript map, in either scan order, so it stays unresolved.
* test(audit): rethrow typed errors from release exposure checks
A failed Result was rewrapped in a plain Error, which dropped its type,
cause and cleanup details from the test output.
* fix(audit): match companions by member identity, not spelling
An ASAR member's location is relative, and a declared map target is
absolute, so a malformed assets.map read before z.js stayed a linker
companion. The examined-map lookup, which is normalized, then returned
early, leaving both source-map checks not_observed.
Index companions by memberLocationKey and keep each reported spelling
under it. Spellings stay distinct because symlinks can make them differ.
* fix(audit): retract only guessed linker maps on a declaration
A script declaring sourceMappingURL=symbols.pdb after the scan reached
symbols.pdb removed the shipped PDB from the companion list, because a
late declaration retracted any companion. native.debug_companions could
then read not_observed.
Keep name-identified companions in their own set, and retract only maps
that were read as non-JSON and guessed to be linker maps.
* fix(audit): record an unfollowed map link as skipped
The directory and ASAR readers do not follow links, so a symlinked
app.js.map dropped out before any map counter moved. Its target was
scanned as plain text, and both source-map checks read not_observed.
Count a link that names a map as skipped wherever entries reach the
audit, and mark it examined so a declaration does not count it twice.
* fix(audit): read relative paths in Go compiler and assembler flags
A binary built with -gcflags=-I private-sdk keeps that setting, but
relative parsing covered only C compiler settings and -ldflags, so the
directory dropped out of go.build_paths.
Give -gcflags and -asmflags their own path options, taken from what
go tool compile and go tool asm document, and strip the package selector
as -ldflags does. The assembler's -D defines a symbol, so it is not one.
* docs(audit): state the packaged image read allowance
Packaged ELF, Mach-O and PE images are read in full and copied to a
temporary file, sharing one max_file_bytes allowance, but the docs and
coverage.allowances still said only a PE gets more than a header read.
Name the allowance and the copies in docs/release-exposure.md,
RELEASE_EXPOSURE_ALLOWANCES and the max_file_bytes description.
* docs(audit): say how go.build_paths treats rooted -X values
A rooted -X value cannot be told from a file path, so the audit reports
it. State that, and why, so the rule is a contract rather than a side
effect of how flags are scanned.
* fix(audit): keep a declared sibling map out of the linker guess
When the selected file declared a sibling map, the sibling classifier
never consulted the declaration. A malformed assets.map then read as a
linker map and raised native.debug_companions.
Leave a declared sibling to the unreached-target accounting, as members
already are, so it stays an unresolved source map.
* fix(audit): keep undecodable Go and Rust metadata unknown
Unsupported meant two things: a container this reader does not handle,
and metadata the image holds in a form the reader cannot decode, such as
a compressed .dep-v0 section. Both read as not_applicable, so an image
that carries metadata reported that the check did not apply.
Mark the second case on the format failure and carry it on
AnalysisUnsupportedTargetError, so the audit records a failed read and
stays unknown. The tools' own errors and messages do not change.
* fix(audit): keep coverage unknown for a PE header past the probe
A packaged member whose PE signature lies beyond the probe prefix stayed
unclassified, so the embedded-package probe never ran and coverage stayed
complete. A map inside such an image then read as not_observed.
Record the member as an unexamined package where it is already marked as
a possible native image.
* fix(audit): read the scripts of a standalone SVG
An SVG can carry a script with an inline source map, but only .html and
.htm members reached the script owner, so such a file left every map
counter at zero and javascript.source_map_sources read not_observed.
Treat .svg as a script-bearing page. htmlArtifactReferences already
reads SVG script elements, so this adds no parsing.
* fix(audit): keep every possible host when no name picks one out
With no named Electron host, every sibling ranks alike and
electron_host_paths stayed empty, so one image's safe fuses settled the
checks even when another possible host reported none.
Keep all sampled candidates as hosts in that case, so a candidate
without fuses leaves the checks unknown.
* fix(audit): read a text-named native member by its bytes
A PE member called payload.json took the text route, so it reached no
native probe, and its CodeView path and packaged-native uncertainty were
lost.
Decide from the bytes the text read already loaded, so real text costs
no extra read and the map and text allowances stay as they were.
* fix(audit): let a map or script read identify a native member
A package lists names without formats, so a PE called payload.js.map
took the map route and never reached a native probe. Its CodeView path
was lost, and with nothing counted as failed, native.pdb_path could read
not_applicable. A PE at a declared script or map target was lost the same
way, because declaration overrode the route.
Decide from the bytes the route already read, in the map route as in the
text route, and keep the listed kind for a promoted script target. Real
text and maps cost no extra read, so those allowances are unchanged.
* fix(audit): name a symlinked sibling debug companion
Selecting an executable skipped every symlink beside it before the name
check, so an app.pdb link left no companion path and no skip count, and
native.debug_companions read not_observed. Selecting the directory
reports the same link.
Check the name first. The map branch already needs a regular file, so no
link is followed.
* fix(audit): keep map coverage unknown for a linked script
A link named app.js to an extensionless payload was skipped as a link,
and its target was read as plain text with no map directives parsed. An
inline map there left no parsed or skipped count, so
javascript.source_map_sources read not_observed. Only links with a map
name were counted.
Count a link under any name that can declare a map, the set an unread
member already uses. It stays one omission when a page or a directive
also names it. The target is still not followed.
* fix(audit): keep a declared JavaScript map beside a selected file unknown
A local sourceMappingURL with a JavaScript map name such as app.js.map
was dropped on the assumption that it would be examined as a member.
When the selection is a file, sibling .js.map files are never read, so
the declared map left javascript.source_map_sources not_observed while
the same target spelled as a file: URL left it unknown. Record every
local target so an unreached one counts as skipped.
* fix(audit): read paths a C compiler forwards to the linker
CGO flags can hand linker options through -Wl,a,b or -Xlinker a. The
compiler flag parser saw -Wl,-L,private-sdk as one field and missed the
relative library directory, so go.build_paths reported not_observed.
Split forwarded options out before reading paths and read them with the
linker's own path options (-L, -T, --library-path, --script). This also
stops -Xlinker -Bsymbolic from being read as the compiler's -B prefix.
* fix(audit): read forwarded linker options by their linker meaning
The forwarded linker flag pattern read -Ttext=0x400000 as a script path
named text=0x400000, and it ignored -I, so -Xlinker -Ilib/ld-custom.so
lost the interpreter path the compiler flag pattern found before the
split.
Exclude the -T section and segment address options (bss, data, text,
text-segment, rodata-segment, ldata-segment) and read -I and
--dynamic-linker as paths.
* fix(audit): split Go flags as Go does and read external linker flags
Go splits a flag value with quoted.Split: a field that starts with a
quote runs to the matching quote, and any other quote is a literal
character. The reader stripped every quote, so CGO_CFLAGS
-I"/home/O'Neil/include" reported /home/ONeil/include, while cc was
given the directory "/home/O'Neil/include", quotes included.
-ldflags kept -extldflags values with the Go linker flags, where only
-L counts, so -extldflags=-Lprivate-sdk reported no path. The values
are now read as the C compiler driver reads them, including -Wl, and
-Xlinker forwarding.
A package member named payload.json or payload.js.map that holds an
ASAR took the text or map route, which checked only native signatures,
so the nested archive was scanned as text and coverage read complete.
The route's own read now recognizes the ASAR signature as well, and the
nested package leaves coverage unknown.
* fix(audit): keep a named script beside a selected HTML file unknown
A script element whose target had a JavaScript name such as app.js was
dropped on the assumption that it would be examined as a member. When
the selection is an HTML file, sibling scripts are never read, so a
sibling holding an inline map left javascript.source_map_sources
not_observed while the same target named app left it unknown. Record
every local script target so an unreached one counts as skipped.
* fix(audit): read a retained -pgo setting as one profile path
`-pgo file` names a single profile, and under -trimpath Go keeps only
its file name, so the flag readers dropped values such as
`private-training.pgo` and split paths with spaces or commas. Report
the value whole, skipping `auto` and `off`.
* fix(audit): read -extldflags paths as the external linker splits them
With -ldflags set to -extldflags '-L "/srv/private sdk"', the outer
flag reader could not match the path behind the inner quote, and the
external reader kept only relative paths, so go.build_paths read
not_observed. Read each -extldflags value once, as go tool link splits
it with quoted.Split, and collect rooted and relative paths from those
same arguments. The rest of the setting is read as before.
* fix(audit): read the Go linker's other path options
go.build_paths read only -L as a Go linker path option, so a retained
-ldflags='-tmpdir=private-obj -s -w' reported no path and --fail_on low
missed the directory.
Read every option `go tool link -h` documents as taking a directory,
path or file: -I, -L, -benchmarkprofile, -capturehostobjs, -cpuprofile,
-fipso, -importcfg, -memprofile, -o, -r and -tmpdir. -extld, -extar and
-libgcc name a program or `none`, so only their rooted values count.
* fix(audit): keep a quoted Go argument whole
goFields already reads -L "/tmp/private - sdk" as two arguments, but
each field was then split again before every whitespace-led dash, so
go.build_paths reported /tmp/private instead of the retained directory.
Split again only a field that begins with a flag, after any package
selector, as a quoted "-L dir -s -w" does. Any other field is one
argument and is kept whole, in -ldflags, -extldflags and CGO flags.
* fix(audit): keep coverage unknown for an unread packaged image
When a listed package member is a native image but its first read
fails, examinePackagedBinary returned before probePackagedImage could
mark its possible embedded package unexamined. Coverage stayed complete
and javascript.source_map_sources could report not_observed for a
payload nobody read.
Mark member coverage unknown wherever a native member goes unread,
including an encrypted one, as an image the probe could not read
already does, and say so in the limitations.
* fix(audit): read rooted Go tool path arguments whole
`-L "/tmp/private,sdk"` and `-L=/tmp/a=b` name one directory, so a `,` or
`=` in a documented path flag's argument is literal. `-trimpath` keeps its
prefix=replacement structure.
* fix(audit): count an unread packaged member once
* fix(audit): keep trimpath rewrites and directory fields apart from selectors
* fix(audit): keep a .map unsettled once a declaration was discarded
* fix(audit): skip env option arguments when reading a Node shebang
* fix(audit): keep companion coverage unresolved for a generic .map link
* fix(audit): count a dropped linker-map guess as a skipped companion
* fix(audit): read env settings after -- in a Node shebang
* fix(audit): route a selected native image named .map by its format
A directly selected or listed native image has no package format, so
examineMember skipped its native-first branch and sent a file such as
release.map through map routing. Its bytes then became a linker-map
guess and marked native.debug_companions exposed. Native format now
outranks the name for every member, packaged or not.
* fix(audit): read forwarded -rpath and -rpath-link as linker paths
A cgo build with CGO_LDFLAGS=-Wl,-rpath,private-runtime kept the
relative directory, but the forwarded linker parser only knew -L, -T,
-I, --library-path, --dynamic-linker and --script, so go.build_paths
reported not_observed. Add -rpath and -rpath-link to the same parser,
in the attached, = and next-field forms. Loader-expanded values such as
$ORIGIN/../lib and @loader_path/Frameworks name no build-host directory
and stay unreported.
* fix(audit): read each entry of a forwarded linker search list
-rpath and -rpath-link take a colon-separated search list. The loader
token filter tested the whole list, so -Wl,-rpath,$ORIGIN:/opt/private-runtime
dropped /opt/private-runtime along with $ORIGIN, and the rooted-path pass
cannot see an entry that follows a colon. Split each search list into its
entries and drop only the loader-expanded ones.
* fix(audit): expand loader tokens only in a linker search list
The runtime loader expands $ORIGIN and @loader_path only in a -rpath or
-rpath-link search list. The token filter also ran on the argument of -T,
-L and -I, so -Wl,-T,$ORIGIN.ld dropped a literal linker script path and
a complete read could report go.build_paths as not observed. Search list
entries keep the filter; every other linker path argument is kept as is.
* fix(audit): count a .map link beside a selected file as a skipped companion
The sibling scan skipped any .map entry that was not a regular file, so a
symlink such as symbols.map -> payload beside a selected executable left
debug companion coverage at not observed. The reader never follows links,
so the linker map such a link may name is unread, not absent. Count a
generic .map sibling link as a skipped companion without following it,
as noteUnfollowedMapLink already does inside a selected directory.
* fix(audit): split a linker search list only for an ELF image
Only the ELF linker reads a -rpath or -rpath-link argument as a
colon-separated list. The Mach-O linker stores each -rpath argument as one
directory, so -Wl,-rpath,@loader_path/lib:extra is a single loader-relative
entry, yet it was split and extra was reported as a build path. Carry the
native image format into the Go flag reader and split search lists only for
ELF; other formats read the whole argument as one entry.
* fix(audit): expand only the image loader's runtime tokens
A runtime search path token is expanded only by the loader of the image's
format. The ELF loader expands $ORIGIN, $LIB and $PLATFORM; dyld expands
@executable_path, @loader_path and @rpath. Each is literal text to the
other loader, so -Wl,-rpath,$ORIGIN:/opt/private-runtime on a Mach-O image
names a literal directory, yet it was dropped and go.build_paths reported
not_observed. Pick the token set from the native image format; the PE
loader reads no search list, so it expands none.
* fix(audit): keep a declared .map link beside a selected script a source map
The sibling scan counted any .map symlink as a skipped debug companion
before checking whether the selected script declared it as its source
map. classifySiblingMap already excludes a declared target that is a
regular file, so a declared symbols.map link added a false unknown to
debug_companions. Apply the declared-target check before either file-type
branch; noteUnreachedTargets still counts the unread map as skipped.
* fix(audit): byte-check members whose kind is only an inventory path role
Inventory gives a packaged member a role from its name, such as resource
for payload.json or source-map for payload.map. The packaged-format guard
treated any kind other than "file" as owner-stated, so a nested ASAR an
ASAR lists under one of those names skipped signature recognition and
cleared coverage. Track whether the package owner stated the kind (Bun
sourcemap region, SEA main script) and only skip the byte check then.
* fix(audit): read Go's linker -r setting as a runtime search list
go tool link -r sets the ELF dynamic linker search path, dir1:dir2:...,
which the linker writes to RUNPATH. It was read as one whole path, so a
release built with -ldflags='-r=$ORIGIN/../lib' reported the
loader-relative entry as a build path and go.build_paths as exposed.
Read -r entry by entry with the same image-loader-aware split and token
filter a forwarded -rpath uses: ELF splits at ':' and drops $ORIGIN
entries; Mach-O keeps the argument as one directory.
* fix(audit): keep a selected package executable's native carrier format
A directly selected PyInstaller executable named release.map recorded the
package format as its member format, even though its ELF or PE carrier was
already recognized. examineMember then missed the native-first branch and
treated the executable as a linker map, reporting a debug companion.
Forward the native carrier format for the selected file, as listed members
already do.
* fix(audit): keep coverage unknown for an unread unclassified member
A packaged member with no format of its own, such as a PyInstaller
payload.bin, recorded only a skipped text member when its read failed
before the header. Member coverage then stayed complete, though the
member could be a native image or carry a nested package. Leave
coverage unknown and say which member could not be read, as an unread
native member already does.
* fix(audit): keep ASAR header admission distinct from max_file_bytes
A directory holding an ASAR whose header exceeds the header admission
was reported as a file-size refusal with max_file_bytes advice, which
cannot fix it. Report the header admission as its own bound instead.
* fix(audit): thread the shared ASAR header admission into release exposure
createReleaseExposureService built ReleaseExposureReader with no admission,
so its inventory and every reopened ASAR reader stayed on the 16 MiB
default and ignored REA_ASAR_MAX_HEADER_BYTES and asar.max_header_bytes.
Resolve the admission in the composition with runtimeAsarHeaderAdmission,
as the Electron and V8 observation providers do, and pass it to both
inventoryArtifactInScope calls, the reopened container readers, and the
nested ASAR signature probe. The refusal now names the setting that can
raise it instead of claiming there is no runtime override.
* fix(audit): keep a nested ASAR probe stopped at the admission undecided
The release exposure reader treated an ASAR signature probe that stopped
at the header admission as "not an ASAR". A packaged member such as
payload.json holding an ASAR whose JSON starts past asar.max_header_bytes
was then read as plain text, its inner maps went unread, and
javascript.source_map_sources could stay not_observed.
A probe that reaches the admission limit, or runs out of a prefix read
after its extents parse, now marks package coverage unknown and records
a limitation, while the member is still examined as text.
* fix(cli): parse audit-release-exposure --max-file-bytes as a decimal token
Main now requires every numeric CLI option to admit decimal tokens and
reject 0x10 and 1e3 (tests/boundary/cli/cliDecimalArguments.test.ts).
Wrap the release exposure maxFileBytes option in cliDecimalField like the
other maxFileBytes options, so the domain field keeps its bounds and default.
* docs(specs): add the release exposure audit specification
Add spec.md, plan.md and tasks.md under docs/specs/artifacts/release-exposure/
as docs/development/documentation.md requires for a new feature, and list
them in docs/specs/README.md. The existing guide at docs/release-exposure.md
keeps describing current behavior and its route is unchanged.
* fix(audit): bind unpacked ASAR member reads to their inventoried digests
A selected ASAR is captured and checked against its inventoried digest, which
binds packed members. An unpacked member is read from its .unpacked companion
on disk instead, and readContainerMember and readEntryBytes never compared
those bytes with the member digest inventory recorded, so a companion
replaced between inventory and the scan was accepted.
readContainerMember now passes the member's inventoried digest for unpacked
entries, and readStreamBounded hashes the full stream, draining past the read
allowance when needed, and fails with "ASAR member changed before
interpretation", which surfaces as an artifact_changed error like the
container check. A regression test replaces a companion after inventory. The
guide, spec, plan and tasks describe the binding.
* fix(audit): let stated JavaScript roles outrank a .map name
isDeclaredMap() routed any member whose name ended in .map to map
handling before the owner's stated role or a page's script declaration
was considered. A Node SEA main script at boot.map, which the SEA reader
marks as javascript, and a .map script an HTML file loads both became
linker-map guesses, so their inline maps were never read and
native.debug_companions was reported exposed.
An owner-stated source-map role or a declared map target still makes a
member a map. Otherwise a stated javascript kind or a script target now
wins, and only then does the .map suffix, or the inventory path role that
repeats it, decide. Regression tests cover both cases, and the guide
notes that a SEA main script is read as a script whatever it is called.
* fix(audit): keep coverage unknown when a packaged text member fails to read
A compressed member named as text, such as payload.json in an unexpanded
PyInstaller host, can fail inflation before any header is returned. The
.bin case already leaves member coverage unknown because an unread header
may hide a nested image or package, but examineTextMember() only counted
the member as skipped text, so otherwise complete coverage stayed
complete and package-dependent checks could settle.
An unread packaged member whose kind its package did not state now goes
through noteUnreadBinary(), which counts it as skipped text, marks
coverage unknown, and records the limitation. A regression test corrupts
the zlib stream of payload.json in a CArchive host and checks the intact
member keeps coverage complete. The guide describes the rule.
* fix(audit): keep the unpacked ASAR digest check inside the read allowance
ea87cf8 made readStreamBounded hash an unpacked ASAR companion to its end
so it could compare against the inventoried digest, which drained the
stream past the read allowance. A 1 GiB unpacked module was read in full
for a 4 KiB header probe and again for a package probe, and a companion
that grew after inventory had no byte limit on that path.
readStreamBounded now stops at the allowance as every other member read
does, and compares the digest only when the member ended within the
allowance. A companion past the allowance stays over_allowance without a
digest verification. A test counts the bytes each scan read pulls from an
8 MiB companion. The guide and spec state the bound.
* fix(audit): read a script target whose bytes are a source map as a map
1d3f89f let a stated javascript kind or an HTML script target outrank a
.map name, so an HTML `<script src="main.map">` that points at a valid
source map with sourcesContent was read only as a script. Its sources
were never counted and javascript.source_map_sources could miss an
exposed finding.
A script role says how a member is loaded, not what its bytes hold. A
.map member or loaded script whose text parses as a source map, a JSON
object with version and mappings, sources, or sections, is now read as
a source map. Anything else is still read as a script, so a SEA main
script named boot.map and a .map script carrying an inline map keep
reaching examineInlineMaps. A regression test covers the map case, and
the guide states the rule.
* fix(audit): retract a linker-map guess when a page loads the map as a script
noteScriptTarget() returned early for a target already read, counting its
source-map coverage as skipped but leaving any linker-map guess the first
read recorded. When a directory or ASAR listed a non-JSON app.map before
the index.html that loads it with <script src="app.map">, app.map stayed in
native.debug_companions and the result depended on enumeration order.
noteMapTarget() already retracted such a guess for a late map declaration.
A late script declaration now retracts the guess too, and still counts the
script as skipped because it was never examined as JavaScript. A test
covers the directory and ASAR order, and the guide states the outcome.
* fix(audit): respect script and native identity for sibling linker maps
classifySiblingMap() guessed a linker map for any sibling .map whose bytes
could not begin a JSON object. A sibling boot.map that a selected index.html
loads with <script src="boot.map">, or an ELF image named release.map beside
a selected executable, was therefore reported in native.debug_companions.
scanSiblings() now skips a sibling that is a script target as well as a map
target, leaving noteUnreachedTargets() to count it, and classifySiblingMap()
records no guess when sniffNativeFormat() recognizes the header. Tests cover
both cases and the guide states the outcome.
* fix(audit): read a script-loaded map from the text that recognized it
scriptHoldsMap() recognizes a .map or script target as a source map from
decodeMemberText(), which honors a UTF-16 byte order mark, but
readScriptAsMap() then passed the raw bytes to readSourceMap(), which reads
UTF-8. A BOM-marked UTF-16 main.map loaded by <script src="main.map"> failed
the second parse and its prefix became a linker-map guess.
readScriptAsMap() now hands the decoded text to readSourceMapText(), which
readSourceMap() also uses for byte input, so both stages read the same text.
A test covers a UTF-16 map with embedded sources.
* fix(release-exposure): honor byte order marks in binary plist data
A binary plist data object holding BOM-marked UTF-16 text was decoded as
UTF-8, so credential markers in it went unmatched while coverage still
read complete. Decode such payloads with the encoding their mark declares,
and leave coverage incomplete for UTF-32, which no reader scans.
* fix(audit): keep an unprobed package member past the text allowance unknown
Once the cumulative text allowance was spent, examineTextMember() counted a
packaged member named like text, such as payload.json in a .NET or
PyInstaller bundle, as skipped text without reading its header. Coverage
stayed complete even though the member could be a native image or a nested
ASAR, while the same bytes named payload.bin kept coverage unknown.
An unexpanded package member whose kind the owner did not state now leaves
coverage unknown with a limitation when the allowance skips it, the same
uncertainty an unread .bin member carries. No extra read is made, so the
allowance still bounds what is opened. A test covers an ASAR with an
embedded source map named payload.json after sixteen full text members.
* fix(release-exposure): require a read host for every Electron layout
A directory with several resources/app.asar layouts counted one named
host as fuse coverage for all of them. Track host coverage per layout so
safe fuses stay unknown when a whole layout's possible hosts were
dropped from the native image sample.
* fix(release-exposure): keep a .map script past the target allowance unassessed
When HTML script references spent the resolved-path allowance, a later
.map script target was dropped without its script role, so its JavaScript
bytes settled as a linker-map guess and debug_companions reported an
exposure. A refused script target now sets mapTargetsDiscarded, the same
flag a refused map target sets, so a later .map stays unassessed instead
of becoming companion evidence.
* fix(release-exposure): read a Node shebang as script in any text member
A selected or listed text file such as runner.txt that starts with a Node
shebang went straight to the text reader, which never gave it a JavaScript
role, so its inline map went unread and source_map_sources read
not_observed. The text reader now takes the role from the bytes it read,
so every route into it sees the shebang, and the probed-text path no longer
sets the role itself.
* fix(release-exposure): exclude Go search lists by position, not by value
compilerPaths dropped every literal path whose text equalled a forwarded
-rpath or -rpath-link list, so CGO_LDFLAGS=-Wl,-L,$ORIGIN -Wl,-rpath,$ORIGIN
lost the literal -L directory and go.build_paths read not_observed. The
literal flags are now read with each search list option and its argument
removed by position, and the search lists are still read entry by entry.
* fix(release-exposure): keep a probed Node launcher's role past the allowance
The probe recognized a Node shebang but handed the text reader the member
without its JavaScript role. Once earlier text spent the shared allowance,
the text reader returned before reading the header again, so an
extensionless launcher added no skipped map and both source-map checks read
not_observed instead of unknown. The probe now passes the role it found.
* fix(release-exposure): decode a map in the encoding its byte order mark names
The map reader, its prefix check and the sibling classifier decoded map
bytes as UTF-8, so a generic symbols.map holding BOM-marked UTF-16 source
map JSON failed to parse and its prefix read as a linker map: a spurious
debug companion and source_map_sources not_observed. Map bytes are now
decoded by their byte order mark at that boundary, and a UTF-32 map, which
this reader does not decode, stays unresolved.
* fix(release-exposure): keep a lone dash inside a quoted Go path argument
splitGoField split a quoted field before every whitespace-led dash, so the
retained -ldflags '-s "-L=/tmp/private - sdk" -w' read the dash inside the
directory as a new flag and goPaths reported /tmp/private. A quoted field
now splits only before a dash that begins a flag, so a dash followed by a
space stays part of the path.
* fix(release-exposure): choose possible Electron hosts within each layout
The named-or-possible host choice ran across every kept image, so a named
a/a.exe removed the unnamed b/launcher.exe of a second ASAR layout from
electron_host_paths, and its unresolved fuses no longer held
electron.run_as_node at unknown. Each layout now chooses its own named or
possible hosts, and the sets are combined.
* fix(release-exposure): read a relative compiler --sysroot as a build path
The compiler path flag list had no --sysroot, and the rooted-path fallback
cannot see a relative operand, so CGO_LDFLAGS=--sysroot=private-sdk left
go.build_paths at not_observed. --sysroot is now a compiler path flag,
attached with = or given as the next field.
* fix(release-exposure): recognize Windows Electron layouts by the owner's path
The desktop owner gives each file a native local_path, such as
C:\ship\App\resources\app.asar on Windows, and desktopPathConventions only
splits on /, so every ASAR basis was skipped: host ranking and
electron_host_paths stayed empty and a named host with no sentinel let a
safe helper read electron.run_as_node as not_observed. Each basis now
carries whether the owner's slash-separated path is an ASAR layout, and the
layout folders read a Windows absolute path with Windows separators on any
host.
* fix(release-exposure): read a Bun module by its JavaScript loader
An extensionless Bun contents member such as $bunfs/root/release carries
loader_name js, but ownerStatedKind kept only the sourcemap region, so the
member was never read as a script and its inline map went unseen. Give a
Bun contents region the javascript role for the js, jsx, ts and tsx
loaders, as SEA main scripts get it.
* fix(release-exposure): read a Node shebang .map as a script
An undeclared `.map` was routed as a source or linker map before its
first bytes were checked, so a Node script named `runner.map` recorded a
linker-map companion and never had its inline map read. When the map
read of an undeclared `.map` starts with a Node shebang, its map slot is
returned and the text reader examines it as JavaScript, so its inline
map is parsed and no companion is reported.
* Detect full-length GitHub refresh tokens
Narrow github.token to gh[opus]_ with 36 characters and give ghr_ its
own 76-character github.refresh_token rule, with a test.
* fix(release-exposure): bind a negative package probe to the inventoried image
An on-disk native image whose embedded-package probe finds nothing clears member coverage, so it now hashes the probed descriptor and compares it with the inventoried digest before accepting absent. A replacement after inventory fails as a changed member instead of reporting complete coverage.
* fix(release-exposure): let an app bundle read settle only its own layout
An app target, an app-bundle fuse read or a sampled selected executable used to mark the Electron executable as included for every ASAR layout, so safe outer fuses reported not_observed even when a nested Electron app's host was never read. Those reads now settle only their own layout; every other discovered layout still needs its own host read, so an omitted nested host keeps fuse checks unknown.
* fix(release-exposure): honor a Node shebang before guessing a sibling linker map
A sibling .map beside the selected file that starts with a Node shebang is a script, as it already is for a member .map, so classifySiblingMap no longer records it as a linker-map debug companion.
* fix(release-exposure): name a bundle's own layout by the owner's path
The settled layout set joined the caller's bundle spelling with Contents, so a selected /tmp/App.app missed the owner's resolved /private/tmp/App.app/Contents layout and safe fuse checks fell to unknown. Each archive basis now carries the owner's relative layout folder, and the bundle's own layout is the one whose relative folder is Contents, looked up by the owner's resolved folder.
* fix(release-exposure): read file-valued linker options as build paths
LINKER_PATH_FLAG listed --script but not the other linker options that read a file, so -extldflags=-Wl,--version-script=private-versions.map left go.build_paths not_observed. --version-script, --dynamic-list and --retain-symbols-file now read their operand as a path, attached or in the next field.
* fix(release-exposure): count a macOS bundle's framework and MacOS hosts in its layout
A .app layout resolves to its Contents folder, but its hosts are Contents/MacOS/* and the Electron framework binary under Contents/Frameworks, so per-layout host ranking and fuse coverage dropped them and one bundle's safe fuses could settle another bundle. Those images now belong to the bundle's layout, with the framework binary ranked as its host.
* fix(release-exposure): keep a bundle's launcher out of its fuse hosts
A macOS bundle's Electron Framework binary carries its fuses. When it
is among a layout's named hosts, it is now the layout's only fuse host;
the MacOS launcher beside it is still sampled but no longer leaves safe
fuse checks undecided when it has no sentinel.
* fix(release-exposure): keep an undecided PE package member out of linker maps
An unexpanded package member named .map whose PE signature lies past the
retained prefix fell through to noteUnreadMap, which read its MZ prefix as
a linker map and reported native.debug_companions exposed with complete
coverage. The map route now records the same undecided-PE uncertainty the
binary route does, so the member stays unexamined and coverage unknown.
* fix(release-exposure): scan the text of an undecided PE map member
A packaged .map member whose MZ header is undecided returned before its
fully read bytes reached the credential scanner, so a PEM key in it read
as unknown instead of exposed. Scan bytes read in full before returning,
while still keeping PE coverage undecided.
* fix(release-exposure): keep an oversized text member's undecided PE header
A packaged text member past the per-member allowance keeps only its prefix,
so an MZ header whose PE signature lies further in returned null and the
text route dropped the native and package uncertainty. Note the undecided
header as the map and binary routes do.
* fix(release-exposure): join Electron layouts by caller path and keep partial discovery unknown
The desktop owner resolves its root, so a target selected through a
symlinked parent got canonical local_paths that never matched the
audit's caller-spelled images, and the named host dropped out of
electron_host_paths. Rebase owner paths under the canonical root onto
the selected path.
When discovery stopped after it found one Electron layout, the basis
list was treated as exhaustive. Report the layouts as incomplete so safe
fuses on the listed host stay unknown.
* fix(release-exposure): keep Electron hosts under a trailing-slash selection
Join the rebased archive path with appendCallerPath so a selected
`/work/app/` yields `/work/app/resources/app.asar`, whose layout folder
matches the inventoried executables' parent.
* fix(release-exposure): keep maps unassessed for unread text past the allowance
A text-named member such as `runner.txt` can start with a Node shebang.
Once the text allowance is spent its header is not read either, so count
every unread member that could hold a script as a skipped map instead of
trusting the filename-derived role.
* fix(release-exposure): keep an unread packaged map member undecided
When a packaged map member yields no header (a failed read or no map
slot), its name is no evidence of its format. Route it through
noteUnreadBinary, as the text route does, so a member that may be a
native image or package keeps coverage unknown.
* fix(release-exposure): keep an undecided sibling PE header out of linker-map guesses
A sibling .map past the map allowance keeps only its probe, so an MZ
header whose PE signature lies further in is undecided. Share the
undecided-PE test with packaged members and count such a sibling as a
skipped companion instead of guessing a linker map.
* fix(release-exposure): keep possible fuse hosts when no ASAR layout names one
Without an ASAR basis no image is ranked, so no image counted as a fuse
host and one image's safe fuses could settle a sentinel-less sibling.
While the desktop family is undecided and no layout names a host, treat
every sampled image as a possible host.
* fix(release-exposure): keep images outside a found ASAR layout as possible fuse hosts
An asar: false build beside a discovered ASAR layout gets no basis, so
its executables were never fuse hosts and the layout's safe fuses could
settle a sentinel-less image in the other build. When no name picks out
a host outside the found layouts, treat every sampled image there as a
possible host, as the no-layout case already does while the family is
undecided.
* fix(release-exposure): keep a dropped host outside a found ASAR layout unread
An asar: false build beside a found ASAR layout names no host, so an
executable outside every layout that the native-image sample dropped may
be that build's unread fuse host. Coverage no longer counts as proven
when such an image was inventoried but not sampled.
* fix(release-exposure): read Go build-path response files named in compiler and linker flags
A CGO setting such as `-Wl,@/tmp/private-link.rsp` or
`-Xlinker @/tmp/private-link.rsp` names a response file the linker reads
options from, and a bare `@FILE` is read by the compiler driver. Report
the file's path, relative or rooted like other path arguments, without
opening it. An option's own value, such as `-install_name
@rpath/libx.dylib`, and dyld runtime tokens are not response files.
* fix(release-exposure): keep a dropped host outside an ASAR layout unread whatever its name
The guard for a sampled-out host beside a found ASAR layout ruled images
out by filename, so an asar: false host named like `b/My.App` was missed
and safe fuses from the sampled layout settled the installation. Every
inventoried native image outside the found layouts that the sample
dropped now keeps the fuse checks unknown.
* fix(release-exposure): keep maps unassessed for an unreadable extensionless launcher
An unclassified file whose initial header probe fails, such as an
extensionless `runner` that became unreadable after inventory, may carry
a Node shebang and a map directive. The probe failure now leaves its
maps unassessed, as an unread `runner.js` or packaged member does,
instead of counting the file as holding no script.
* fix(release-exposure): read a response file named like a dyld token
`-Wl,@rpath/private-link.rsp` names a linker response file, not a dyld
runtime path, so dropping every `@rpath` or `@loader_path` argument hid
it. A dyld token is now skipped only as the value of an option that takes
one, such as `-install_name` or `-dylib_file`.
* fix(release-exposure): keep the partial member scan on ordinary cancellation
A scan cancelled after some members were examined reported its partial
scan only when cleanup also failed, so a clean cancellation dropped the
recovered source-map and credential observations. A cancelled scan now
hands its assembled facts to the interrupted-scan visitor with unknown
coverage and a cancellation limitation, and the caller still receives
the cancellation error carrying that partial audit.
* docs(release-exposure): describe the partial report kept when the member scan is cancelled
Since the member scan keeps what it examined on ordinary cancellation,
the status guide no longer says that cancelling the scan returns no
partial report. It now describes the partial observation and limits the
no-report case to cancellation before the scan can be assembled.
* fix(release-exposure): keep maps unassessed when a text header is cut short
A text-named member read with one or two bytes of the text allowance
left returns only `#` or `#!`, which cannot establish a Node shebang, so
`runner.txt` kept its non-script role and its unexamined inline map was
treated as absent. A partial or missing header that could still start a
shebang line now leaves the member's maps unassessed, as an unread
member's do once the allowance is spent.
* fix(release-exposure): keep dotted executables as possible hosts in ASAR layouts
Inside an ASAR layout, a sibling whose name had a dot and no `.exe`
suffix, such as `my.app`, was neither ranked for the native sample nor
counted as a possible Electron host. With shared libraries filling the
sample, that image could be dropped while the layout still counted its
host as read, so safe fuses on a helper reported `run_as_node` as not
observed. A dotted sibling that is not named like a shared library is
now sampled after plain siblings and counted as a possible host.
* fix(release-exposure): split env -S shebangs on unquoted blanks only
The env shebang reader split the whole line on spaces before handling -S,
so a launcher such as `#!/usr/bin/env -S NODE_OPTIONS='--conditions
development' node` named `development'` as its program and its maps were
never assessed. An -S operand now takes the rest of the line and is split
the way GNU env -S splits it: single and double quotes, backslash escapes,
\_ and \c, and a leading # comment. A string env itself rejects, such as an
unterminated quote, runs no program.
* fix(release-exposure): keep owner facts completed before cancellation
When a Go, Rust, fuse or desktop owner finished its Evidence and then saw
cancellation, completeProviderEvidence returned that Evidence as the
error's partial observation, but the audit only kept the error and
substituted a not-run fact, so a recovered Go build path or Rust package
list reported unknown. Each owner's Evidence-to-fact reading now lives in
one function used for both a successful read and a completed partial, so
the partial passes the same schema and inventory digest checks before its
fact is kept. Later owners still do not run.
* fix(release-exposure): keep env -S programs named by a variable unassessed
`env -S` expands `${NAME}` from the environment, so a launcher such as
`#!/usr/bin/env -S ${NODE_BIN}` may run Node, but the shebang reader kept
the placeholder as the program name and treated the file as plain text,
reporting its inline map as absent. The env splitter now marks fields that
expand a variable, and a program whose name holds one leaves the shebang
undecided: the member's maps count as unassessed, and a `.map` companion
with such a header is not taken as a linker map. Quoted or escaped `${`
stays literal, and a resolved basename such as `${DIR}/bin/deno` still
decides.
* fix(release-exposure): keep the ASAR header admission for selected files
A directly selected app.asar whose header exceeds asar.max_header_bytes
now returns the typed header admission refusal and its recovery, as the
directory scan already does, instead of a generic partial report.
* fix(release-exposure): keep a map member with a variable env -S program unassessed
An undeclared `runner.map` whose `env -S` program is `${NODE_BIN}` may
run Node, so `examineMapMember` now treats an undecided `nodeShebang`
result as an unassessed map and companion, as `classifySiblingMap`
already does, rather than falling through to a linker-map guess.
* fix(release-exposure): read a forwarded -Map linker map path
splitForwardedLinkerFlags hands -Wl,-Map=private-link.map to the linker
side, but LINKER_PATH_FLAG had no Map entry, so the relative map path was
dropped and go.build_paths read not_observed. Add Map to the file-valued
linker options so -Map=FILE and -Map FILE are both reported.
* fix(release-exposure): count unread text in undecided map branches
An oversized .map whose shebang names its program with an unresolved
variable, or a package member with an undecided PE header, returned
before counting its unscanned text, so credential markers could read
not_observed. Count any non-bytes read as skipped text in both branches.
* fix(audit): bound map reads and preserve linker paths
* fix(audit): retain unread package uncertainty
* fix(audit): retain ambiguous linker operands
* fix(audit): preserve compiler path separators
* fix(audit): retain distinct linker map occurrences
* fix(audit): apply map declarations to their exact source path
A declaration recorded only its NFC lookup key, so every member or
sibling sharing that key took the declared JavaScript-map route. On a
normalization-sensitive filesystem an unrelated linker map spelled in
another form lost its companion evidence when the declaring script was
scanned first.
Record the resolved spellings each declaration writes and treat only an
exact match as declared. Another spelling in the bucket is read as an
undeclared map; when no declared spelling is reached, its linker-map
guess stays unresolved coverage rather than companion evidence.
* fix(audit): keep partial inventory from settling fuse-host coverage
When a directory read found ASAR layouts but the inventory stopped
early, a separate asar: false build outside those layouts may never
have been listed. Only sampled-then-dropped images counted as a
possible missed host, so reading every found layout's host reported
safe fuses as not_observed for the whole selection.
In limitNativeSample, treat an incomplete inventory as a possible
missed host outside the found layouts unless the read root is itself
a found layout, where host identity already settles coverage.
* fix(audit): apply script targets to their exact source path
A page's script element was recorded only by its NFC lookup key, so on
a normalization-sensitive filesystem a distinct file spelled in another
normalization form inherited the JavaScript role. An unrelated linker
map such as café.map beside a loaded café.map was never classified
and debug companion coverage could settle as not observed.
Keep the resolved spellings each page wrote under the lookup key, as map
declarations already do, and give the script role only to a member or
sibling whose exact path a page loaded.
* fix(audit): keep partial inventory uncertain beside a root ASAR layout
When the read root was itself a found ASAR layout, an incomplete
inventory was trusted to have listed every possible fuse host. A second
asar: false build under a subdirectory names no layout and sits outside
the root layout, so an inventory that stopped after the root host could
miss its launcher and still report the root's safe fuses as
not_observed. The same launcher, once listed, already kept the result
unknown.
Drop the root-layout exemption in limitNativeSample so any incomplete
inventory beside a found layout keeps fuse-host coverage unknown. A
complete inventory that lists only the named host still settles.
* fix(audit): read the linker's --just-symbols file operand
A Go build that forwarded -Wl,--just-symbols=private.syms through
-extldflags or CGO_LDFLAGS kept the relative symbol file in its build
settings, but the forwarded linker matcher knew only the short -R
spelling. Complete metadata then reported go.build_paths as
not_observed while the equivalent -R build was exposed.
Add --just-symbols to the linker options that read a file. Only -R
turns into -rpath when given a directory, so the $ORIGIN ambiguity
check stays on the short spelling.
* fix(audit): anchor expanded package members to the selected path
An inventory-expanded package member reports its location relative to
the selected path, such as app.asar/boot.map, but its lookup identity
resolved that location against the process working directory. An
on-disk page loading app.asar/boot.map, or an absolute file: map
declaration into the archive, resolved under the selected root, so the
declaration never matched the member it named. A script target with an
inline map then settled as a linker map, and a declared map stayed
skipped.
Resolve every member lookup and declaration base against the selected
path, and keep the reported location as written. Linker-map retraction
compares the same anchored spelling.
* fix(audit): read GCC's -I- include barrier as an option
CGO_CFLAGS=-I- is GCC's include barrier, the same as --include-barrier,
not -I with the directory "-". The compiler path matcher read it as a
build path, so a Go binary retaining it reported go.build_paths exposed.
Skip that exact field before path matching.
* fix(audit): keep packaged-image uncertainty after reading the host
A native image stored inside a package is never opened by a fuse reader,
so reading the Electron host on disk does not settle its fuses. Count the
packaged omission toward a partial fuse sample on its own, so safe host
implications stay unknown instead of not_observed while a packaged image
is unread.
* fix(audit): require every named host in a layout to be sampled
An ASAR layout can name more than one host, such as `a/a.exe` beside
`a/electron.exe`. The sample counted the layout as read when any one
named host was kept, so dropping the other still reported the Electron
executable as included and let safe fuses from the sampled images settle
run_as_node as not_observed. Count a layout as read only when every host
it could have was sampled, reusing the named-host selection the fuse
reducer already applies.
* fix(audit): keep a layout-less bundle read from settling partial inventory
A bundle with no ASAR layout, such as an asar: false build, counted its
framework read as covering the Electron host even when inventory stopped
early or the sample dropped an image. A nested build's host could sit in
the part never listed, so the root framework settled RunAsNode as
not_observed. Apply the same incomplete-inventory guard the layout branch
uses to the bundle read.
* fix(audit): read the linker's --sysroot directory operand
A Go ELF build that forwarded -Wl,--sysroot=private-sdk through
-extldflags or CGO_LDFLAGS kept the relative sysroot in its build
settings, but the forwarded linker matcher did not know the option.
Complete metadata then reported go.build_paths as not_observed, while
the compiler-owned --sysroot was already recognized.
Add --sysroot to the linker options that take a path, so the -Wl comma
form, the -Wl = form and both -Xlinker forms are read.
* test(audit): group Deno boundary regressions
* fix(audit): preserve Go linker file operands
* fix(audit): track script completion per source occurrence
* refactor(audit): give Go path parsing one semantic owner
* refactor(audit): preserve map roles at the parsing boundary
* refactor(audit): consume toolchain operands once
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
85 KiB
CLI and Evidence
Use the CLI for a direct inspection or a script. It uses the same application workflows and evidence contracts as REA's MCP server. Each CLI invocation is a separate process; save results when you want to use them in a later command. For a pinned, noninteractive workflow with retained reports, see static analysis in GitHub Actions.
Run a command
After installing REA, use rea. You can also run a command
without a global installation:
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
Use an extracted application directory or an ASAR as the target. Static
JavaScript analysis returns the application graph and Evidence directly.
Generic rea analyze PATH selects this workflow for directories and .asar
files when neither --provider nor --snapshot is supplied. See
JavaScript artifact reconstruction
for results, integrity checks and coverage.
The complete application Evidence can be hundreds of megabytes for a real application. When an agent or a person reads the result, save it to a file and project a summary, a module page or one module from the saved Evidence:
rea analyze-javascript-application /absolute/path/to/app --json > app-evidence.json
jq -c '{source: {kind: "inline", evidence: .}, view: {kind: "summary"}}' app-evidence.json > app-view.json
rea inspect-analysis-view app-view.json
The request can also name the saved file instead of embedding it:
{"source": {"kind": "evidence-file", "path": "app-evidence.json"}, "view": {"kind": "summary"}}.
The CLI resolves a relative path against the working directory; MCP clients
pass an absolute path. REA reads and authenticates the file, then projects it
like retained Evidence, so a large file is bounded by the file reader rather
than charged as inline input.
An argument that accepts inline JSON or a JSON file path also accepts -, which
reads the request from standard input, so a generated request needs no
temporary file:
jq -nc '{source: {kind: "evidence-file", path: "app-evidence.json"}, view: {kind: "summary"}}' | rea inspect-analysis-view -
Standard input is read once as a single UTF-8 JSON document without a byte-order mark, and it is limited to 8 MiB. Put a larger request in a file.
Local file inspection commands reject empty paths and NUL bytes before resolving
relative inputs, auxiliary files or output destinations. An empty string never
selects the working directory; use an explicit . when selecting that directory
is supported by the command.
For example, a 334 MB Obsidian application Evidence file yields a summary view
of about 10 KB. Replace the view with
{"kind": "page", "collection": "modules", "offset": 0, "limit": 32} for a
module page. See
JavaScript application workflows for
views, feature traces and comparisons that take the same saved Evidence.
Add --timings to a command to write one JSON timing record to stderr. For
example, rea inspect-elf-notes program --json --timings > result.json keeps
stdout as a parseable result and writes
{"rea_timings":{"version":1,"command_ms":42}} to stderr. command_ms
measures CLI command dispatch and result delivery; it is not provider-only or
analysis-only time. The flag is off by default, and the record omits stages
that REA did not measure. Root version reporting does not dispatch a command
and has no command timing record.
JSON Lines record output
Use --format jsonl to consume row-oriented results one record per line:
cat > observations-view.json <<'JSON'
{"source":{"kind":"evidence-file","path":"app-evidence.json"},"view":{"kind":"javascript-facts","property_keys":[],"offset":0,"limit":32}}
JSON
rea inspect-analysis-view observations-view.json --format jsonl > observations.jsonl
This request selects up to 32 observation identities and their provenance, without observation property values.
This changes JSONL object-collection framing. Scripts that need one complete
report or a scalar field should use --format json. For a full application
graph, first project a supported analysis view rather than selecting an
arbitrary nested graph array.
A top-level array emits its elements in source order. An object must have one
clear rows, items, records, or occurrences array, directly or under
page. Evidence uses the same boundaries inside normalized_result.
Any other selection is refused before a record is written, including an
arbitrary report, a scalar, a nested array elsewhere, and a columnar object such
as the inspect-sqlite-database --table normalized_result.rows
({columns, values}). The refusal is the caller error invalid_request
(invalid_input) and names the selection and its observed shape; the analysis
itself succeeded, so use --format json for the complete document. If several
collections exist, select one with --filter-output, for example
normalized_result.rows for an analysis view.
Object collections finish with a control record:
{
"$rea": "jsonl/v1",
"type": "metadata",
"records": {
"path": ["normalized_result", "rows"],
"emitted": 2,
"source": 10,
"filter": "normalized_result.rows[0,2]"
},
"context": {
"evidence_id": "ev_…",
"normalized_result": {
"coverage": {
"status": "page",
"examined": 10,
"total": 100,
"next_offset": 10,
"exhausted": false
}
}
}
}
This abbreviated example illustrates framing. The actual context preserves
all non-row fields, including Evidence subject, provider, authority, confidence,
locations, links, limitations, parent/view digests, pagination and coverage.
With --full-output, it also retains the command envelope and its metadata.
A data filter selects row fields/slices while this context remains available;
excluded peer record collections and unselected Evidence raw_result are not
restored. An explicitly selected raw_result remains selected. Retain the
original Evidence for complete observations.
records.emitted and records.source count the selected output and its source
array; they do not assert analysis completeness. Source coverage and unknowns
retain their original meaning. Existing row identities and indices remain
unchanged unless your field selection excludes them. Empty object collections
still emit metadata; an empty top-level array emits no lines. Bare top-level
arrays have no synthetic coverage record.
Command and output-validation failures emit {"$rea":"jsonl/v1","type":"error","ok":false,"result":…}
and retain the original diagnostic payload. SDK validation/sentinel errors use
the SDK's exit status rather than guessing from data fields. $rea is reserved
at the top level of a data record, so control records are unambiguous; collisions
are refused before rows are written. Nested $rea values are ordinary data.
A missing final metadata record or nonzero exit can indicate an interrupted
export; do not treat already emitted rows as a complete result.
--token-count estimates this exact representation, including metadata, one
line at a time. Errors remain error records even with token counting enabled.
Token windows cannot preserve JSON Lines and are rejected. JSON encoding and
stdout writes are incremental, including large escaped strings; this is output
streaming over a completed result, not incremental analysis. The analysis still
needs its normal resources. A single record must fit the runtime string limit
for token estimation; remove --token-count to stream a larger record.
Standalone version and MCP doctor diagnostics remain single JSON documents in JSON Lines mode. Incur's streaming generator events and successful sentinel responses retain their SDK protocol. MCP application results and Evidence contracts are unchanged.
Shell completion
The CLI includes optional shell completion. Run rea completions --help to
see the available shells and their setup commands. For Bash, load the hook
into the current shell:
eval "$(rea completions bash)"
For zsh, initialize completion before loading the hook. If your shell already
runs compinit, place the hook after that existing initialization:
autoload -Uz compinit
compinit
eval "$(rea completions zsh)"
The Bash hook supports macOS system Bash 3.2. It uses optional completion
features when the shell provides them and falls back to portable registration.
Completion includes standard global options and the active command's options;
rea mcp offers only add and doctor. Output-option values remain data when
resolving the command scope.
For future interactive non-login Bash sessions, add the command to
~/.bashrc. Interactive login Bash sessions read the first available file in
~/.bash_profile, ~/.bash_login, and ~/.profile; add the command to the
file your shell reads, or have that file source ~/.bashrc. Remove the command
from the startup file to stop loading completion, and run complete -r rea to
remove the registration from the current shell. These are manual, opt-in
steps.
Completion is a discovery aid; use rea COMMAND --help to confirm the
available options and subcommands.
Managed assemblies (.NET)
For a .NET PE/CLI assembly, inspect its identity and deployment form before inspecting members. These commands read metadata and Common Intermediate Language (CIL) without loading or executing the assembly and require no native analysis engine:
rea inspect-managed-artifact /absolute/path/Assembly.dll --json
rea inspect-managed-members /absolute/path/Assembly.dll --json
rea inspect-managed-members /absolute/path/Assembly.dll --type Example.Parser --json
Replace Example.Parser with an exact full_name from the member inspection,
or a declaring type name you already know. Omit --type to inspect all members,
or repeat it to select several types.
Selection limits decoding to the selected types; member references remain an
assembly-wide inventory, so the output can still be large.
Select --detail summary --evidence-bundle /absolute/path/members.json to
receive identity, selection, coverage, limitations and collection counts while
saving the complete original Evidence in a new bundle. Summary requires that
explicit destination; existing files are preserved. Later
inspect-analysis-view calls can page the exact parent from the saved bundle
without reopening the assembly. See
CLI summary and saved member rows
for the complete command sequence. Omitted or --detail complete retains the
full inline response.
Choose the workflow from the artifact's contents rather than its .exe or
.dll extension. The managed parser requires PE/CLI metadata; it does not
unpack .NET single-file hosts or decode IL2CPP. For the image, type and method
names in a Unity global-metadata.dat, use
IL2CPP metadata. NativeAOT method bodies require native
analysis. See the managed-code guide
for supported deployment forms, selection semantics, and coverage limits.
SQLite snapshots
For a local database snapshot, use rea inspect-sqlite-database PATH --json.
Add --table NAME --row-limit 20 to read a bounded sample from an ordinary
table. This command does not require a native analysis provider. See
SQLite database snapshots for database/WAL identity,
source-file handling and row-value semantics.
Native analysis
Inspect embedded Go build metadata without installing Go or configuring a disassembler:
rea inspect-go-binary /absolute/path/to/program --json
The shared CLI/MCP workflow reports compiler version, modules, replacements, build settings, original bytes and SHA-256. See Go binaries for supported containers and absent or malformed metadata.
Inspect a Godot PCK without running the game:
rea inspect-artifact game.pck --detail summary --evidence_output evidence.json --json
rea extract-artifact game.pck --json
Formats 2, 3 and 4 retain header, directory and integrity facts. Compact delivery keeps complete Evidence in the requested bundle. See Godot PCK for embedded packs, batch queries and unknown encrypted coverage.
Inspect a UnityFS AssetBundle without running Unity:
rea inspect-artifact content.bundle --detail summary --evidence_output evidence.json --json
rea extract-artifact content.bundle --json
Format versions 6 through 8 retain header, block and directory facts with none/LZ4/LZ4HC block decompression. See UnityFS for encryption and LZMA coverage boundaries.
Inspect a WeChat mini-program package without WeChat or an unpacker:
rea inspect-artifact app.wxapkg --detail summary --evidence_output evidence.json --json
rea extract-artifact app.wxapkg --json
Plaintext packages retain header, table and member facts with verbatim member
bytes. V1MMWX packages are detected as encrypted coverage and refused on
extraction. See wxapkg for layout and coverage boundaries.
Inspect an ESP32-family app or bootloader image without esptool:
rea inspect-artifact firmware.bin --json
rea extract-artifact firmware.bin --json
The inventory lists each segment with its ESP-IDF load classification, the checksum and SHA-256 results, the descriptor and any Secure Boot v2 signature blocks. See ESP images for recognition, coverage and refusals.
Inspect an ESP flash dump, or one read from a flash address other than 0:
rea inspect-artifact flash.bin --json
rea inspect-artifact dump.bin --target-format esp-flash-image --flash-offset 0x1000 --json
rea extract-artifact flash.bin --json
The inventory lists the bootloader, the partition table and each partition, with ESP image facts for app partitions and the OTA records. See ESP flash images for table location, inputs and limits.
Inspect a Node.js SEA carrier without running it:
rea inspect-artifact ./application --json
rea extract-artifact ./application --json
See Node.js SEA for supported layouts, script and asset paths, fuse observations, and the real-fixture verification lane.
Inventory and extract Intel HEX or S-record regions:
rea inspect-artifact ./firmware.hex --json
rea extract-artifact ./firmware.hex --json
See Intel HEX and S-record images for recognition, region names, metadata and limits.
Inspect and extract stored U-Boot legacy uImage payloads and components:
rea inspect-artifact ./firmware.bin --detail summary --evidence_output firmware.json --json
rea extract-artifact ./firmware.bin --json
See U-Boot uImage for verified CRCs, physical component ranges, unknown names, resource limits and retained views. Compressed bytes stay stored.
inspect-artifact and extract-artifact also read ZIP members compressed with
Zstandard (standard method 93). They retain the shared ZIP CRC, declared-size,
overlap and archive expansion checks. The native streaming decoder keeps the
existing 128 MiB Zstandard window cap; frames requiring a larger window are
reported as a resource limit (reason: limit, boundary: memory), rather than
as archive corruption. The cap cannot be raised at runtime: recompress with a
window of at most 128 MiB, or decompress externally before retrying.
Deprecated method 20 and other unregistered ZIP codecs remain unsupported.
Inventory and extract UF2 or DfuSe regions:
rea inspect-artifact ./firmware.uf2 --json
rea extract-artifact ./firmware.dfu --json
See UF2 and DfuSe images for recognition, families, numbering, the DfuSe suffix CRC and limits.
Inventory and extract U-Boot FIT images:
rea inspect-artifact ./boot.itb --json
rea extract-artifact ./boot.itb --json
See U-Boot FIT images for recognition, data sources, hashes and limits.
Inspect local model declarations without loading weights:
rea inspect-model-artifact /absolute/path/model.gguf --json
rea inspect-model-artifact /absolute/path/model.safetensors --hash full --json
Default hashing reads only headers and leaves full-file identity unknown.
--hash full streams the complete file for SHA-256. Both return typed metadata,
original tensor declarations and derived totals. MCP summaries retain complete
Evidence for filtered metadata, tensor and type-total pages; CLI compact delivery
uses --detail summary --evidence_output FILE. See model files.
Inspect Unity IL2CPP metadata names without Unity or a disassembler:
rea inspect-il2cpp-metadata /absolute/path/global-metadata.dat --json
The command decodes a version 24, 27, 29, 31 or 39 global-metadata.dat and reports assembly
images, type, method and member names, string literals, index ranges, header
sections and SHA-256. With --binary PATH it also reads the matching
GameAssembly.dylib, GameAssembly.dll or libil2cpp.so (never runs it) and
links each method to its code address; --generic-instances adds generic
method instances and --ghidra-seeds PATH then writes Ghidra function seeds. It does not decrypt metadata. MCP defaults to compact counts
with retained Evidence; query indexed rows with filters, batch indices and
selected fields. The CLI defaults to complete Evidence;
--detail summary --evidence_output FILE exports the full result to a new file
and returns a compact overview. See IL2CPP metadata.
Inspect an embedded cargo-auditable Rust dependency list without installing Rust or configuring a disassembler:
rea inspect-rust-binary /absolute/path/to/program --json
The same workflow reports package names, versions, sources, the root package,
dependency edges, and the section's file range. A missing .dep-v0 section is
null metadata, not proof that the binary has no dependencies. See
Rust binaries.
Inspect a PE Authenticode certificate table without executing the image or verifying signatures:
rea inspect-pe-certificates /absolute/path/to/application.exe --json
The workflow reports WIN_CERTIFICATE entries and, for PKCS#7 signedData,
embedded X.509 serial, issuer, subject, and validity fields. A missing security
directory is observed absence of that table. See
PE Authenticode certificate tables.
Inspect PE import and delay-load tables without executing the image:
rea inspect-pe-imports /absolute/path/to/application.exe --json
The workflow reports DLL names, named and ordinal functions, delay-load descriptors, and a pefile-compatible import hash of the standard import table. A missing import directory is observed absence of that table. See PE import and delay-load tables.
Inspect a Windows Shell Link (.lnk) without resolving or executing the
target:
rea inspect-windows-shortcut /absolute/path/to/shortcut.lnk --json
The workflow reports header flags and FILETIMEs, IDList items, LinkInfo volume and network names, StringData arguments and icon location, ExtraData blocks including Tracker droid facts labelled derived, and derived observations such as argument length and a minimized ShowCommand. See Windows Shell Link shortcuts.
Inspect ELF PT_NOTE and SHT_NOTE tables without executing the image:
rea inspect-elf-notes /absolute/path/to/program --json
The workflow reports note owners, types, descriptor hashes, and GNU build-id or ABI-tag fields when those descriptors match. A missing note table is observed absence of those declarations. See ELF note tables.
List ELF and Mach-O initializer and finalizer functions without executing the image:
rea inspect-native-initializers /absolute/path/to/program --json
The workflow reports DT_INIT, init/fini arrays, legacy .ctors/.dtors, and
Mach-O __mod_init_func, __init_offsets and __mod_term_func entries with
their resolved targets. Universal Mach-O files need --architecture. See
ELF and Mach-O initializer functions.
Inspect ELF unwind function ranges without executing the image:
rea inspect-elf-unwind-ranges /absolute/path/to/program --json
The workflow reports FDE start, end, and size, the containing section, an exact
symbol name when one exists, CIE identity, LSDA, and personality. Coverage
counts ranges that match a symbol, ranges without a symbol, and whether the
.eh_frame_hdr search table agrees with the decoded FDEs. .debug_frame is
reported present, absent, or incomplete and is not decoded. A missing unwind table is
observed absence of those declarations. See
ELF unwind function ranges.
Inspect an Apple .ips crash report without executing a target:
rea inspect-apple-ips /absolute/path/to/report.ips --json
The workflow reports first-line metadata, process/exception/termination facts,
threads, usedImages, lossless integer registers, and documented crash classes
when bug_type is "309". Other DiagnosticReports types return no crash
result. See Apple IPS crash reports.
Inspect Linux kernel module metadata without executing the module or requiring
modinfo:
rea inspect-kernel-module /absolute/path/to/module.ko --json
The workflow reports ordered .modinfo keys (duplicate alias and parm
entries preserved), __versions symbol CRCs, presence of
.gnu.linkonce.this_module and __ksymtab sections, observed .init.text
and .exit.text sizes, and an appended signature trailer. struct module is
not decoded and the signature is not validated. Other ELF types are not
applicable. gzip, xz, and zstd containers are unsupported and name
external_decompression as the next probe: decompress with an external
utility, then inspect the decompressed file. See
Linux kernel modules.
Read a Linux kernel image's version banner and embedded CONFIG_IKCONFIG
build config without executing it:
rea inspect-kernel-build-config /absolute/path/to/vmlinux --json
rea inspect-kernel-build-config /absolute/path/to/bzImage --name CONFIG_MODULE_SIG --json
The workflow reports the banner version, builder, and compiler, the config
option count, and paged CONFIG_* entries (y, m, strings, numbers, and
not_set). An IKCFG_ST string counts as a config only when a valid gzip
member follows it and IKCFG_ED follows that member. A gzip bzImage payload
is inflated and searched; other payload codecs are unsupported and name
external_decompression as the next probe. See
Linux kernel build config.
Inspect a CPython 3.7–3.13 .pyc header and nested marshal code objects (3.14 and
3.15 headers are qualified but their marshal layout is not pinned)
without executing bytecode or installing Python:
rea inspect-python-pyc /absolute/path/to/module.pyc --json
The workflow reports the pinned CPython version for the magic, PEP 552 hash or mtime/size fields, nested function names, and bytecode SHA-256. Bytecode is not decompiled. See CPython pyc inspection.
Inspect an Erlang BEAM file without executing bytecode:
rea inspect-erlang-beam /absolute/path/to/app.beam --json
The workflow reports IFF chunk names, atom tables, exports, imports, lambdas, and the Code chunk header. Missing optional tables stay null. See Erlang BEAM files.
Complete rea decompile --json Evidence can be saved and inspected through
inspect-analysis-view with an inline source and
view: {kind: "native", facet: "pseudocode", offset: 0, limit: 20000}.
The projection reads that saved observation without starting a provider;
offsets/limits count UTF-16 code units. A summary view preserves the original
selector and distinguishes null from empty text. The MCP tool additionally
supports detail: "summary" on its first call to retain the complete source
without returning the body inline; see native investigation.
Configure Hopper or Ghidra, the IDA adapter, or Kuna before analyzing a native target. Substitute your target, search text and function name or address in these examples:
rea analyze /absolute/path/to/program --provider ghidra --json
rea search /absolute/path/to/program "search" --provider ghidra --json
rea function /absolute/path/to/program main --provider ghidra --json
rea decompile /absolute/path/to/program 0x1000 --provider ghidra --json
rea xrefs /absolute/path/to/program 0x1000 --provider ghidra --json
rea trace /absolute/path/to/program "search" --provider ghidra --json
analyze and inspect share the native overview workflow. function returns
a function dossier; decompile returns pseudocode. For assembly instructions
alone, use rea instructions. A macOS .app bundle can be supplied directly.
A complete dossier can reach several megabytes of value-flow and references.
rea function ... --detail summary --evidence_output /absolute/path/dossier.json returns the
compact summary and saves the complete dossier as an Evidence bundle, never
overwriting an existing file. Page that bundle without reanalysis by naming it
as an inspect-analysis-view source, selecting the summary's
normalized_result.parent_evidence_id when the bundle holds several records:
{"source": {"kind": "evidence-file", "path": "/absolute/path/dossier.json", "evidence_id": "ev_…"}, "view": {"kind": "native", "facet": "pseudocode", "offset": 0, "limit": 4096}}.
MCP analyze_function defaults to the same summary and retains the complete
dossier in the session for inspect_analysis_view.
Run rea --help or a command's --help for arguments and output options.
The generated catalog lists all CLI commands and MCP
tools. Provider-specific target support is described in the
native guide, DOS guide,
raw binary guide, GameCube DOL guide, and
Windows Ghidra guide.
Choose a provider
List the available providers and their supported operations:
rea providers --json
rea capabilities --json
These commands describe binary-session providers and auxiliary capabilities. For the complete MCP surface, use the connected server's tool list and the session's advertised availability; individual guides describe prerequisites.
With automatic selection, REA uses the single available provider that supports the target. When several providers support it, select one explicitly:
rea analyze /absolute/path/to/program --provider hopper
Set REA_ANALYSIS_PROVIDER for a standing preference. An explicit --provider
overrides it. In MCP, pass provider_id to open_binary:
{
"path": "/absolute/path/to/program",
"provider_id": "hopper"
}
The selected provider remains bound to the session until an explicit switch
or close. Provider failures are returned with their original reason. For an
ambiguous selection error, choose from details.candidate_ids; for
provider_unavailable, follow message, remediation.action, and the original
reasons in details.rejections. Each rejection names its origin: availability
entries describe local readiness failures, including missing configuration or an
invalid existing registration; they do not establish target support. target
entries record a refused kind, format, architecture, or role. An availability-only
message describes the recorded rejections without claiming that every provider
is unconfigured or that target support is known.
For Hopper, Ghidra, IDA, JADX, and Kuna, run
rea doctor --provider ID --json to diagnose the selected engine. Other adapters,
such as pwntools-elf, report their configuration requirements directly and are
not accepted by doctor --provider. See
task readiness and
provider selection.
The session reports work still in progress through analysis_activity.
A client timeout can end its wait while the provider continues analyzing.
cleanup_incomplete identifies resources whose shutdown or removal could not
be verified. See MCP contracts for session lifecycle and
Ghidra first-query deadlines
for import, client timeouts and recovery.
One-shot binary analysis validates the selected operation's input before creating a provider session, opening the target, or replaying a snapshot. Invalid selectors and option combinations report their input errors before target or provider availability errors. An already-cancelled request keeps its cancellation reason. Caller-selected paths and request parameters remain separate from parsed execution arguments in Evidence.
A one-shot command attempts provider-session and runtime cleanup before reporting
a failure. If cleanup remains incomplete, inspect the reported resources and
follow the error's remediation. Same-session cleanup retry,
reuse and update_analysis_settings apply to a live MCP connection.
For one-shot commands, details.limit reports scope: "rea-invocation" and
applies_at: "next_invocation"; set the named environment (or the per-call
parameter) for a new invocation and rerun. For example, a Ghidra decompile
deadline names REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS.
rea debug-with-gdb and rea debug-with-rizin hold their debugger only for the
command's own process. If cleanup cannot confirm the debugger stopped, they print
cleanup_incomplete and exit nonzero without stopping it. remediation.action
names the process, as does retained_debugger_pids in details.diagnostics, or
in details.diagnostics.cleanup_error.details.diagnostics when the command also
failed or was interrupted. Exiting closes the debugger's command and output pipes.
REA does not establish what the debugger or its target does next, so check that
process. An MCP server that shuts down likewise exits with status 1 instead of
waiting on a debugger it could not stop. It names the process on stderr and as
retained_debugger_pids in its error log.
If one-shot analysis fails and closing its session also fails, the original
error details remain inline and details.cleanup_error describes the later
close failure. Each diagnostic retains its own reported resources; the later
failure does not establish whether an earlier resource was released. Captured
output and partial observations remain in the original details.
Save and reuse analysis snapshots
A snapshot retains successful analysis results for later queries. REA reuses an exact result when the target bytes, operation, parameters, provider and settings match. Mutations and cursor-dependent calls are excluded from the cache. Snapshot files are local and use owner-only permissions.
A binary CLI replay reports the current invocation's selected path while leaving the saved Evidence and snapshot query unchanged. See caller-selected paths for primary and auxiliary path fields and their relation to Evidence identity.
Importing an Evidence bundle refreshes retained request and subject display metadata. It preserves cached analysis results, including observations for other targets. Snapshot bindings that use a replaced record retain its updated display path.
Dispatching a provider metadata write invalidates the active session's snapshot cache, even if its reply fails: the provider may already have applied the write. Close and reopen the target before importing or saving another snapshot.
Saved snapshots include only cache entries whose subject identifies the selected target. Unbound observations and observations of child artifacts remain available in the open session and its Evidence bundle; they are excluded from the target's saved replay entries without changing their subjects. The snapshot also retains available Evidence linked by selected records, including transitive sources with an unbound or different subject. Those sources are not replay entries for the selected target. Unrelated records remain excluded.
Snapshots retain eligible target-scoped question histories in full, including mutation Evidence recorded while another target was active. Observations and related questions must still belong to the saved target. If those dependencies cannot be retained, the entire history is excluded instead of reverting the question to an earlier revision or disposition.
rea analyze /absolute/path/to/program --provider ghidra --snapshot /absolute/path/to/analysis/program.json
# Repeat the same query to reuse its saved result.
rea analyze /absolute/path/to/program --provider ghidra --snapshot /absolute/path/to/analysis/program.json
An exact CLI cache hit is normally read before starting a provider process.
decompile --provider ghidra --snapshot ... also retains its single selected
Program's pseudocode result, even without an explicit document selector. Old
snapshots that contain only the Evidence need one new query to add that replay
entry. This does not retain every body from decompile-all or make a persistent
session's implicit document replayable.
On Windows, Ghidra first admits the selected source through its native snapshot
owner, so replay can start that provider.
In MCP, open_binary accepts snapshot_path to import a snapshot atomically
for its matching target; an MCP provider may still start before returning a
cached result. close_binary accepts snapshot_path and optional
overwrite: true to save before releasing provider resources. A failed save
leaves the session open so the caller can resolve the output failure.
When a validated snapshot does not match the selected binding, the error's
details.binding_mismatch reports confirmed target identities, provider
identities, or profile digests that differ. snapshot is the saved value and
selected is the requested value. Multiple differences can be reported. A
profile_comparison: "not_compared" means the profile was not available at
that boundary; it does not establish a match. A profileless snapshot can report
workflow_scope: "incompatible" even when its target and profile match.
Choose a matching target, provider, and profile, or analyze without the snapshot
and save a new one for the intended binding.
For a profile mismatch, the saved snapshot contains
binding.analysis_profile.parameters. If binary_session reports an active
profile whose digest matches binding_mismatch.profile_digest.selected, its
analysis_provider_binding.analysis_profile.parameters can be compared with
those saved parameters. A rejected switch leaves the previous session active,
so its profile may be different from the attempted selection; check the digest
before using it for this comparison. A configuration difference does not by
itself establish why analysis results differ.
If no target is open and you intend to inspect the selected binding, open that
target without the snapshot, then check its profile digest in binary_session
before comparing parameters. This may start the selected provider; the saved
snapshot remains a separate file.
If a selected snapshot's file descriptor cannot close, open_binary reports
cleanup_incomplete. binary_session reports cleanup_pending even when no
target opened. Retry close_binary to release the retained descriptor before
opening another target.
If a snapshot switch fails after the previous target was retired, restoration keeps that target's analysis commitments with the replacement client. This includes the native dispatch workflow profile on a profileless route, so its existing snapshot and fresh observations retain the same provenance.
For native dispatch, the snapshot binding retains the reader profile; workflow
Evidence and workflow_entries retain the composed profile, which commits that
reader and the workflow's analysis caveats. Restoration preserves both profiles.
The MCP save receipt reports primitive_entries, workflow_entries, and
evidence_records separately. Zero primitive bindings can still accompany retained workflow
results and Evidence. These are cached observations, not a saved provider
database: only eligible exact queries can reuse a result, and new or live
queries can still require provider startup. The CLI uses the same snapshot
format and preserves these records when loading and updating it.
When an MCP session replays a provider observation, its delivered Evidence identifies
the original observation through evidence_links and reports that the
provider was not rerun. The original and its available source records remain
in the investigation bundle.
Import, export and compare Evidence
Evidence records retain artifact identity, source locations, observations, inferences and unresolved findings. Validate an existing bundle, export its canonical form, or compare two supplied bundles:
rea evidence-import /absolute/path/to/evidence/bundle.json
rea evidence-export /absolute/path/to/evidence/bundle.json /absolute/path/to/evidence/canonical.json
rea compare /absolute/path/to/evidence/left.json /absolute/path/to/evidence/right.json
rea compare left.json right.json --record_pairs '[{"left_evidence_id":"ev_<left-sha256>","right_evidence_id":"ev_<right-sha256>"}]' --json
Migration: replace rea compare-bundles with rea compare; the former command
has been removed. The MCP tool remains compare_bundles.
rea evidence-import performs process-local bundle authentication and
validation, reporting record and Unknown-revision counts. unknowns_added
counts revisions, not distinct Unknown IDs. It does not retain records in a
separate or background MCP server process. To retain records across calls in an
active MCP session, use the MCP tool import_evidence_bundle in that session.
rea compare compares records by Evidence identity and explicit pairs. It does
not read artifact contents: the inventories of two builds are unresolved
records, or one opaque change when you pair them. Use compare-artifacts,
described at the end of this section, to compare their contents.
--record_pairs accepts an inline JSON array or the path to a local JSON
file containing that array, matching MCP compare_bundles.record_pairs. For a
file, use rea compare left.json right.json --record_pairs pairs.json --json.
Pair IDs must exist on their named sides and be one-to-one. Without pairs,
one-sided membership remains unknown; REA does not infer correspondence from
position or display names.
Exports preserve an existing destination unless --overwrite is explicit.
Atomic Evidence output stages on the selected destination's filesystem. If the
OS pathname ceiling prevents its internal staging name, REA retries in a shorter
ancestor on that same filesystem and removes the unused stage. Existing output
protection still applies. A genuine pathname refusal uses resource_constraint
with details.reason: path-limit and boundary: filesystem-path.
For application traces and version comparisons, see JavaScript application workflows. MCP retained Evidence references belong to one connection. Separate CLI calls can consume full saved records or resolve exact retained IDs from an explicitly selected local bundle:
rea trace-application-feature request.json --evidence-bundle /absolute/path/evidence.json --json
Here request.json uses the same logical Evidence input as MCP, for example
"application": {"kind":"retained-evidence","evidence_id":"ev_<64 lowercase hex characters>"}.
Inline records and references may be mixed in the request's declared source
fields, including arrays and nested inputs. The bundle is read and authenticated
once for that command; no target is reopened, no producer is rerun, and no
record is selected by file order. Source identity, provenance, coverage and
partial or unknown facts agree with the complete inline form. When starting
from summary or inspection Evidence, select its
normalized_result.parent_evidence_id for a workflow requiring complete source
facts. Omit --evidence-bundle when all sources are inline.
The JSON workflow commands supporting this option are:
trace-application-feature,trace-javascript-semantics,compare-application-versions,compare-javascript-export-shapes,compare-source-to-bundle, andreconcile-javascript-runtime.import-managed-reconstruction,verify-managed-native-boundaries, andproject-managed-application-graph.project-android-application-graph,project-apple-application-graph, andproject-harmony-application-graph.compare-wasm-interfacesandinspect-analysis-view.
For inspect-analysis-view, the request's source reference selects a parent
from the bundle for every supported view kind. Bundles exported from MCP work
for these commands. Its inline source form cannot also select a bundle.
A missing ID is refused with details.reason: "missing";
supplying a bundle does not make incompatible Evidence valid for a workflow.
compare-managed-members keeps its two assembly paths and an optional
repeatable --type selection applied to both sides, and
compare-process-captures keeps its two capture Evidence JSON paths. The bundle
option above selects an input; producer options that write a new bundle, such
as inspect-managed-members --evidence-bundle, retain their existing meaning.
File selectors remain available only on contracts that explicitly support them.
Compare two artifact inventories with compare-artifacts. It takes the same
left and right input as MCP compare_artifacts: complete Evidence, or an
evidence-file source with an optional evidence_id that selects one record
from a bundle. Relative paths resolve from the working directory:
rea inspect-artifact old.zip --json > old.json
rea inspect-artifact new.zip --json > new.json
rea compare-artifacts '{"left":{"kind":"evidence-file","path":"old.json"},"right":{"kind":"evidence-file","path":"new.json"}}' --json
A one-shot compare-artifacts command keeps no session Evidence, so it refuses a
retained-evidence source with details.reason: "missing". Select the record
from a saved bundle instead.
For an artifact summary selected from a bundle, include its complete parent
inventory in the same bundle. The command resolves that parent from the
authenticated file records; a summary-only file cannot supply missing facts.
Import historical source
Historical source inventories preserve exact filenames, including literal backslashes on POSIX hosts. Relative import lookup uses the recorded source file's owning directory and decodes URL percent escapes once; encoded slash, NUL and malformed escapes remain unresolved. A decoded backslash remains part of a filename rather than a directory separator. This inventory lookup does not claim that Node can load that filename or apply Node's runtime file-URL admission policy.
Import an older source tree to compare with the current artifact:
rea import-reference-source /absolute/path/to/source
rea import-reference-source /absolute/path/to/source --detail summary --outline-depth 2 --json
rea import-reference-source /absolute/path/to/source --exclude-paths '["vendor/","*.generated","!keep.generated"]'
The default (--detail full) returns the complete HistoricalSourceGraph.
Choose --detail summary for a HistoricalSourceSummary of that same committed
inventory: its HistoricalSourceManifest contains the semantic graph and root
digests, with provenance, VCS facts, languages, manifest paths, entry and
relationship counts, parse-failure counts, file/directory states, exclusions
and limitations. The summary is a presentation view, not a graph accepted by
source-to-bundle comparison; keep the full output for that workflow.
Known filesystem failures commit the operation, parsed errno, entry-relative
location and recovery guidance. Entry local_diagnostics, when present,
retains the exact host message, including local paths, outside the semantic
graph commitment. Equivalent permission failures therefore retain their manifest
identity after relocation while preserving original source diagnostics in full
output and visible summary outline entries. Other substantive limitations still
participate in the graph commitment.
An unreadable child directory remains a partial inventory entry. Directory-scoped ignore policy is required before enumerating a directory that can be opened; an unreadable or invalid policy still fails the import. Project negations cannot override defaults or caller exclusions; caller patterns retain their own ordered negations. Configured secret exclusions remain final.
--outline-depth requires --detail summary and accepts a nonnegative safe
integer. Depth 1 shows root children, depth 2 (the default) also shows their
children, and depth 0 omits all outline entries. Paths retain canonical Unicode
code-point order. outline.omitted_entry_count counts only inventoried entries
hidden by the chosen depth; it does not count unexamined children of excluded or
unreadable directories, or change inventory_state. Exclusions and source
limitations remain separate from this presentation omission.
file_sizes.known_bytes is the exact sum of known inventoried file sizes as
decimal text, so large sums cannot lose integer precision. Unknown sizes count
in file_sizes.unknown_file_count; excluded or unexamined paths are not included
in that total. A zero known-byte sum therefore does not establish an empty source
tree. Full output retains individual file hashes, relationships and parse-failure
diagnostics. Summary mode performs the same import and cleanup, then projects
the committed result without rereading source files. It reduces the selected
output, not the amount of source examined. A shallow tree or many exclusions can
still produce a large summary; depth 0 removes the outline but preserves the
other facts. No fixed output-size or runtime improvement is promised.
Import failures use the shared error contract (code, category, message,
retryable, and remediation). When reads completed before failure,
details.diagnostics.partial retains the reader facts, with file bytes as
arrays of byte values. Unresolved cleanup uses cleanup_incomplete and
retains the source failure and resource paths in details.
The import records hashes and metadata for the supplied files, separately
from observations of the current app. For a directory inside a Git checkout,
it records the HEAD of the nearest enclosing repository; whether the working
tree matches that commit remains unknown. Root selection follows filesystem
symlink/parent semantics before canonical identity is derived. It honors
.gitignore files at the selected root and in traversed child directories,
with patterns relative to their containing directory and deeper negations
applied only when the parent was not excluded. Matching uses the nearest
repository's local core.ignorecase setting (false when unset), including the
shared configuration of linked worktrees. Policy and Git metadata reads retain
their descriptor cleanup under the import's resource owner; unreadable policy
is a typed failure, not an empty policy. Above-root .gitignore files, global
excludes, .git/info/exclude, config includes and worktree-specific config are
not loaded. The import also applies default exclusions for common generated and dependency paths such
as node_modules/, dist/, and *.log. Pattern-based exclusions record the
matched pattern and whether it came from project, default, caller, or sensitive
path policy. Links into excluded paths retain their internal target metadata;
the exclusion explains why the target has no inventory entry, and the inventory
remains partial. Exclusions record entry_kind when the importer observed it;
older saved exclusions without that field have unknown kind. An excluded
ancestor proves an internal descendant only when its kind is directory.
Set REA_REFERENCE_SECRET_PATTERNS_JSON to a JSON array of ignore
patterns when you want to mark selected paths as sensitive; those patterns are
reported separately as configured-secret exclusions.
Re-import saved graphs whose pattern-based exclusions lack pattern before
using them in source-to-bundle comparisons.
Entries that change during an import retain an unavailable-entry diagnostic alongside readable siblings. If enumeration of an already admitted root fails, the graph keeps its observed children and records the root failure as a top-level limitation. That partial inventory does not establish absence among unexamined children. POSIX directory-entry bytes that are not valid UTF-8 cannot be represented as graph paths; the containing directory (or root) is marked unsupported, and REA does not access the entry through a replacement-character name. Symlink targets are read as bytes too: a target that is not valid UTF-8 is retained as an unreadable symlink with its bytes reported in the limitation, and is never resolved through a replacement-character path. Readable siblings remain inventoried.
JavaScript and TypeScript import parsing requires valid UTF-8. Malformed source
bytes retain their original hashes and sizes with a decoding diagnostic; REA
does not infer module targets from replacement characters. Exact dynamic-import
strings, expression-free templates and runtime-transparent TypeScript wrappers
retain their targets; computed imports remain unknown. TypeScript import-equals
dependencies remain requires relationships whether or not they are exported.
Historical dependencies also include TypeScript import types (import("./dep").T
and typeof import("./dep")) and declarations inside ambient module bodies;
these source relationships do not assert runtime loading. Language labels retain
the conventional case-sensitive .C (C++) and .M (Objective-C++) suffixes.
REA derives historical CommonJS paths only when the selected loader and its
base are proven: this includes the exact
createRequire(new URL("./file.cjs", import.meta.url)) form and
require.resolve with { paths: [__dirname] }. Exact optional-call forms such
as require.resolve?.("./dep.cjs") use the same loader and mutation facts. The
nearest inventoried package manifest supplies .js/.ts package facts to the
shared parser; explicit filename modes take precedence. A known ES module has no
implicit CommonJS wrapper, including when a factory is derived from an unbound
require. An unreadable, excluded or malformed manifest, or package scope outside
the selected inventory, leaves otherwise unproven loader resolution unknown with
the requested specifier and a reason. A nearest manifest without a type field
uses the shared parser's JavaScript module-syntax facts. Unresolved TypeScript,
JSX compilation and declaration contexts do not establish runtime wrapper bases.
Other custom bases and search options remain partial with unknown resolution and
the literal requested specifier. Overwritten resolver methods retain that uncertainty; they are not
attributed to Node. require.main is a module object, so calling it does not
establish a loader reference. These relationships do not establish execution;
Node's require.resolve
performs lookup without loading the target. An excluded package.json
also leaves package-directory selection unknown rather than treating the
manifest as absent and applying an index fallback.
Import requests use URL pathname semantics: query and fragment remain request
identity, percent escapes decode exactly once for inventory lookup, and encoded
separators or malformed escapes retain an unresolved relationship and diagnostic.
URL-bearing relationships retain the authored source_specifier separately from
the inventoried to path; distinct query/fragment requests remain distinct even
when they name the same file. CommonJS requests retain literal filename semantics
for percent signs, query marks and fragments.
Historical-source import requires safe no-follow file opens on Linux or
macOS. Native Windows returns unsupported_host; use Linux REA inside WSL
or another supported host. See
source-to-bundle comparison
for mapping identities, inferences and unknowns.
Capture runtime behavior
Choose the guide for the target and intended interaction:
- Browser observation: inspect a selected page through CDP.
- Browser scenarios: run declared interactions and capture their results.
- Electron observation: inspect an Electron renderer or capture an application scenario.
- Electron fuses: read packaged fuse bytes before expecting
--inspectto reach an Electron main process. - Node/Electron Inspector: record script locations and execution contexts.
- Process capture: run an executable and compare terminal, exit and filesystem observations.
- Desktop application capture: launch a Linux desktop application and record its windows and home-directory state at checkpoints while it is used.
- Desktop application state: read a capture folder into the application's state files, typed views of each version and kinds of change.
- Desktop application export: write a capture folder as an agentenv computer app bundle with its class, skill and seed.
- Desktop application tasks: describe how a capture's files changed, then validate an agent-written task against the capture and export it with its verifier.
Runtime requests name the target and actions. Launched targets run with your user permissions; consult the chosen guide for host requirements and effects.
Serve MCP over streamable HTTP
rea mcp speaks stdio to the client that launched it. To serve the same MCP
catalog over streamable HTTP for one local client, such as an agent environment
runtime, start the dispatcher entry with --http:
rea mcp --http --port 8765
--host and --port default to MCP_HOST and MCP_PORT from the environment,
then to 127.0.0.1 and an ephemeral port. A bind outside loopback requires
MCP_ALLOWED_HOSTS or --allowed-hosts, a comma-separated list of client host
names or IP addresses without URLs or ports.
Repeat --allowed-hosts to append names, or put multiple names in one
comma-separated value. All occurrences are validated together and deduplicated
after hostname normalization; the explicit list replaces MCP_ALLOWED_HOSTS.
For example:
rea mcp --http --host 0.0.0.0 --allowed-hosts 127.0.0.1,rea.lan
Requests with a different Host or browser Origin receive 403; a missing
Origin is accepted. The announced client URL uses the bound hostname when
admitted, otherwise the first declared hostname, with the actual bound port.
It is written to stderr as one JSON log line ("msg":"MCP transport listening") whatever REA_LOG_LEVEL
is set to; nothing is written to stdout. rea mcp --http --help lists the accepted options. The listener serves
one client at a time: a session ends with the client's DELETE, and a client
that connects while another session is open takes it over, so a client that
exited without ending its session needs no restart. The listener runs until
SIGINT or SIGTERM and does not stop when stdin closes. See
transports for the session, Host header and
shutdown contract. Client registrations written by setup and the MCP registry
entry keep stdio.
Serve an environment manifest
rea environment-serve <manifest.json> --evidence <capture.json> [--replay-only] [--host H] [--port N] serves an environment manifest
projected by inspect-analysis-view as an agent environment: an MCP server at
/mcp, the environment card at /.well-known/agent-env.json, and a
/agentenv data plane. The manifest's parent_evidence_id must equal the
evidence file's evidence_id; otherwise the command refuses with
invalid_request before binding. Before binding it derives the
environment-state view of the capture with the manifest's template_rules,
as inspect-analysis-view would, and serves from it: read tools whose views
are state-backed answer from a live state, write tools are served only where
the capture's later reads confirmed their effect, and
/agentenv serves data/reset, data/add and data/get. When the state
cannot be derived, or its catalog exceeds the discovery reservation, it logs a
warning with the error code and message and serves replay; --replay-only serves replay without deriving. In replay every
manifest tool replays the retained capture records it cites and /agentenv
answers every method with JSON-RPC -32601. The stopped document's state
member is {"served": true, "evidence_id": …},
{"served": false, "reason": "replay-only"} or
{"served": false, "reason": "unavailable", "code": …, "message": …}; see
serve from the state.
rea environment-serve manifest.json --evidence capture.json --port 8765
--host and --port resolve like rea mcp --http: flags, then MCP_HOST and
MCP_PORT, then 127.0.0.1 and an ephemeral port. A non-loopback bind requires
MCP_ALLOWED_HOSTS with the same hostname rules and Host/Origin gates as above.
environment-serve uses this environment setting; --allowed-hosts belongs to
rea mcp --http. For example:
MCP_ALLOWED_HOSTS=127.0.0.1,rea.lan rea environment-serve manifest.json --evidence capture.json --host 0.0.0.0
ENVIRONMENT_NAME renames
the served environment; set but blank, it is refused as configuration_invalid
like a blank MCP_HOST. The admitted client URL is written to stderr as one JSON log line
("msg":"MCP transport listening"). See
agent environments for what the state and replay
return and do not establish.
Build an environment bundle
rea environment-build <capture> [dir] [--name <name>] [--template-rule <rule> ...] [--format-rules-only] [--package-tarball <path>] [--capture-timeout-ms <ms>] [--dry-run] builds the bundle
environment-export writes from one recorded capture: a .har file, which it
inspects as inspect-web-network-capture does, or capture Evidence JSON from
inspect-web-network-capture (HAR) or capture-browser-scenario. It projects
the environment manifest from the in-memory Evidence with the opt-in rules
after-uid-literal, opaque-token, sibling-variance and prefix-variance
unless --template-rule or --format-rules-only selects others, prepares the
state, and writes the bundle; every file is byte-identical to the three-step
path's when that path lists the same rules in the views' order and exports
under the build's name. A loopback or IP-literal capture is named after its file unless --name
is given. It prints one JSON report: the capture and manifest identities, the
state, a coverage summary of which tools answer from the state, which replay
and why each write is withheld with a hint, the files, warnings and the next
command. --dry-run writes nothing and may omit the directory.
rea environment-build grafana.har ./grafana --json
See agent environments for the report, the hints and what the bundle ships.
Export an environment bundle
rea environment-export <manifest.json> --evidence <capture.json> <dir> [--package-tarball <path>] [--name <override>] writes the same manifest and
capture pair as a bundle folder the agentenv framework runs with
agent-env run <dir>. It reads each file once, authenticates and cross-checks
the pair with the same checks as environment-serve, checks the bundle name,
then prepares the state under that name as the bundle's server will at startup
to predict what the bundle serves (replay when the state is unavailable),
copies the bytes it authenticated, refuses to write into a directory that
exists and is not empty, and never overwrites a file.
rea environment-export manifest.json --evidence capture.json ./grafana-example --json
The folder holds a README.md with the bundle's provenance, an env folder
envs/<name>/ with a Dockerfile that installs this exact rea-agents
version and serves the copied manifest.json and evidence.json (written
with mode 0600 on POSIX; Windows retains inherited ACLs) with
environment-serve (from the state by default), a Python verifier under
artifacts/check_<name>/ that checks the served tool count and one served
answer, and the smoke task tasks/smoke.json. --package-tarball copies a packed rea-agents
tarball into the env folder and installs it instead of the registry package,
which is how the hosted lane proves a checkout rather than a release. --name
replaces the manifest's kebab-case name as the env folder and env id; a device
name Windows reserves (con, nul, com1 and the like) is refused. The
command prints one JSON document listing the written files with their sizes,
the pinned package source, the served tool_count, and state in the shape
of the serve command's stopped document. See
agent environments for the folder
layout and what the smoke task checks.
rea environment-export prints the written files and exits 0 on success.
Input-pair refusals, an occupied destination, a missing package tarball, and
invalid labels fail before writing output. Host permission failures identify
whether reading or writing was denied and retain the affected path.
A failure during writing can leave a partial folder. Inspect it before removing it and retrying; the next attempt refuses a non-empty destination. When one file caused the failure, the diagnostic names that file. A folder-sync failure can report only its cause.
If details.partial_observation.kind is environment-bundle-publication, the
bundle was already published. Its directory and files record the committed
output, even when a later identity check or cleanup failed. Inspect that bundle
and resolve the reported failure before using or replacing it; the receipt is
not an instruction to delete published output. Cleanup resources remain a
separate obligation.
Compare an environment with its application
rea environment-compare <manifest.json> --evidence <capture.json> --probe <probe.json> [--probe <probe.json> ...]
compares the environment the pair serves with probe captures of the application it came from. Each probe is capture
Evidence of one call sequence recorded against the application after a reset; the environment's state is reset before
each probe, every request to the application's origin is answered as the served environment answers it, and the
answer is compared with the application's after ids are mapped and clocks and generated values are normalised. The command reads files only and
makes no network call. It prints the report and exits 0, or prints a structured invalid_request error and exits 1
for the same pair refusals as environment-serve, a probe that is not capture Evidence, or a browser probe captured
without response bodies, or without the request bodies its writes need. See
agent environments for the report.
rea environment-compare manifest.json --evidence capture.json --probe probe.json --json
Report version and runtime
rea --version prints the package version. Add an output format to get the
version with the runtime and platform context that a support request needs:
Version reports do not initialize analysis providers. Invalid global settings or output options still report their usual errors.
rea --version --json
{
"name": "rea-agents",
"version": "6.7.0",
"runtime": { "id": "node", "version": "24.22.1" },
"platform": "linux",
"architecture": "x64",
"supported_runtime": {
"range": "^22.23.4 || ^24.22.1 || >=26.12.1",
"satisfied": true
}
}
| Field | Meaning |
|---|---|
name, version |
Published package name and version; version matches plain --version. |
runtime.id |
node or bun. |
runtime.version |
Version of that runtime. |
runtime.node_compatibility_version |
Bun only: the Node.js version it reports compatibility with. |
platform, architecture |
Node.js process.platform and process.arch, such as linux and x64. |
supported_runtime.range |
Supported Node.js range. |
supported_runtime.satisfied |
Whether this runtime is supported, judged as rea doctor judges it; Bun by its Node.js compatibility version. |
The field names are stable. The report contains no paths, environment values,
user or host names, and it starts no provider and reads no target. --format
selects any CLI output format, such as --format yaml. JSON follows the usual
rule: indented on a terminal, compact when piped or redirected. --help still
takes precedence. With other output controls, --version keeps its existing
behavior: valid controls such as --filter-output or --full-output print the
plain version, and invalid combinations report the usual output-flag error,
such as UNSUPPORTED_OUTPUT_COMBINATION for --json with --token-limit. Use
rea doctor for provider and host readiness.
Output and exit status
Commands reject surplus positional arguments before execution, including commands
that accept no positionals. Use rea <command> --help for the declared argument
shape; a final variadic argument continues to accept multiple values.
rea mcp add accepts options only and rejects unknown options before reading or
backing up client settings or starting registration. Its --agent and
--command/-c options accept spaced and equals forms. Values must be nonempty; a following flag does not satisfy a spaced option's
missing value.
Registration command text is a launcher format, not a shell program. ASCII spaces separate words; single and double quotes delimit adjacent fragments of a word. Backslashes, tabs, variables and shell punctuation are literal data. Quoted empty arguments are preserved, including an empty argument after the executable. An empty executable, unfinished quote or NUL character fails before registration. For example:
rea mcp add --agent cursor --command '"/path with spaces/server" "" --mcp' --json
The registered executable is /path with spaces/server, with arguments "" and
--mcp. Windows backslashes use the same literal rules after the invoking shell
has passed the option value. With no --command, REA uses its exact current
runtime and entrypoint arguments without converting them to text and parsing them
again. No package acquisition or server execution occurs during registration.
--no-global refuses any selection requiring global storage before writing files.
rea mcp add --schema --json returns the named input and output JSON Schemas.
Successful and partial registration reports contain the exact command, args
and a clients outcome list. ok is true only when every selected client is
configured or unchanged. Failed clients retain actionable errors; cancellation
marks remaining clients not_started. A failed client does not erase a successful
sibling's receipt. Existing settings and environment overrides survive refresh;
backups identify the original bytes, and identical repeats create no new backup.
Client IDs are case-insensitive for the helper-compatible catalog; documented
aliases include droid/factory, gemini, grok, kilo/kilocode, kimi,
kiro, mastra, pi-agent, hermes-agent, cline-vscode, and
codeium/cascade. github-copilot selects VS Code; use
github-copilot-cli for Copilot CLI. Amp uses --agent amp.
Kilo project registration follows its native configuration layers and requires a
verified Git worktree. Selected KILO_CONFIG, KILO_CONFIG_DIR and
KILO_CONFIG_CONTENT overrides remain authoritative. A conflicting higher-priority
source must be updated before registration can succeed; readonly layers are never
copied into the selected file. An unknown project root fails before writing and
does not fall back to global settings.
rea mcp add --command accepts stdio launcher text. An HTTP(S) URL in the
executable position is refused before any settings change; configure remote MCP
transport in the client's own settings. Earlier delegated registration could
interpret a URL as remote transport for some clients while storing it as an
executable for Amp. That inconsistent URL interpretation and package acquisition
are removed. Launcher arguments that happen to contain URLs remain arguments.
rea mcp doctor --json checks production MCP startup and reports child logs in
diagnostics. It drains stderr while retaining at most its first 64 KiB of raw
bytes as text, omitting a final UTF-8 code point if it crosses that byte limit.
stderr_bytes counts all observed bytes, including omitted output;
stderr_truncated indicates that output exceeded the retention limit. The same
diagnostics appear inside the report when --full-output is selected. Log
truncation alone does not change the diagnostic health or exit status.
The default terminal format is TOON. Use --json when saving results for a
JSON consumer. Valued output flags (--format, --filter-output,
--token-limit, --token-offset) accept --flag value or --flag=value; a
missing value fails with MISSING_OUTPUT_VALUE before the command runs.
Malformed format values and token windows fail with INVALID_OUTPUT_VALUE in the
selected structured format. Token limits and offsets must be nonnegative safe
integers; zero selects an empty window. These checks run before command execution. --json is indented on a terminal and compact when piped or
redirected, which keeps large results smaller for agents and scripts; pipe
through jq . for indented files. A formatting refusal exits nonzero even when
the underlying operation succeeded.
--format md escapes backslashes, pipes, and inline Markdown syntax in table
headers and cells so source text is displayed literally, and renders cell line
breaks as <br>. Underscores between letters or digits, as in source_index,
cannot form emphasis and stay unescaped. These are presentation changes; retained
Evidence and JSON output keep the original values. Nested objects and arrays
use JSON inside their owning Markdown cells, retaining the field names and
values instead of replacing them with an object label. Table widths are measured
iteratively, so an admitted row page is not limited by the JavaScript function
argument count.
Markdown refuses selected NUL characters and unpaired UTF-16 surrogates because renderers or UTF-8 output would substitute them. TOON refuses selected unpaired surrogates for the same UTF-8 reason. Use JSON or YAML to retain those values. An authored replacement character and valid supplementary characters are not treated as malformed.
--format csv and --format tsv export one selected row collection. Select an
exact array path with --filter-output and provide comma-separated direct
scalar columns with --columns; column order is the output column order:
For an artifact inventory request that selects the occurrences collection
and includes logical_path and artifact_id in its view fields:
rea inspect-analysis-view request.json --format csv \
--filter-output normalized_result.items --columns logical_path,artifact_id
The first line is a REA metadata record,
# rea-delimited-v1 <JSON>. It carries the selected path, view request, limitations and unknowns,
coverage, source coverage and completeness, command, and duration. A metadata
value of null means that the selected row collection did not provide that
fact; it does not mean complete coverage. The following line is the header.
CSV records use CRLF line endings; TSV records use LF. Cells containing the
delimiter, quotes, or line breaks are quoted and embedded quotes are doubled.
Values are emitted as UTF-8 without Unicode normalization. Missing and null
cells are empty; booleans and finite numbers use their ordinary text forms.
String cells and headers that begin with spreadsheet formula syntax are refused
with a structured error; unpaired surrogates in selected cells or headers are
also refused. JSON retains those values unchanged.
Delimited output does not flatten nested Evidence or nested field values. It returns a structured error and a nonzero status if the filter does not select exactly one row array, a row is not an object, or a selected cell is nested or formula-leading. JSON remains the complete canonical output; use it to retain fields outside the selected columns or preserve exact formula-leading text. Token counting and token windows are not available for CSV/TSV because slicing would corrupt records or metadata.
--filter-output selects fields from successful command data. Classified failures
retain their complete typed diagnostic projection even when the selector matches
a field such as code or message, or names a missing field. --full-output
controls whether the outer {ok, error, meta} envelope is included.
Selected own JSON members, including __proto__, remain ordinary output data.
Sibling array slices that select different fields are combined by original row
index and emitted in source order, regardless of selector order. Selecting a
whole parent row keeps that row intact and takes precedence over narrower fields
selected from the same row. For example, rows[0,2].name,rows[1,3].version
combines both fields for overlapping rows, while rows[0,2],rows[1,3].version
retains rows 0 and 1 whole.
For cutter-command, --json-output requests command-specific JSON from
Cutter when available. This is separate from --format json, which selects the
CLI result representation; the global --json flag selects that same CLI
representation.
Cutter no longer accepts command-local --json=true or --json=false.
Replace these with --json-output=true or --json-output=false, respectively.
Combine --json-output with bare --json when both Cutter command output and
the CLI result should use JSON; either argument order is accepted.
| Status | Meaning |
|---|---|
0 |
The operation completed. Its result may include partial evidence, warnings or unresolved questions. |
1 |
The operation could not complete. Structured output identifies invalid input, permissions, cancellation, timeouts or another failure when available. |
3 |
audit-release-exposure completed with --fail_on and an exposed check meets that severity. Unknown and unassessed checks do not change this status. |
128 + N |
Signal-derived status. An unhandled signal may have ended the process, with the shell or runtime preserving the conventional status; a command may instead have handled the signal, waited for its operation and cleanup to settle, and assigned the same status (130 for SIGINT, 143 for SIGTERM). In the handled case, it may also have printed a structured cancelled result. |
rea mcp --http is long-running and prints no structured result. It exits
130 on SIGINT or 143 on SIGTERM after closing the listener, the session and
its providers, and 1 when that cleanup fails.
rea environment-serve is also long-running: it serves until SIGINT or
SIGTERM, closes the listener, then prints one structured
{"status":"stopped", …} document naming the URL, environment and tool count
and exits 130 or 143 through the shared cancellation handler below.
Refusals before binding (unreadable files, non-Evidence JSON, capture Evidence
in a format other than HAR, a manifest or state whose parent is not the
supplied evidence, an unbindable port) print a structured
error and exit 1, as does a listener that fails after binding.
If shutdown fails, the command reports cleanup_incomplete with the retained
server resources. It preserves a primary listener diagnostic, or a
shutdown_observation naming the requested shutdown and its URL, environment,
tool count and Evidence identifiers. The successful stopped document is
returned only after cleanup is confirmed. The same in-process server owner
retains failed stages for retry; the CLI exposes no separate retry command.
rea environment-build is one-shot too: it prints its report and exits 0,
or prints the refusing stage's structured error and exits 1, writing nothing
when the refusal comes before the bundle is written. SIGINT cancels it with a
cancelled error; a cancellation before the bundle stage writes nothing.
A cancelled result does not by itself determine the exit status. Commands
that handle SIGINT or SIGTERM assign 130 or 143 after their operation and
cleanup settle; their structured result can be printed before that status is
assigned. Read the process status and typed result separately.
Commands with --evidence_output save the complete Evidence bundle before
delivering the selected view. For committed-file receipts, retained partial
Evidence and missing source records, see
Evidence publication and source recovery.
Other outputs have their own receipt shapes; for example,
environment export reports a published directory.
A failure that carries partial Evidence in details.partial_observation, such
as provider_timeout or resource_constraint, publishes that partial Evidence
to --evidence_output under the same new-file and atomic rules, then keeps its
error code, message and remediation when cleanup is confirmed. With --detail summary,
details.partial_observation holds the summary view of the partial Evidence
instead of the complete record, and details.diagnostics.evidence_bundle
reports the written path and byte count. Linked source records must all be
available before publication; missing sources leave the destination unchanged
and are reported in details.source_closure. A provider cleanup failure can
still publish its captured Evidence without clearing the cleanup error or its
resource ownership. If publication is refused before commit, for example because
the file already exists, the complete partial Evidence stays inline and details.diagnostics.evidence_bundle_error gives the
refusal's code, message and a limitation. A refusal after commit keeps the
receipt in details.diagnostics.evidence_bundle and the delivered view: when
staging cleanup stays incomplete, the code becomes cleanup_incomplete, the
original failure is retained in details.execution_failure, and details also carries published,
published_path, bytes, cleanup: "incomplete" and resources, as for a
successful run, and a later cancellation reports cancelled in
evidence_bundle_error with the signal's exit status. Without
--evidence_output, and for cancellation before publication or raw provider
observations, the failure output is unchanged.
When the reader of piped output closes early, as rea ... | head does, the
CLI stops writing, prints no error and exits 141 (128 + 13, the status of a
SIGPIPE-terminated writer). The operation itself may have completed; only its
output was declined.
inspect-artifact also returns a structured cancelled result. Its logging
wrapper sits outside the cancellation handler and replaces the signal-derived
status with the analysis failure status, so it exits 1. Setup has its own contract: cancellation exits 0. Treat the
structured result and process status as separate signals when scripting
cancellation.
setup --dry-run returns planned and exits 0. For setup and
environment-build, explicit --dry-run=true and --dry-run=false are
accepted; empty or other values fail with PARSE_ERROR before any changes.
Every supplied value is checked even when a later occurrence overrides it.
A cancelled setup also exits
0. Setup returns 1 for needs_confirmation or needs_human.
doctor returns 1 when required checks in its selected readiness scope fail;
unavailable optional providers remain informational for unrelated tasks.
Enable pipefail in a supporting shell so a downstream formatter preserves
REA's failure status:
set -o pipefail
rea inspect-artifact ./app.asar --json | jq . > inspection.json
--token-count reports a tokenx estimate for the selected emitted document,
including a requested full-output envelope and metadata. JSON uses the same
terminal indentation or compact piped representation as normal output; JSONL
uses its emitted line representation. The final newline is included (tokenx
assigns whitespace no tokens). This is an estimate, not a model tokenizer count.
Counting retains the existing bounded formatted-string allocation after capacity
admission; omit the flag to stream large JSON documents without materializing
the complete formatted text. Exhausted token windows display an empty range at
the total token count and have no next offset.
inspect-devicetree
Inspect an explicit local FDT v16/v17 blob without execution or an external tool. Returns header, reservations, node/property tree, derived standard values and selected phandle references. See devicetree inspection for limits and retained-view pagination.
rea inspect-devicetree /artifacts/board.dtb --json
rea inspect-devicetree /artifacts/board.dtb --detail summary --evidence_output /artifacts/board-evidence.json --json
PE TLS and load configuration
rea inspect-pe-load-config /absolute/path/to/application.exe --json
Reports TLS callbacks and size-covered load-config fields without execution.
Use --max-callbacks and --max-file-bytes to set read budgets. See
PE TLS and load configuration.
Binary hardening
rea inspect-binary-hardening /absolute/path/to/program --json
Reports ELF, PE, and Mach-O hardening rows without execution. A missing import
is unknown, not disabled. Use --max-file-bytes to set the read budget. See
binary hardening.
Bun standalone executables use the existing inspect-artifact and
extract-artifact commands to inventory and materialize embedded source,
sourcemaps and bytecode without running the executable. See
Bun standalone executables for layout coverage and limits.
Release exposure
rea audit-release-exposure /absolute/path/to/MyApp.app --json
rea audit-release-exposure ./dist/MyApp.app --fail_on high --json
Reports what static analysis of a shipped .app, install directory, ASAR, or
executable recovers. --fail_on high|medium|low exits 3 when a completed
audit has an exposed check at that severity. Unknown and unassessed checks stay
in the report and do not change the exit status. See
Release exposure.
Deno compile executables use the same commands to inventory and materialize the
embedded virtual filesystem, remote modules, and recorded options. Inventory retains
embedded environment-file values and certificate bytes in local Evidence with their
counts and byte lengths, and extraction does not write them as files. The npm snapshot
length is reported and not decoded, and the executable is not run. See
Deno compile executables.