Compare commits

..
Author SHA1 Message Date
808acf6dec test: keep child deadlines that rule out fallback exits
These fixtures' descendants exit on their own after 20 or 30 seconds. The
shorter explicit deadline is what proves cleanup stopped them first.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 04:12:00 +00:00
2064e6a66e test: bound every test process registry by the running test
Registries created directly with createTestProcessRegistry did not get the
processTest default, so a hung child there had no deadline once its explicit
value was removed; Vitest's test timeout does not settle the awaited run, so
shutdown was never reached. Derive the default in the registry from the
running test instead, using the real clock.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 04:12:00 +00:00
aeb30599af test: remove fixed child-process deadlines from tests
239 literal timeoutMs values on cli.run and processes.run only restated
a guess at host speed. On hosted runners with four workers, cliOutput's
3-second value failed while the command still produced correct output.
Deadlines that are the behavior under test, derived from an operation
budget, or that bound a regression's wait remain.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 04:12:00 +00:00
db0748389f test: bound fixture child processes by the remaining test deadline
A processTest child run without its own timeoutMs now gets the test's
remaining deadline less a reporting reserve (at least 5 seconds, or a
tenth of the deadline). A hung child still fails with its captured output,
without a fixed per-call value that assumes a host's speed.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 04:11:59 +00:00
gensokyo-devandClaude Opus 5.5 e8c838e8bd test(mcp): keep the surrogate path handler check within its deadline
The handler regression added in #10390 listed tools under the full input
profile and matched every advertised schema before calling each tool. It
took about 14 s locally and passed the 30 s deadline on loaded CI runners.
advertisedPatternsVMode.test.ts already proves that no input or output
profile advertises the surrogate escape, so this case now checks only the
runtime refusal through the default-profile handlers, as the
relative-path case does.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 05:06:09 +01:00
gensokyo-devandClaude Opus 5.5 809f0d69db fix(mcp): report HTTP listener recovery from its parsed failure
rea mcp --http printed "Choose another --port or stop the process using
it" for every listener failure except a missing host allowlist, so a host
lookup failure, an unassigned address or a permission denial got port
advice. StreamableHttpListenError already classifies the native failure
and owns a recovery for each kind.

Startup now always prints that owned recovery behind one shared lead-in,
and the fixed message is removed. The native cause text stays out of
stderr as before.

Fixes #7346

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 04:57:03 +01:00
e1d229cd6b test(process): plan scenario admission cases for a host that reaches it
Planning for win32 refuses PTY capture before root admission runs, so the
cancellation cases now plan for Linux on any host. The ENOTDIR admission
case skips on Windows, which reports ENOENT for a path below a file and
admits it as a root the scenario may create.

Squashed from #9306 onto current main.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 04:50:06 +01:00
01fcf8e3d7 test(ghidra): give simulated Linux installs a POSIX path on Windows
The Ghidra profile fixture simulates a Linux host, so posix.isAbsolute
refused the C:\ install directory a Windows test run supplied. The
fixture now owns a POSIX-shaped install directory with shared ownership
and cleanup, used by the DOL, MIPS, PowerPC, RISC-V and language
override suites.

Squashed from #9284 onto current main.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 04:50:06 +01:00
RRFZ 0a3368a6a9 test(filesystem): admit the snapshot fixture as written on Windows
The snapshot identity test writes its file and admits it at once with the
default ChangeTime mode. On Windows that ChangeTime is inside the 2 s drift
window, so admission keeps re-reading the descriptor until it settles. The
returned baseline is then a later observation, not the one the test
captured, and the descriptor stat runs more than once.

Admit the fixture as written. That is the documented mode for a file that
was just written, and it reads the descriptor once on every host. Settling
keeps its own coverage in ChangeTimeBaseline.test.ts.
2026-11-14 04:50:06 +01:00
RRFZ 3986bf9f8e test(artifacts): fit the buffered member case to Windows stream buffers
The shared reader lifecycle case waits for a member's writable side to
finish before it reads anything. That only happens while the member fits
below the readable high-water mark. Node's default is 16 KiB on Windows and
64 KiB elsewhere, and the ESP flash fixture's first member is a 32 KiB
bootloader region, so both cases hung until the 30 s timeout on Windows.

Pick the first nonempty member that fits the default buffer and assert
that it fits the opened stream before waiting.
2026-11-14 04:50:06 +01:00
morluto bddbd80afb refactor(cli): run one-shot commands through one shared owner (#10405)
* refactor(cli): run one-shot commands through one shared owner

scripts/rea.mjs and the test CLI session worker each implemented the
one-shot command sequence: argument normalization, output admission,
configuration errors, version documents, the Amp scope refusal, and
result delivery. The worker copy had drifted: it never rendered
formatted version reports or the Amp refusal.

Move the sequence into src/cliOneShot.ts. Both callers supply a host
for their streams, exit and resource ownership. The launcher keeps
closed-pipe status, worker shutdown and process exit; the session
worker captures output per command.

The cliOutput bundle entry is superseded and removed. The dispatcher
test now replaces only the command runtime, so output admission runs
production code. A new parity test compares the file session with a
fresh process.

* test(cli): bundle the one-shot entry in the runtime bundle fixture
2026-11-14 11:48:19 +08:00
morluto f1e250ee76 test(dmg): stop hard-coding host speed in attachment recovery (#10431)
The test bounded each hdiutil and CLI step with its own 10-30 s process
deadline. On hosted macOS runners the test takes 32-45 s (about three
times a local run), and the busy-image CLI call, which verifies the whole
image before its attach is refused, exceeded 30 s and was killed in two
recent main runs. Remove those per-step deadlines; the project deadline
still bounds a hang.
2026-11-14 11:47:59 +08:00
morluto 71712f27f7 test(mcp): open stdio clients through the shared local-tools fixture (#10411)
* test(mcp): open stdio clients through the shared local-tools fixture

Twelve files built their own StdioClientTransport and Client for
`rea mcp`. Those clients never listed tools, so the SDK did not
validate structured results against advertised output schemas, and each
file repeated transport setup, stderr draining and close ordering.

Use connectLocalToolsMcp, which lists tools, validates replies,
silences logging, leaves deep providers unconfigured and owns cleanup.
Per-test clients register test-scoped cleanup; module-level clients
collect cleanup for afterAll.

* test(mcp): keep failed file-scoped MCP cleanup owned

The three module-level clients removed every cleanup callback before
running them, so a failed cleanup lost its only handle. Add
fileLocalToolsMcp, which connects before the file's tests and removes
each cleanup only after it succeeds, and use it in those files.
2026-11-14 11:47:55 +08:00
morluto fcc0a8c454 perf(mcp): build advertised tool schemas once per contract (#10421)
* perf(mcp): build advertised tool schemas once per contract

The MCP SDK reprojects every registered tool schema on each tools/list.
REA memoized that projection, but per wrapper, and each server built new
wrappers for every contract, so each new server reconverted the whole
catalog: about 280 ms per tools/list for 255 tools with the full input
profile. Each wrapper also registered a new schema in zod's global
registry, so heap grew with every server.

Contracts are immutable module values, so build each advertised input
and output wrapper once per contract. tools/list on later servers drops
to about 26 ms (full profile) or 48 ms (compact).

* test(mcp): load only the contract family under test
2026-11-14 11:47:51 +08:00
morluto 243e4015cb docs(ci): remove dated shard measurements from the testing guides (#10414)
* docs(ci): move dated shard measurements into a verification record

The testing guide and testing-lanes guide mixed the October 2026 shard
budget and stale-history measurements into current rules. Move them to
docs/verification/ci-shard-scheduling-2026-10-20.md and link it from
the rules they justify.

* docs(ci): drop dated shard measurements instead of keeping a record
2026-11-14 11:47:46 +08:00
RRFZ 4a4e959513 fix(javascript): retain global escapes across repeated calls 2026-11-14 04:46:27 +01:00
RRFZ 4e1e9b62a7 fix(javascript): preserve global escapes across callback boundaries 2026-11-14 04:46:27 +01:00
RRFZ 54677ec1d6 fix(javascript): preserve timer delays after global escapes 2026-11-14 04:46:27 +01:00
RRFZ 1abd327913 fix(javascript): retain timer delays after opaque global effects 2026-11-14 04:46:27 +01:00
RRFZ 186b2c352c fix(javascript): omit Node timers with rejected delay values 2026-11-14 04:46:27 +01:00
RRFZ 827b31c128 fix(javascript): honor child-process callback overloads 2026-11-14 04:46:27 +01:00
gensokyo-devandClaude Opus 5.5 ee339e5db5 fix(debuggers): keep stopped Rizin and GDB sessions explainable
After REA stopped a Rizin debug session (frame or output limit, deadline,
cancellation, protocol failure) or the debugger exited, later commands
returned a generic invalid_request and status gave no reason. GDB
collapsed the same states the same way.

Both managers now keep a published session addressable until
close_*_session releases it, record the first reason it stopped, report
it as closed_reason in session status, and refuse later commands with
capability_unavailable naming that reason and the close-then-restart
recovery. An ID the server never returned, or that close released, still
fails as invalid_request with its own recovery. Retained stopped
sessions keep their slot, which the capacity recovery now says. A GDB
command deadline now records its reason like cancellation already did.

Fixes #8024

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 04:42:35 +01:00
gensokyo-devandClaude Opus 5.5 6ce708cfde fix(rizin): report shell refusals instead of complete empty results
Rizin exits 0 when its shell refuses a statement, so execute_rizin_command
reported completion_status complete with exit code 0 and an empty stdout
that read as a genuine empty result.

Rizin 0.6 through 0.9.1 log exactly four refusal lines from
librz/core/cmd/cmd.c: an unknown command, a wrong argument count, an
invalid statement (RZ_CMD_STATUS_INVALID, which also stops the remaining
statements) and a parse error. The Rizin command profile now classifies a
completed process's stderr against those lines; the shared one-shot
service reports completion_status "rejected" and a limitation that names
the refusal and says stdout holds only earlier statements' output. Other
ERROR lines come from commands that ran and stay diagnostics.

Fixes #10364

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 04:42:35 +01:00
gensokyo-devandClaude Opus 5.5 884b683a94 fix(cli): advertise the decimal token grammar on integer arguments
`cliIntegerArgument` piped a regex-guarded string into a number but never
published Zod's input view, so `--schema` advertised a bare integer for 15
fields while the parser accepted only decimal argv text. `environment-serve
--port` had the same gap through the transforming MCP_PORT owner.

Remove `cliIntegerArgument` and route every call site through
`cliDecimalField`, binding each field to its domain or contract owner where
one exists so bounds, defaults and optionality come from that owner. Export
the input-view publisher as `publishArgvInput` and apply it to `--port`,
which keeps the MCP_PORT grammar shared with `rea mcp --http`.

The decimal catalog test now requires every numeric field's advertisement
to admit its decimal argv token, covers repeated options and
string-advertised token grammars, and checks `--schema` output directly.

The string branch's missing numeric range (#9348) is unchanged.

Fixes #9344

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 04:27:54 +01:00
5815178a8d ci: give the Apple artifact lane a passing-run budget
Passing runs of the macOS artifact lane took 10.2-14.9 minutes (median
13.6) across recent runs, and 4 of 12 runs that reached their steps were
cancelled at the 15-minute limit without a failing test. Allow 25
minutes so runner speed alone does not fail the lane.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 05:54:30 +08:00
87743d67f5 test: check the deadlines Vitest applies to collected tests
Probes in a source and a built project read each collected test's
deadline, including test, suite, and module long-running tags, so a
change in project inheritance or tag resolution fails directly. Both
the policy and the probes read one set of base deadlines.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 05:27:07 +08:00
d3efeb0c36 test: assert elapsed time where a deadline guarded complexity
Four wide-input regressions used a short per-test deadline as their only
check against a slow but terminating implementation. Assert the same
budgets explicitly so the project deadline only detects hangs. The
composed-body test keeps its exact read count instead.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 05:27:07 +08:00
46728ca41d test: remove per-test deadlines owned by project policy
Explicit deadlines at or below the project deadline only shortened it:
a literal 30-second value also disabled the coverage scaling, which timed
out wide-capacity regressions on main. Workloads above the built
deadline use the long-running tag. Deadlines derived from an operation
budget remain.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 05:27:07 +08:00
62798fa8b0 test: share the wide fingerprint analysis across projection modes
Both projection modes read the same 130,000-callable analysis without
changing it, so analyze the source once.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 05:27:07 +08:00
5bad48e122 test: let Vitest projects own test deadlines
Source projects allow 30 seconds and built projects 120 seconds, both
doubled under coverage, and hooks share their project's deadline instead
of Vitest's 10-second default. A long-running tag covers bounded
workloads that need more.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 05:27:07 +08:00
morluto cc7d73d23a test(ci): double test deadlines on hosted runners (#10417)
* test(javascript): give the wide fingerprint regression a capacity deadline

Each case runs about 22 s of CPU-bound projection on a typical hosted
runner. Its explicit 30 s deadline failed on slower runners at both two
and four Vitest workers, and it also overrode the suite's 60 s
coverage-mode allowance.

* test(ci): double test deadlines on hosted runners

Hosted CI jobs run four Vitest workers on a four-core runner. CPU-bound
capacity cases then share every core with other workers and their
subprocesses; three such cases exceeded the 30 s deadline at four
workers (16.8-22.7 s at two). The deadline only needs to bound a hang,
so hosted CI doubles it: 60 s, or 120 s with coverage. Local runs keep
30 s and 60 s.

* test(ci): check worker and deadline policy for local and hosted runners

The policy test loaded vitest.config.ts in a child that inherited the
parent's GITHUB_ACTIONS, so on CI it observed the hosted deadlines and
failed. Select the runner explicitly and check both: two workers and the
base deadline locally, four workers and twice the deadline on hosted CI.
2026-11-13 15:17:24 -05:00
morluto da91bbd4eb test(ci): retire loader-ownership history seeding (#10404)
* test(ci): retire loader-ownership history seeding

Both timing tiers now publish javascriptLiteralLoaderOwnership.test.ts's
own duration (fast 23.7s, full 26.6s), so the seeding returned its input
unchanged. Remove it and its tests.

* docs(ci): describe shard weighting without retired loader seeding
2026-11-13 14:20:11 -05:00
morlutoandClaude Fable 5.2 3403338ea0 perf(ci): use four Vitest workers on hosted runners (#10399)
Hosted CI jobs own a four-core runner but the suite ran on two workers.
Other hosts keep the two-worker bound because they are shared.

Co-authored-by: Claude Fable 5.2 <[email protected]>
2026-11-13 13:51:39 -05:00
Kaoru0822 4566e483ab fix(process): retain wide descendant inventories (#10386) 2026-11-13 10:49:10 -08:00
Kaoru0822 bf63760cb4 fix(setup): normalize Gemini trust rules before selection (#10378) 2026-11-13 10:48:50 -08:00
Kaoru0822 3b006841e2 fix(rizin): qualify process completion in command evidence (#10377)
* fix(rizin): expose command validity limits in observations

* test(rizin): compare guidance with its owning contract
2026-11-13 10:48:35 -08:00
gensokyo-devandClaude Opus 5.5 3cb75d46bf test(mcp): guard portable pattern escapes in every schema profile
The advertised-pattern guard only inspected the default profile and only
rejected the `\0` and `\u0000` NUL spellings, so a `\uD800` range shipped
in 6.9.0 unnoticed. Reject any unescaped `\u` or `\0` escape, and run that
check for each input and output schema profile.

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-13 18:57:31 +01:00
gensokyo-devandClaude Opus 5.5 b67c1e8ce6 fix(contracts): enforce well-formed Unicode paths without advertising surrogate patterns
rea-agents 6.9.0 advertised `^[^\uD800-\uDFFF]*$` on every local path
input. RE2 has no `\u` escape and Rust's `regex`/`jsonschema` crates refuse
non-scalar code points, so strict MCP clients and gateways (DeepSeek, RE2
gateways, Open Design) rejected the complete tools request and no REA tool
could run.

Move the rule into one runtime check, `rejectUnpairedSurrogates`, which
advertises nothing and still raises the `invalid_format` regex issue, so
CLI and MCP corrections keep reason, expected value, and message. The
objdump disassembly symbol already enforced the same rule with its own
refinement; it now shares the owner and the correction shape.

Dropping the pattern also brings inspect_android_jni_bindings within the
compact profile's per-schema budget, so compact delivery now keeps its
DEX dimension pattern. The descriptor admission test follows the actual
presentation instead of assuming compact always omits it.

Fixes #10238
Fixes #10348
Fixes #10198

Co-Authored-By: gensokyo-dev <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-13 18:57:31 +01:00
morluto cdc4541ef7 refactor: remove redundant seams and preserve cleanup ownership (#10375)
* refactor: remove redundant seams and preserve cleanup ownership

* fix: validate PE resource extents before budget checks
2026-11-13 10:11:06 -05:00
Kaoru0822 4b26f38d6f fix(javascript): preserve wide async chunk collection (#10362)
* fix(javascript): collect async chunks without call spreads

* test(javascript): bound the public checkpoint control
2026-11-13 06:17:43 -08:00
Kaoru0822 e841fb16e4 fix(browser): validate wide scenario reference lists (#10359) 2026-11-13 06:17:22 -08:00
Kaoru0822 26b70c8ea4 fix(mcp): complete wide retained occurrence inventories (#10356) 2026-11-13 06:16:57 -08:00
Kaoru0822 5d046b9ad7 fix(javascript): retain wide function fingerprint inventories (#10355) 2026-11-13 06:16:36 -08:00
Kaoru0822 c4cec6373d fix(javascript): retain wide runtime load-state inventories (#10354) 2026-11-13 06:15:03 -08:00
Kaoru0822 02a33a8f58 fix(javascript): reject invalid fs open argument roles (#10346) 2026-11-13 06:10:25 -08:00
Kaoru0822 ea101dbe30 fix(browser): interpret agent link relations once per element (#10345) 2026-11-13 06:07:58 -08:00
Kaoru0822 fd3e613d23 fix(captures): retain wide native numeric exclusions (#10343)
fix(captures): append admitted numeric exclusions without call spreads
2026-11-13 06:07:41 -08:00
Kaoru0822 adf8bdf833 fix(javascript): preserve GraphQL Source ownership across spreads (#10338)
* fix(javascript): preserve GraphQL Source ownership across spreads

* fix(javascript): keep inline Sources owned by their enclosing Source
2026-11-13 06:07:24 -08:00
Kaoru0822 cd96c5e9eb fix(desktop): preserve own JSON keys in task grading (#10331)
* fix(desktop): preserve own state keys in task witnesses

* test(desktop): preserve witness parse failure details

* fix(desktop): retain own column keys in task conditions
2026-11-13 06:07:06 -08:00
Kaoru0822 617e4cbfb3 fix(windows): preserve pre-epoch shortcut timestamp milliseconds (#10305)
fix(windows): keep pre-epoch shortcut times in their millisecond
2026-11-13 06:06:47 -08:00
morluto bbe2bcc979 refactor: remove legacy seams and consolidate resource ownership (#10349) 2026-11-13 04:45:16 -05:00
Kaoru0822 5cddc69c51 fix(verify): keep adopted resources live until command owner closes (#10314) 2026-11-12 23:58:18 -08:00
Kaoru0822 b2c4635a36 fix(verify): retain Go fixtures until owned cleanup completes (#10309) 2026-11-12 23:58:00 -08:00
Kaoru0822 15a2de737c fix(navigation): offer available instruction observations (#10307) 2026-11-12 23:57:46 -08:00
Kaoru0822 c2ca3819e5 fix(analysis): iterate admitted inventories without call spreads (#10304) 2026-11-12 23:57:04 -08:00
Kaoru0822 54906fdb1f fix(verify): check Android readiness repair guidance (#10242) 2026-11-12 20:40:42 -08:00
Kaoru0822 0084810ef7 fix(browser): bound cached responses with zero-padded lengths (#10237) 2026-11-12 20:40:26 -08:00
Kaoru0822 feca30c72c fix(go): validate string extents before budget admission (#10231) 2026-11-12 20:40:05 -08:00
Kaoru0822 913b029c96 fix(desktop): retain dconf failures without stderr (#10230) 2026-11-12 20:39:50 -08:00
Kaoru0822 bfecbb1d48 test(native): cover deadlines throughout signature inspection (#10217) 2026-11-12 20:39:31 -08:00
Kaoru0822 070cfce443 fix(ci): settle cancelled test-group descendants (#10229) 2026-11-12 20:38:32 -08:00
Kaoru0822 0c01517cc0 fix(pcap): disclose truncated DNS responses as partial (#10214)
* fix(pcap): disclose truncated DNS responses as partial

* fix(pcap): explain DNS truncation in coverage limitations

* fix(pcap): preserve mDNS TC semantics
2026-11-12 20:34:34 -08:00
Kaoru0822 7c9d65fe97 fix(test): resolve filesystem aliases before planning prerequisites (#10213) 2026-11-12 20:29:42 -08:00
ShuaixinHuangandShuaixinHuang 9c86465d47 fix(captures): retain preparation cleanup owners (#10288)
Co-authored-by: ShuaixinHuang <[email protected]>
2026-11-12 22:08:37 -05:00
morluto c836f57b7d refactor: consolidate shared analysis boundaries (#10293) 2026-11-12 22:05:32 -05:00
morluto 6878a84796 fix(tests): restore worker fixture and allow setup completion 2026-11-12 20:57:43 -05:00
Kaoru0822 937d10df1c fix(pe): enforce DER extension critical encoding (#10212) 2026-11-12 19:33:31 -05:00
Kaoru0822 c19d319929 fix(desktop): preserve literal directories in ELF origin expansion (#10215)
fix(desktop): expand ELF origin paths as literal directory names
2026-11-12 19:33:16 -05:00
Kaoru0822 966bde3780 fix(android): enforce DEX array dimension limit (#10227) 2026-11-12 19:29:53 -05:00
Kaoru0822 4d15b7f509 fix(android): preserve SDK-gated manifest permissions (#10236) 2026-11-12 19:29:40 -05:00
Kaoru0822 50d4f7bea4 fix(desktop): validate guest file replies at their shared boundary (#10224)
* fix(desktop): reject malformed guest file base64

* fix(desktop): scope guest file metadata validation to its namespace
2026-11-12 19:29:18 -05:00
Kaoru0822 594d73985c fix(desktop): preserve Unicode separators in entry values (#10210) 2026-11-12 19:29:05 -05:00
Kaoru0822 418e3f5e91 fix(release): honor selected npm in update verification (#10240) 2026-11-12 19:28:29 -05:00
Kaoru0822 0f5ac9d5f3 fix(desktop): preserve multiline apt-config locations (#10241) 2026-11-12 19:28:11 -05:00
2SinceJulyand2SinceJuly 7559e06740 fix(contracts): drop nested defaults from a root union sibling (#10254)
* fix(contracts): drop nested defaults from a root union sibling

Flattening a union of strict object groups lifts each group's properties
into one object. A default the scenario group carried inside
normalization.rules survived that lift even though the passive group
declares no normalization, so a client that fills nested defaults sent
{normalization: {rules: []}} and the runtime parser refused the call
with "Unrecognized key: normalization".

Remove defaults below a property that any group omits, not just the one
on the property itself. A property every group owns keeps its own
nested guidance.

* fix(contracts): keep a nested default a reference still targets

Dropping the nested default of a property a sibling group omits also removed defaults that references point into. A resource whose validation $ref targets its own default was advertised with that reference intact but its target gone, so the advertised schema no longer compiled in AJV or client projection.

Collect the paths into a default that references inside the branch target and keep those entries while stripping the rest. Other nested defaults are still removed, so the strict sibling group keeps refusing the object.

* test(contracts): type the root union default regression fixture

The fixture spread the canonical branch through a widened cast, so the repository typecheck rejected the spread and indexed access. Narrow the branch shape and guard the optional schema before collecting its referenced defaults.

---------

Co-authored-by: 2SinceJuly <[email protected]>
2026-11-12 19:27:58 -05:00
ShuaixinHuangandShuaixinHuang 9d32836d37 fix(apple): expand loaded siblings along the first dyld chain (#10248)
* fix(apple): expand loaded siblings along the first dyld chain

* fix(apple): retain conditional incoming dyld expansion edges

* fix(apple): qualify findings by their expansion chain

---------

Co-authored-by: ShuaixinHuang <[email protected]>
2026-11-12 19:27:40 -05:00
morluto 02ebd3e02d refactor: prune test-only seams and obsolete helpers (#10275) 2026-11-12 19:24:54 -05:00
1450 changed files with 22770 additions and 15727 deletions

No files matched your search

+580 -96
View File
@@ -2,123 +2,607 @@
name: reverse-engineer-anything
description: Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA, and build agent environments (AgentEnv bundles) of web applications from a live instance or a capture, and of Linux desktop apps with a task from a request. Use shipped-artifact or requested runtime evidence to explain features, compare versions, decompile code, or guide a reconstruction. Skip REA for ordinary source-repository architecture analysis.
metadata:
version: "64"
version: "62"
---
# REA
Use REA when a claim depends on a shipped artifact, decompilation, requested
runtime evidence, or behavior not established by available source. For ordinary
analysis of a complete source repository, use normal repository tools and do not run REA
Use REA when a claim depends on a shipped binary or package, decompilation,
requested runtime evidence, or comparison with
behavior not established by available source. For ordinary analysis of a
complete source repository, use normal repository tools and do not run REA
readiness or provider commands.
## Select the target and authority
Use the target selected for this task. If the user says only "my app", ask which
app before searching artifacts. Resolve a named app to one clear installed
artifact; ask when ambiguous. Unrelated reports and nearby fixtures do not select
it. Never substitute an example or broaden the selected target or runtime actions.
Distinguish observations, derivations, inferences, and unknowns. Cite Evidence
IDs and preserve source identity, coverage, limitations and partial facts.
Static code, strings and declarations do not establish observed execution.
Within the requested investigation, call available analysis tools directly;
REA requires no permission grants or per-call approval flags. The client may
independently require approval; preserve its actual access requirements.
## Connect only when needed
If REA tools are available and registration is not known stale, proceed directly.
Use the connected server's actual tool list and schemas: repository-main guidance
may describe features absent from an older release. Keep complete inline Evidence
when retained references are not advertised. Skill installation alone adds no
MCP server or engines; a Claude Code, Codex or Cursor Marketplace plugin bundles its own server.
Installing this skill supplies instructions; it does not register the REA MCP
server, install analysis engines, or add tools to an already-running agent.
The exception is a Claude Code, Codex, or Cursor Marketplace plugin install:
the plugin bundles this skill and registers its own `rea` server, outside the
state that `rea setup` and `rea doctor` manage.
For absent tools, blocked calls, unavailable providers, host timeouts or startup
failures, read [connection and recovery](references/connection-and-recovery.md)
before repair. Diagnose the observed blocker, continue useful supported work,
and resume the original task after repair. Do not repeat unchanged failed calls.
A host timeout without a REA result does not establish cancellation or cleanup.
Retry a dropped connection once only if safe to repeat or known not started;
if it may have changed the target, or fails again, ask for a client restart.
Retry failed connection startup once; do not retry the same unchanged failure.
If REA tools are available and their registration is not known to be stale,
proceed directly to the target. Do not run diagnostics or setup before every
investigation. Use the connected server's actual tool list and input schemas;
a skill installed from repository main may describe capabilities absent from
an older npm release. Keep complete inline Evidence when that server does not
advertise retained references.
Choose the registration owner: plugin recovery differs from setup-owned repair.
If tools are absent, a call is blocked by the client, or a required provider is
unavailable, read [connection and recovery](references/connection-and-recovery.md).
Diagnose the observed blocker, continue useful supported work, and resume the
original task after repair. Client approval, shell PATH, registration and engine
availability have different remedies; avoid repeating setup for all of them.
If the host times out without a REA result, do not repeat the unchanged call or
assume work stopped. Read the recovery guide's deadline distinction before retrying.
If the MCP connection drops, retry that call once only when it is safe to
repeat or known not to have started. If it may already have changed the
target, or it fails again, tell the user and ask them to restart the MCP
client. If REA could not start its connection, retry once, then tell the user
and ask them to restart the MCP client. Do not retry a startup failure that
returns the same unchanged result. For plugin installs, follow
[plugin recovery](references/connection-and-recovery.md#rea-installed-as-a-client-plugin).
For setup-owned registrations, run `rea doctor` when REA could not start
or tools stay missing after that restart. For `cleanup_incomplete`, a retryable
capacity limit, or `configuration_invalid`, follow the recovery guide's ladder
before asking for a restart. Read that guide before a second repair attempt.
Choose the installation owner before repairing registration. Plugin installs use
[plugin recovery](references/connection-and-recovery.md#rea-installed-as-a-client-plugin);
only setup-owned registrations use the guide's
[registration repair plan](references/connection-and-recovery.md#repair-registration-when-needed).
A plugin-only doctor result of `missing` does not call for `rea setup`.
Setup-owned repair starts with a read-only plan showing paths, backups and skill
replacement before authorized writes. Do not install unrelated software to repair
registration. REA setup never installs/upgrades Node, npm, Homebrew, Java,
Ghidra, IDA, JADX, Kuna, Binwalk or Unblob.
For an unsupported client, use manual stdio with an existing absolute runtime
and entry script, or the CLI fallback in the recovery guide.
## Route before analysis
REA setup never installs or upgrades Node.js, npm, Homebrew, Java, Ghidra, IDA,
JADX, Kuna, Binwalk, or Unblob. Use existing prerequisites; do not install unrelated
software to repair MCP registration. For an unsupported client, use manual stdio
registration with an existing installation’s absolute runtime and entry script, or continue through the CLI.
Read the matching guide before its workflow; read only the relevant sections.
Target-free tools take their explicit paths/endpoints without `open_binary`.
Active-target native/archive tools require it. Archive members and executable
code are different questions; follow the selected artifact's application layer.
A concrete CLI fallback for an operator-supplied JavaScript tree or ASAR is:
| Supplied target or question | Guide and first operation |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Native executable/library, Ghidra/IDA/Kuna, managed PE/CLI or NativeAOT | [Native and managed](references/native-and-artifacts.md): `open_binary`; managed CLI metadata: `inspect_managed_artifact`. Read provider/lifecycle sections before reuse or edits. |
| Headerless/raw firmware, Cortex-M, ESP app/bootloader or whole-flash dump, HEX/S-record/UF2/DfuSe | [Firmware and raw images](references/firmware-and-raw-images.md): check loader support first; raw: explicit `open_binary` format/mapping; ESP/container: `open_binary` then `inspect_artifact`; region scan/extraction: advertised firmware tools. |
| Archive/package inventory or extraction, PyInstaller, Bun/Deno executable, Godot PCK, UnityFS or wxapkg | [Packages and archives](references/packages-and-archives.md): `open_binary`, then `inspect_artifact`; read extraction/mount limits before materializing. |
| ASAR/extracted JavaScript or Electron tree, feature trace or version comparison | [JavaScript applications](references/javascript-applications.md): `analyze_javascript_application`. |
| APK code, manifest, classes, methods or incoming references | [Android applications](references/android-applications.md): `inspect_android_package`; signing/inventory use their separate routes. |
| PE/ELF/Mach-O tables or hardening, Go/Rust/IL2CPP/GPU/model metadata, Windows shortcuts, kernel config, pyc/BEAM, SQLite, WASM, EVM, APK signing or recorded crash | [Offline format inspectors](references/format-inspectors.md): select the named target-free inspector in that format's section. |
| Shipped application exposure for a local app, install directory, ASAR or executable | [Offline format inspectors](references/format-inspectors.md#release-exposure): `audit_release_exposure`; preserve unknown/unassessed checks and marker-only credential findings. |
| Apple plist, Interface Builder, asset catalog or keyed archive resources; unknown desktop installation directory | [Resource and directory inspection](references/native-and-artifacts.md#apple-app-resources): `open_bundle_resources` or `inspect_desktop_application`; no executable/provider required for resource sessions. |
| Native execution, live calls, instrumentation, process effects; browser/Electron runtime or agent-performed actions | [Runtime observation](references/runtime-observation.md): select the observation and declared lifecycle first. Already-open page: `list_browser_targets`; Electron: `list_electron_targets`; agent actions: `capture_browser_scenario`. |
| Recorded HAR/SAZ/mitmproxy/qlog, packet capture, protocol-field discovery or logic/signal recording | [Recorded captures](references/recorded-captures.md): `inspect_web_network_capture`, packet/signal inspection or decoder discovery as appropriate; no live acquisition or recorded-URL fetch. |
| Web AgentEnv bundle from a live instance or capture; task for an exported desktop environment | [Environments](references/environments.md), including its desktop-task section: read before recording/export; `capture_browser_scenario` then `export_environment_bundle` for a live web bundle. |
| Linux desktop application environment built from a user request | [Desktop builds](references/desktop-build.md): drive the selected app on a build computer, then export and witness. |
| Retained Evidence paging/composition, comparisons, unknowns or a broader investigation | [Evidence workflows](references/evidence-workflows.md): use existing source IDs and the smallest view that answers the question. |
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
If a needed tool is unadvertised, inspect `binary_session {}` and
`result.tool_availability` for availability reasons/remediation; never call an
unadvertised tool. Expand group `names`, inheriting inventory client requirements
unless overridden. Their union is the complete in-scope inventory; `tools/list`
owns canonical schemas. The default summary keeps provider candidate status,
code and reason; `omitted_fields` names exclusions. Request `detail: "complete"`
only for remediation/diagnostics, candidate capabilities or tool effect flags.
Candidate facts describe possible providers; top-level capabilities and
`analysis_provider_binding` describe the current route. Keep those scopes distinct.
No MCP registration or native engine is required. Read the returned Evidence,
graph, limitations, and unknowns with the same care as an MCP result; the CLI
returns the Evidence record directly. This fallback does not establish that
MCP is configured. Native CLI tasks still require their selected engine.
Prefer an existing `rea` CLI. When none is installed and REA package acquisition
is within the authorized scope, use `npx -y rea-agents@latest` in its place from a
trusted directory outside the target project, with absolute target paths. Managed
setup binds the current installation directly; retain its runtime and package
cache entry, or restore that installation before repairing its registration.
For setup-owned registration, configuration repair starts with a read-only setup plan; show paths, backups and
skill replacement before applying authorized changes. Preserve existing approval
and target-execution boundaries.
## Route the target first
Choose the first tool from the target the user supplied. Use `open_binary` for
active-target native or archive workflows; target-free tools take their own
explicit path or endpoint and do not need it.
- ASAR or extracted JavaScript/Electron tree:
`analyze_javascript_application`.
- Archive/package member inventory (ZIP/APK/IPA/MSIX/AppX, DMG, or a PyInstaller
executable): `open_binary` with the supplied local path; use `inspect_artifact`
when its graph and findings help answer the question. A PyInstaller CArchive
is the application layer; the bootloader is only the native prefix. Start with
`detail: "summary"` for large inventories, then use retained inventory views
for selected member fields/pages. A complete small inventory can cost less
than extra calls and schemas. Runtime options are metadata, not files.
Compact delivery still inventories and hashes members. See the
[Python packaged applications guide](https://github.com/morluto/rea/blob/main/docs/python-packaged-applications.md).
- Bun `--compile` executable: `open_binary`, then `inspect_artifact`; the Bun
module graph is the application layer. Extract stored modules and analyze the
JavaScript. See [Bun standalone executables](https://github.com/morluto/rea/blob/main/docs/bun-standalone.md).
- Deno `deno compile` executable: `open_binary`, then `inspect_artifact`; the
embedded virtual filesystem is the application layer. Extract stored files and
analyze compatible JavaScript. Environment-file values and CA certificate bytes
are retained in local Evidence with their counts and byte lengths. The npm
snapshot length is reported and not decoded, and the executable is not run. See
[Deno compile executables](https://github.com/morluto/rea/blob/main/docs/deno-standalone.md).
- Apple app plist, Interface Builder, asset catalog, or keyed archive resources:
`open_bundle_resources`, then the selected resource tool. This does not
require `CFBundleExecutable` or a deep provider. Resource sessions have no
executable digest or binary snapshot; use `open_binary` for native code.
- Android APK code, classes, methods, or incoming references:
`inspect_android_package`, then focused Android tools when advertised.
Archive member inventory still uses the archive route above.
- Managed PE/CLI assembly: `inspect_managed_artifact`.
- Firmware image: `inspect_firmware_regions` when advertised. Use
`extract_firmware` when extraction is requested, with a caller-selected new
absolute output directory. These tools use caller-supplied Binwalk/Unblob on
Linux; see the [firmware guide](https://github.com/morluto/rea/blob/main/docs/guides/firmware/firmware-analysis.md).
- ESP32-family app or bootloader image (`0xE9` magic): `inspect_artifact`
without a provider; see the ESP section of `references/native-and-artifacts.md`.
- .NET NativeAOT PE/ELF: native Ghidra analysis. Native code can yield pseudocode;
consult the NativeAOT workflow in `references/native-and-artifacts.md` for
optional metadata recovery and its supported host/layout boundary.
- Native program behavior, live calls, instrumentation, or command-run effects:
choose the matching [native runtime workflow](references/runtime-observation.md#native-runtime-questions).
`open_binary` and `trace_native_values` establish static facts; they do not
observe execution. Runtime tools have distinct launch, attach, mutation and
cleanup effects, and do not require a preliminary static session unless their
own contract says so.
- User-owned browser page already open: `list_browser_targets`.
- Passive traffic capture of a SPA: the default scope is the page's own origin,
and data APIs usually live on another origin. Discover API origins from
absolute URLs in loaded bundles or script text via JavaScript analysis tools
(another passive capture without those origins still scopes to the page
origin, and `completeness.excluded` counts lack URLs). Pass both the page
origin and those API origins in `allowed_origins` — the list replaces the
default scope — and read `completeness.excluded` before reporting that an
API is absent. For long captures with body shapes, use
`export_evidence_bundle` instead of expecting an inline result.
- Browser action the agent must perform (click, type, navigate):
`capture_browser_scenario` with declared actions. See
[runtime observation](references/runtime-observation.md) for connect/launch
and the focused follow-up browser tools.
- Agent environment (AgentEnv bundle) of a web application: read
[environments](references/environments.md) before recording; it records with
`capture_browser_scenario` and writes the bundle with `export_environment_bundle`.
- Task for a desktop application's exported environment, from a
`capture-desktop-application` folder: read
[desktop application tasks](references/environments.md#tasks-for-a-desktop-application-environment).
- Environment of a Linux desktop app with a task, from a request such as
"build me an Inkscape environment where the agent changes a poster's
headline": read [desktop builds](references/desktop-build.md). You drive the
app on a build computer from `start_build_computer`, then export and witness.
- Fiddler Everywhere traffic or a native `.saz` capture: read the
[Fiddler workflow](references/runtime-observation.md#fiddler-everywhere-traffic-debugging).
Inspect saved SAZ with `inspect_web_network_capture` and `format: "saz"`;
connect Everywhere's separate MCP server for live traffic.
- Retained HAR/SAZ/native mitmproxy/aioquic qlog capture: `inspect_web_network_capture`. Select
original record ordinals when useful; inspect producer fields and byte/number
sidecars without fetching recorded URLs or claiming live attribution. HAR/SAZ/qlog
old-generation heap is `REA_CAPTURE_DECODER_OLD_GENERATION_MIB` (default 192 MiB,
floor 8, maximum 16384), set where REA starts. Host memory and `NODE_OPTIONS` do not
raise it. See
[historical captures](https://github.com/morluto/rea/blob/main/docs/guides/web/web-network-captures.md) for the exact upstream
profile and credential exclusions. Qlog support starts with aioquic 1.5.0
JSON 0.3; stream coverage and endpoint loss declarations do not recover bodies
or establish network loss. Prefer detail summary, then kind qlog trace/stream
pages; select one trace, stream ID and direction for contribution/range pages.
Facts retain all-recorded-events-before-selection scope. Other qlog layouts
and PCAP correlation are unsupported.
- User-owned Electron runtime already open: `list_electron_targets`. Before an electron-main Inspector attempt, check packaged fuses with `inspect_electron_fuses`; disabled `EnableNodeCliInspectArguments` means the app ignores `--inspect`.
- Shipped application exposure for a local `.app`, install directory, ASAR, or executable: `audit_release_exposure` / `rea audit-release-exposure`. Each check has a stable id, a status, and fixed remediation. Unknown and unassessed checks stay in the report. This does not establish that an application is secure. Credential-marker hits report the rule id and location only. See [release exposure](https://github.com/morluto/rea/blob/main/docs/release-exposure.md).
- Packet-field discovery: `inspect_packet_fields` searches installed protocols
and field names/types before choosing fields for `inspect_packet_capture`.
Query/page the relevant records; registrations do not establish observed traffic.
- Recorded packet captures: `inspect_packet_capture` with selected TShark fields,
display filters and decode-as rules. Requires Linux, util-linux prlimit and
TShark 4.x on an absolute `PATH` entry or via the optional absolute
`REA_TSHARK_COMMAND` override; see
[capture guide](https://github.com/morluto/rea/blob/main/docs/guides/data/packet-captures.md).
Packet limits count input packets before filtering. Output defaults to 50 matching
frames; use `match_offset` / `match_limit` for later pages, or explicit null
limit for complete matching output. Preserve the source digest across pages.
This does not perform live capture.
For address/DNS/SNI/protocol summaries use `inspect_pcap_capture` with MCP
detail summary, then retained kind pcap pages. Requires Wireshark 4.x tshark
and capinfos, with Linux prlimit or Windows Job Objects; see
[packet summaries](https://github.com/morluto/rea/blob/main/docs/guides/data/pcap-analysis.md).
Select an exact DNS question for address pages; page exhaustion does not prove
network completeness. This reads recordings and does not acquire traffic.
- Decoder discovery: `inspect_signal_decoders` searches installed decoder IDs
or selected decoder help, including channels and options. Query and page the
relevant details before choosing settings; this does not scan hardware.
- Recorded logic/signal traces: `decode_signal_recording` with explicit decoder,
channel and protocol settings. On Linux with util-linux prlimit, supply sigrok-cli 0.7.2 through
`REA_SIGROK_COMMAND`; see [recording guide](https://github.com/morluto/rea/blob/main/docs/guides/signals/signal-recordings.md).
Output defaults to 50 literal lines; select annotation classes and page with
`line_offset` / `line_limit`, or explicit null for all remaining output.
Window counts are display lines, not annotations or samples. Preserve source digests across pages.
This reads existing files; it does not acquire hardware signals.
- Explicit local WebAssembly artifact: `inspect_wasm_artifact`. Supply WABT 1.0.42
through `REA_WABT_BIN_DIRECTORY`; retain exact byte/tool identity and decoded WAT.
Select JavaScript glue and candidate paths explicitly and preserve URL/path
ambiguity. No implicit fetch or target execution; WAT is not original source.
See the [WASM guide](https://github.com/morluto/rea/blob/main/docs/guides/wasm/wasm-artifacts.md).
- Explicit local Android APK (or ZIP/AAB) signing: `inspect_apk_signature`.
Prefer its summary or selected signer/digest/lineage page; retain complete
Evidence and use `inspect_analysis_view` kind `apk-signature` for follow-ups.
Reports which schemes are present (v1 JAR, v2, v3, v3.1), each signer's
certificate SHA-256 fingerprint (v1 included, from its PKCS#7 block), v3 key-rotation lineage, source stamp, and
whether each signed digest still matches the bytes (self-consistency). This
is identity and integrity, not a trust or authenticity verdict.
See the [APK signature guide](https://github.com/morluto/rea/blob/main/docs/apk-signature.md).
- Explicit local EVM bytecode carrier: `inspect_evm_interface` with caller-selected
`raw` or `hex` encoding. The owned worker deadline is `timeout_ms` (default
30000 ms, range 1..2147483647, next call). Select byte, address-space and CPU
allowances separately through `producer_limits`; tighter inherited OS limits
remain enforced, and the 256 MiB worker heap is independent. Preserve
carrier/decoded digests and treat selectors,
argument strings and mutability as inferred candidates; this performs no chain
lookup or target execution. For the complete inferred interface without hex or
duplicate stdout, discover support and select `detail: "summary"`; complete
Evidence stays retained under its original identity. Use the returned
`parent_evidence_id` for exact function or `bytecode_hex`/stdout/stderr pages.
Candidate coverage does not establish interface completeness. See the
[EVM bytecode guide](https://github.com/morluto/rea/blob/main/docs/guides/evm/evm-bytecode.md).
- Embedded Go compiler, module, build-setting metadata, or static `(ptr, len)`
string literals: `inspect_go_binary` with the explicit local file (pass
`include_string_literals: true` and optional `max_string_literals` for bounded
inferred static string-header recovery, and page retained rows with
`inspect_analysis_view` kind `go` collection `string_literals`). This built-in
target-free inspection requires no Go installation or disassembler. Preserve
raw module bytes and source ranges; absent metadata does not prove a binary is
not Go, static string recovery excludes compiler-inlined stack or immediate
strings, and unparsed text records remain explicit. See the
[Go binary guide](https://github.com/morluto/rea/blob/main/docs/guides/native/go-binaries.md).
- Embedded CUDA/HIP code objects: `inspect_gpu_code_objects`; inventory targets
first, then decode selected payloads and query retained GPU Evidence.
- Bundled GGUF or safetensors model declarations: `inspect_model_artifact`
with the explicit local file. Default header hashing leaves full-file identity
unknown; `hash: "full"` additionally reads all weights for SHA-256. MCP returns
retained summaries; optional `view` selects one page in the initial call. Use
`inspect_analysis_view` kind `model-artifact` for
batched metadata keys, tensor names or type totals, selected fields and pages.
Preserve header-scoped indices and array preview omissions; do not infer model
behavior from declared architecture or tensor types. See the
[model file guide](https://github.com/morluto/rea/blob/main/docs/model-artifacts.md).
- Unity IL2CPP names, member owners and string literals from `global-metadata.dat`:
`inspect_il2cpp_metadata` with the explicit local file; MCP defaults to compact
counts. Query sections, images, types, methods, fields, properties, events,
parameters or string_literals through `inspect_analysis_view`
with the returned `parent_evidence_id`, using exact filters, batch indices and
selected fields. Parameters accept `method_indices`, avoiding a scan of all
parameter pages. Missing collections in historical Evidence remain unknown.
Versions 24, 27, 29, 31 and 39; no Unity, IL2CPP
binary or disassembler is run. Unrecognized magic (possibly encrypted) and
other versions are unsupported. Optional matching `binary_path` enables static native addresses and type references; query `method_addresses` with indices or addresses/states, preserving every shared-body method and both artifact identities. Without a linked binary native counts remain unknown. Byte-pattern fallback is inferred; no runtime behavior or same-build pairing is proved. See the
[IL2CPP metadata guide](https://github.com/morluto/rea/blob/main/docs/il2cpp-metadata.md).
- Embedded cargo-auditable Rust dependency metadata: `inspect_rust_binary`
with the explicit local ELF, PE, or thin Mach-O file. This built-in
target-free inspection requires no Rust installation or disassembler. Preserve
package order, dependency indices, and the section file range. A missing
`.dep-v0` section is null metadata, not proof the binary has no dependencies.
A rustc commit hash is an inference from a read-only `/rustc/<40-hex>/` path.
For selected crates, use `view: {collection: "packages", names: [...],
fields: ["name", "version", "source"]}` on the first call; explicitly include
`dependencies` for direct index references. Use `detail: "summary"` for counts
and ranges, or retained `inspect_analysis_view` Rust package/edge pages for
subsequent filters. Keep the complete parent Evidence accessible; CLI compact
delivery requires `--evidence_output`.
See the
[Rust binary guide](https://github.com/morluto/rea/blob/main/docs/rust-binaries.md).
- PE Authenticode certificate table: `inspect_pe_certificates` with the explicit
local PE32/PE32+ file. This built-in target-free inspection requires no
Windows SDK. Preserve WIN_CERTIFICATE ranges, types, payload SHA-256, and
PKCS#7 signer/X.509 identity fields, including `subject_key_identifier_hex`
when the signer uses that form. A missing security directory is observed
absence of that table, not proof the image is unsigned in every catalog sense.
Signatures are not verified. See the
[PE certificate guide](https://github.com/morluto/rea/blob/main/docs/pe-certificates.md).
- PE import and delay-load tables: `inspect_pe_imports` with the explicit
local PE32/PE32+ file. This built-in target-free inspection requires no
Windows SDK. Preserve DLL names, named and ordinal functions, ILT/IAT RVAs,
delay-load descriptors, and the pefile import hash of the standard import
table. A missing import directory is observed absence of that table, not
proof the image never imported APIs through another table. Bound snapshots
and loaded IAT contents remain unobserved. See the
[PE import guide](https://github.com/morluto/rea/blob/main/docs/pe-imports.md).
- Windows Shell Link shortcuts: `inspect_windows_shortcut` with the explicit
local `.lnk` file. This built-in target-free inspection requires no Windows
SDK or COM. Preserve header flags and FILETIMEs, IDList items, LinkInfo
volume and network names, StringData arguments and icon location, ExtraData
signatures, and Tracker droid facts labelled derived. Unknown IDList items
and ExtraData signatures stay raw with SHA-256. The target path is not
resolved or executed. See the
[Windows shortcut guide](https://github.com/morluto/rea/blob/main/docs/guides/native/windows-shortcut.md).
- PE TLS callbacks and load configuration: `inspect_pe_load_config` with the
explicit local PE32/PE32+ file. This built-in target-free inspection requires
no Windows SDK. Preserve callback VAs as strings with their RVA, file offset
and section, and the size-covered security cookie, SafeSEH, CFG,
EH-continuation and XFG fields. Guard-flag mitigation claims are inferred
linker declarations, not proof of enforcement. A missing directory is observed
absence of that declaration. See the
[PE load configuration guide](https://github.com/morluto/rea/blob/main/docs/pe-load-config.md).
- ELF, PE, and Mach-O hardening: `inspect_binary_hardening` with the explicit
local file. This built-in target-free inspection requires no pwntools or other
host tool. One image per Mach-O slice. Each property is `enabled`, `disabled`,
`partial`, `unknown`, or `not_applicable`, with the header field or import
behind it. A missing stack-protector or fortify import is `unknown`, not
proof the protection was omitted. Authenticode and Mach-O code signatures
report presence, not validity. Hardened runtime stays unknown and names
`inspect_signature`. The image is not executed. See the
[binary hardening guide](https://github.com/morluto/rea/blob/main/docs/binary-hardening.md).
- ELF note tables: `inspect_elf_notes` with the explicit local ELF32/ELF64
file. This built-in target-free inspection requires no disassembler. Preserve
PT_NOTE and SHT_NOTE ranges, owners, types, descriptor SHA-256, and GNU
build-id or ABI-tag fields. A missing table is observed absence of those
declarations, not proof the image never carried notes. See the
[ELF notes guide](https://github.com/morluto/rea/blob/main/docs/elf-notes.md).
- Code that runs before `main` or at exit in ELF or Mach-O:
`inspect_native_initializers` with the explicit local file (and
`architecture` for a universal Mach-O). This built-in inspection requires no
disassembler. Preserve slot and target addresses, the `resolution` of each
entry, and its limitations. An `unresolved` slot is not a zero pointer, and a
missing table is not proof that no constructor exists. See the
[initializer guide](https://github.com/morluto/rea/blob/main/docs/guides/native/native-initializers.md).
- ELF unwind function ranges: `inspect_elf_unwind_ranges` with the explicit
local ELF32/ELF64 file. This built-in target-free inspection requires no
disassembler. Preserve FDE start, end, size, containing section, exact
symbol name, CIE index, LSDA, and personality. Page with `offset` and
`limit`. A missing `.eh_frame` is observed absence of that declaration,
not proof the image has no functions. `.debug_frame` is reported present
or absent and is not decoded. CFA instructions are counted and not
evaluated. See the
[ELF unwind ranges guide](https://github.com/morluto/rea/blob/main/docs/guides/native/elf-unwind-ranges.md).
- Apple `.ips` crash reports: `inspect_apple_ips` with the explicit local
file. This built-in target-free inspection requires no Xcode, atos, or
pwntools. Preserve first-line metadata, `bug_type` as a string, process and
exception facts, lossless integer registers, usedImages UUIDs, and
documented classification matches. Only `bug_type` `"309"` yields a crash
body; other types, including `"288"` stackshots, return no crash result.
Select an optional `join_path` for UUID-and-architecture matching to one
local Mach-O or dSYM; preserve slice identity and unjoined reasons. Joined
static addresses do not resolve function names or dSYM symbols. See the
[Apple IPS guide](https://github.com/morluto/rea/blob/main/docs/apple-ips.md).
- Linux kernel module metadata: `inspect_kernel_module` with the explicit local
ELF32/ELF64 relocatable file. This built-in target-free inspection requires
no Linux host or `modinfo`. Preserve `.modinfo` key order and duplicate keys,
`__versions` CRC hex, section file ranges, and the appended signature
trailer's PKCS#7 SHA-256. A missing section is observed absence when section
names are known, not proof the module never carried that metadata.
`struct module` is not decoded. The signature is not validated. gzip, xz, and
zstd containers are unsupported; decompress into a separate file with an
external codec utility, then inspect the decompressed `ET_REL` image. See the
[kernel module guide](https://github.com/morluto/rea/blob/main/docs/kernel-modules.md).
- Linux kernel version and build config (`CONFIG_*` options such as
`CONFIG_SECURITY_SELINUX`): `inspect_kernel_build_config` with the explicit
local `vmlinux`, raw `Image`, x86 `bzImage`, or `configs.ko`. This built-in
target-free inspection requires no Linux host or `extract-ikconfig`. Preserve
the banner version, config SHA-256, option count, and value kinds; use `name`
for one option. An `IKCFG_ST` string is a config only with a valid gzip member
and `IKCFG_ED`. An absent config may mean the kernel was built without
`CONFIG_IKCONFIG`. Non-gzip payloads are unsupported; decompress externally,
then inspect the result. See the
[kernel build config guide](https://github.com/morluto/rea/blob/main/docs/guides/native/kernel-build-config.md).
- CPython 3.7–3.13 `.pyc` header, nested marshal code objects and bytecode
identity: `inspect_python_pyc` with the explicit local file. This built-in
target-free inspection requires no Python installation. Preserve magic,
PEP 552 hash or mtime/size, nested names, and bytecode SHA-256. Bytecode is
not decompiled, and a decoded header does not prove the matching `.py` is
present. See the
[CPython pyc guide](https://github.com/morluto/rea/blob/main/docs/python-pyc.md).
- Erlang BEAM IFF container: `inspect_erlang_beam` with the explicit local
`.beam` file. This built-in target-free inspection requires no Erlang
installation. Preserve chunk names, atom order, export/import/lambda tables,
and the Code header. For selected facts, prefer `detail: "summary"` plus a
`beam` view (collection, names/indices, fields and paging), retaining the full
Evidence. CLI summary requires `--evidence_output`. Missing table counts stay
null; decoded empty tables count zero. Bytecode is not executed. See the
[Erlang BEAM guide](https://github.com/morluto/rea/blob/main/docs/erlang-beam.md).
- Explicit local SQLite database snapshot: `inspect_sqlite_database`. Inspect
with Node.js 24.x >=24.15 or 26+ providing native SQLite limits. Read schema
first (compact inventory by default), or select an exact ordinary table
and `columns` when rows are needed. Defaults are selected-table schema and
20 rows; request `schema_scope: "database"` for the complete stored schema. Preserve both database and existing sibling WAL identity; row samples
report truncation and have unspecified order. Use a quiescent snapshot rather
than assuming a live database/WAL pair was copied atomically. See the
[SQLite guide](https://github.com/morluto/rea/blob/main/docs/guides/data/sqlite-databases.md).
- Explicit Linux ELF file for offline layout, symbols, relocations or static
mitigation evidence: `inspect_binary_layout`. This target-free operation uses
caller-supplied pwntools without opening a disassembler database. Preserve its
raw locations and inference/coverage limits; see the
[offline binary guide](https://github.com/morluto/rea/blob/main/docs/guides/native/binary-diagnostics.md).
- Supplied Linux x86-64 ELF core: `inspect_recorded_crash`. Read every recorded
thread, raw note and source-bound register without opening a live target.
Historical PIDs do not select live processes. Request `include_debugger_context`
only when core-only mapping candidates help; see the
[recorded crash guide](https://github.com/morluto/rea/blob/main/docs/guides/native/recorded-crashes.md).
- PE resource type/name/language trees and static icon group references:
`inspect_pe_resources` with an explicit path. This portable operation requires
no active target or engine; opaque payloads retain file ranges and SHA-256.
- PE export directories and ELF dynamic-symbol exports: `inspect_native_exports`
with an explicit path; portable, no active target or engine.
- Native executable, library, or analysis database: `open_binary`, then
use focused analysis tools directly; call `binary_overview` when metadata or
inventory context is useful and available from the selected provider.
For an existing IDA MCP configuration, use `open_binary` with the original
input binary and `provider_id: "ida"`; do not pass an `.idb` or `.i64` database.
The legacy attached profile binds the already-open GUI input by SHA-256 and
leaves its database open. The modern headless profile creates and closes an
owned private database without saving. Installation and registration reuse
[mrexodia/ida-pro-mcp](https://github.com/mrexodia/ida-pro-mcp); see the
[IDA provider guide](https://github.com/morluto/rea/blob/main/docs/providers/ida-provider.md).
For migration to the separate native IDA MCP plugin, follow the guide's
[legacy attached migration](https://github.com/morluto/rea/blob/main/docs/providers/ida-provider.md#migrate-a-legacy-attached-registration).
Replace the upstream registration explicitly; modern mrexodia tools are not the
native profile. Native attachment preserves the database but does not provide
direct callers; check session availability and the guide's verification limits.
Proceed directly to `analyze_function`, pseudocode, or function/string searches
after opening; IDA does not supply `binary_overview`. Consult session availability
when composing broader workflows. Modern direct callers and unsupported dossier
facets remain unknown; live IDA results are not replayed from snapshots.
With `REA_KUNA_COMMAND` configured, `provider_id: "kuna"` answers procedure
lists, lookups, pseudocode, assembly and `analyze_function` from Kuna's cached
whole-program export. Callers/callees use a separate body-free graph shared by
the session; `xrefs` returns exact-address incoming sites and filters forwarding
aliases. These queries and inventory lookups do not trigger a C export. Indirect
targets remain unknown and can make relationship coverage partial. Reference
caches expire on close; a fresh CLI process repeats the native reference walk.
Typed references, CFG, raw-memory tools and annotations remain unsupported. A cold first
answer can come from a single-function run (its limitations say so); assembly
waits for the export. For pseudocode and assembly, `raw_result.source` names the
cached `.c`/`.asm` file and byte span, which you may search directly instead of
calling per function. See the
[Kuna provider guide](https://github.com/morluto/rea/blob/main/docs/kuna-provider.md).
If the user says only "my app" without a name, path, or target already selected
for this task, ask which app before searching for artifacts. Paths in unrelated
saved reports and nearby test fixtures do not select the user's target. Resolve
an actual human-readable app name to one clear installed artifact when possible;
ask when matches are ambiguous. Never choose an example app on the user's behalf.
In a target-free session, use `open_binary` to bind any archive/package or
native target whose analysis tool operates on the active target. Do not call an
unadvertised tool; inspect `binary_session` with `{}` and its
`result.tool_availability` for availability reasons and remediation. Expand each
group's `names` into entries, inheriting the inventory-level client requirements
unless the group overrides them. The grouped name union is the complete tool
inventory; use `tools/list` for canonical tool schemas. The default summary keeps
each provider candidate's availability and target-support status, code and
reason; `omitted_fields` lists what it leaves out. Request `{"detail": "complete"}`
only when you need an unavailable candidate's remediation, provider
diagnostics, candidate capability sets, or tool surface, effect and annotation
flags; in those closed flag vocabularies, listed
flags are true and omitted known flags are false. Provider candidate facts
describe possible providers, while top-level session capabilities describe the
current route and `analysis_provider_binding` names the selected provider. Do
not treat those scopes as interchangeable.
For editable Ghidra analysis that must survive a later agent process, select
the [owned Program checkpoint workflow](references/native-and-artifacts.md#continue-editable-ghidra-analysis-across-processes)
before making metadata edits. It is separate from Evidence/Unknown persistence.
For a Ghidra startup timeout on a large native target, follow
[deadline recovery](references/native-and-artifacts.md#ghidra-startup-deadline-recovery)
before abandoning deep analysis or repeating the same cold import. The default
330 seconds can be changed for new startup attempts through
`update_analysis_settings` using `ghidra.startup_timeout_ms`. Changing the
server's launch environment instead requires a new REA process.
## Work summary-first
Start with inline Evidence and graph context. For unknown/large JavaScript or ELF
layout, select advertised `detail: "summary"`; MCP `analyze_function` already
defaults to a summary. Small complete results can cost less than extra pages.
Make a focused follow-up only for a specific unanswered question. Reuse matching
session state and Evidence instead of repeating analysis; recheck state when a
lifecycle change makes it useful.
Start with the default result and use its inline Evidence and graph context.
For a JavaScript application or ELF layout whose size is unknown or large,
call `analyze_javascript_application` or `inspect_binary_layout` with
`"detail": "summary"`, then inspect the retained `parent_evidence_id` with
`inspect_analysis_view`. Over MCP, `analyze_function` is summary-first without
a flag: its default reply is the summary, and the complete dossier stays
retained under `parent_evidence_id` for facet paging. Pass that ID as a
`retained-evidence` reference to `compare_functions`, or inside
`match_native_functions` `left_observations`/`right_observations` or
`build_call_path.functions`; request `"detail": "complete"` only when the
dossier must travel inline.
Use the same exact-reference form in other advertised logical Evidence inputs,
including optional sources, arrays and nested sources across domains. Each
source can independently be inline or retained; preserve its source identity,
coverage, partial facts and unknowns. A summary or inspection page has its own
Evidence ID: use `normalized_result.parent_evidence_id` when the consumer needs
the complete source. For portable CLI follow-ups, commands advertising
`--evidence-bundle` resolve references from that selected exported bundle.
See [Evidence composition](references/evidence-workflows.md) for scope and
recovery; file selectors are supported only where explicitly advertised.
Page module identities and select the returned `node_id` before requesting an
item; a single file path can represent several graph roles. Do not repeat an
identical analysis or rejected call without a changed input or resolved blocker.
Recheck session state when a lifecycle change makes that status useful.
Make a focused follow-up only when the
returned result leaves a specific question unanswered.
Complete source Evidence stays accessible through advertised retained references.
A summary/page has its own Evidence ID; use `normalized_result.parent_evidence_id`
when a consumer needs the complete source. Pass `{kind: "retained-evidence",
evidence_id}` in inputs advertising that form; do not resend complete records.
Sources may independently be inline or retained. Read the Evidence guide for
exact filters, bundle-based CLI reuse, comparison roles and missing-source recovery.
If MCP reports `resource_constraint` with `details.resource: "transport"`, call
`inspect_analysis_view` with its reported same-session Evidence reference for a
summary, one section/module, or a stable page. Use `trace_application_feature`
once a seed is known, or call `export_evidence_bundle` to write the complete
session to a selected path. The analysis remains complete in the ledger; avoid
repeating it merely to request the same oversized response. Complete CLI JSON
output also streams without a single MCP frame.
For REA transport refusal or client-truncated output, inspect a smaller retained
view or export complete Evidence. Do not parse a preview as canonical JSON or
repeat the producer. Never invent an Evidence ID; if unknown, find it with
`get_evidence_bundle` summary filters. Retention is bounded and session-local;
it does not establish automatic persistence or complete source coverage.
When the retained parent ID is unknown, use `get_evidence_bundle` with
`detail: "summary"` and exact filters for operation, target SHA-256,
analysis-profile digest or canonical procedure address. Select its returned
Evidence ID for the next view instead of requesting the complete bundle. When
that record has no supported view (for example an annotation), read it with
`detail: "records"` and an `evidence_id` filter; `missing_evidence_links`
names linked sources this session does not retain.
`retention: "complete-record"` means the whole record is stored; native
value-flow truncation and unknown metadata remain separate. For an exported
portable bundle, use `rea inspect-evidence-bundle` with JSON containing `path`,
`detail: "summary"` and the same `filters`. Reading that file still requires
validating its full contents within the local memory budget.
For broad investigations, read the Evidence guide's staged workflow. Track the
questions, source boundaries and unresolved gaps; do not claim completion while
required questions remain open. Inspect packaged resources alongside code when
relevant, and use runtime evidence only within the requested scope.
A client can also truncate a successful tool result without a REA transport
error. Treat that preview as incomplete; use the retained parent ID for smaller
views or export when the full record is needed. Do not parse the preview as
canonical JSON or repeat the producer. For JavaScript, read the
[module-view and trace recipes](references/javascript-applications.md) before
drilling into a large graph; a broad bidirectional trace can expand unrelated
containment relationships even after summary analysis.
Every conclusion must distinguish observations, inferences, and unknowns. Cite
Evidence IDs, preserve limitations and incomplete coverage, and never imply
that static analysis observed execution. Runtime requests execute the declared
target and lifecycle; do not broaden the target or action beyond those fields.
Within the user's requested investigation, call available analysis tools
directly. REA does not require permission grants or per-call approval flags.
The agent client can independently require approval. Follow the declared request
scope and the host's actual access requirements; use the recovery guide for an
observed client-policy rejection.
## Plan broader investigations
For requests that span multiple features or subsystems, use a staged workflow:
1. Turn the request into a checklist of questions and the evidence each answer
needs. Resolve target identity and constraints from the conversation and
workspace before asking for information again.
2. Inspect the current REA session, artifact identity, saved analysis database,
bookmarks, and prior evidence. Reuse matching state; do not open duplicate
sessions or repeat identical analysis.
3. Start with the smallest useful overview or inventory. Follow each question
from its entry point through relevant data and state changes to its result.
Batch related operations around a specific hypothesis, then expand only when
the returned evidence leaves a concrete gap.
4. Inspect relevant packaged resources and configuration alongside code when
they affect the question. Use format-aware inventory and parsers; do not
infer behavior from filenames, strings, or layout alone.
5. Corroborate a conclusion with the evidence type it requires. Use runtime
observation only when static evidence cannot answer the question and the
required host runtime and OS access are available. For JavaScript behavior,
run probes against the actual app through the available browser, Electron,
or process capture workflow.
6. Decompose work into independent questions. When parallel workers are
available and the questions do not depend on one another, assign distinct
scopes, point workers to existing evidence, and ask them to return sources,
conclusions, and unresolved gaps. Otherwise, work sequentially.
7. Keep a concise finding ledger linking each conclusion to Evidence IDs,
confidence/evidence type, search boundary, and remaining unknowns. Update
the shared index or investigation report so later passes can reuse results.
Before finishing, revisit the original checklist. Mark each question as
answered, partially answered, or unresolved based on its evidence; keep
bounded negative searches bounded, and do not describe a broad investigation
as complete while required questions remain open.
## Read only the relevant guide
- Native binaries, managed assemblies, archives, and extraction:
[references/native-and-artifacts.md](references/native-and-artifacts.md)
- ASARs, extracted JavaScript, feature tracing, and version comparison:
[references/javascript-applications.md](references/javascript-applications.md)
- Android APK declarations, classes, methods, and static references:
[references/android-applications.md](references/android-applications.md)
- Native instrumentation, debugger and process capture; browser/Electron
observation; static/runtime comparison:
[references/runtime-observation.md](references/runtime-observation.md)
- Evidence paging, comparisons, residual unknowns, and verification:
[references/evidence-workflows.md](references/evidence-workflows.md)
- Agent environments (AgentEnv bundles) of a web application, recorded from
a live instance or a HAR:
[references/environments.md](references/environments.md)
- Tasks for a desktop application's exported environment:
[references/environments.md](references/environments.md#tasks-for-a-desktop-application-environment)
- A desktop app environment from a request, built and witnessed on build
computers: [references/desktop-build.md](references/desktop-build.md)
## Finish the task
Explain findings in plain language and tie them to returned Evidence. For builds,
use normal coding tools and separate observed behavior from design choices.
Close an opened native/resource session with `close_binary` when no longer needed.
Evidence references and Unknown history remain on the same MCP connection.
End with `reset_investigation`; use `save_path` to checkpoint the complete bundle
before clearing. Import that bundle, including its source closure, to restore
original references and Unknown history in a new process. Editable Ghidra Program
checkpoints are separate; read that workflow before edits needing persistence.
Explain findings in plain language and tie them to returned evidence. When the
user asks to build something, use normal coding tools and separate observed
behavior from design choices. Close an opened native session with
`close_binary` when its engine is no longer needed. Evidence references and Unknown
history remain in memory on the same MCP connection. End the investigation with
`reset_investigation`; supply `save_path` to checkpoint the complete bundle before
clearing. Import the saved bundle to restore original references and Unknown
history in a new process. Retention is bounded and is not automatic persistence.
@@ -1,23 +1,11 @@
# Connection and task recovery
Read only the sections needed for the selected task.
- [Choose the remedy from the observed failure](#choose-the-remedy-from-the-observed-failure)
- [Distinguish client and server deadlines](#distinguish-client-and-server-deadlines)
- [Recovery ladder](#recovery-ladder)
- [REA installed as a client plugin](#rea-installed-as-a-client-plugin)
- [Repair registration when needed](#repair-registration-when-needed)
- [Codex tool approval is separate from REA readiness](#codex-tool-approval-is-separate-from-rea-readiness)
- [Repair an editable REA limit](#repair-an-editable-rea-limit)
- [Continue through an installed CLI](#continue-through-an-installed-cli)
Read this guide only for an observed connection, command, approval or provider
blocker. Keep the user's target and question in view. Explain what prevented the
operation and the next useful step; after repair, continue the investigation.
Commands below use an installed `rea`. If none is available, use the package
invocation under [Continue through an installed CLI](#continue-through-an-installed-cli)
when acquiring REA is authorized. Follow the connected
invocation in `SKILL.md` when acquiring REA is authorized. Follow the connected
server's schemas and the installed CLI's help rather than assuming repository
main and an older release expose the same arguments. Tool-name prefixes belong
to the client: find the canonical REA operation in its actual tool listing and
@@ -234,25 +222,6 @@ not edit host deadlines, response buffers, registrations or fixed limits.
## Continue through an installed CLI
A concrete CLI fallback for an operator-supplied JavaScript tree or ASAR is:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
No MCP registration or native engine is required. Read the returned Evidence,
graph, limitations, and unknowns with the same care as an MCP result; the CLI
returns the Evidence record directly. This fallback does not establish that
MCP is configured. Native CLI tasks still require their selected engine.
Prefer an existing `rea` CLI. When none is installed and REA package acquisition
is within the authorized scope, use `npx -y rea-agents@latest` in its place from a
trusted directory outside the target project, with absolute target paths. Managed
setup binds the current installation directly; retain its runtime and package
cache entry, or restore that installation before repairing its registration.
For setup-owned registration, configuration repair starts with a read-only setup plan; show paths, backups and
skill replacement before applying authorized changes. Preserve existing approval
and target-execution boundaries.
Check the command environment only if the launcher fails:
```bash
@@ -1,13 +1,5 @@
# Agent environments from a live application
Read only the sections needed for the selected task.
- [Before recording](#before-recording)
- [Record a session an environment can learn writes from](#record-a-session-an-environment-can-learn-writes-from)
- [Check coverage](#check-coverage)
- [Export and hand off](#export-and-hand-off)
- [Tasks for a desktop application environment](#tasks-for-a-desktop-application-environment)
Use this when the user asks for an agent environment (an AgentEnv bundle) of a
web application: a folder `agent-env` deploys, whose MCP tools answer from what
one recording of the application observed and serve the writes that recording
@@ -1,25 +1,7 @@
# Evidence, comparison, and verification workflows
Read only the sections needed for the selected task.
- [Read and compose source Evidence](#read-and-compose-source-evidence)
- [Inventory and decompilation views](#inventory-and-decompilation-views)
- [Unknowns and comparisons](#unknowns-and-comparisons)
- [Process captures and environment views](#process-captures-and-environment-views)
- [Native export table pages](#native-export-table-pages)
- [Recover oversized delivery and find retained Evidence](#recover-oversized-delivery-and-find-retained-evidence)
- [Plan broader investigations](#plan-broader-investigations)
## Read and compose source Evidence
Evidence-producing MCP tools return Evidence directly in `structuredContent`.
CLI complete JSON returns Evidence directly. CLI summary delivery through
`--evidence_output` returns `{summary, evidence_bundle}`: `summary` is the view's
Evidence, and `evidence_bundle` is the export receipt with its path and byte count.
Read `summary.normalized_result.parent_evidence_id` for the complete source ID;
pass the receipt's path to `--evidence-bundle` on a later supported CLI command.
Advertised summary delivery retains the complete source separately;
complete inline delivery is available where the connected contract says so. Read `normalized_result` for operation data,
Evidence-producing tools return the complete Evidence record directly in MCP
`structuredContent` and CLI JSON. Read `normalized_result` for operation data,
and cite `evidence_id` when another tool or your explanation needs a stable
reference. Preserve authority, limitations, coverage, and residual unknowns. If a result
is incomplete or paginated, continue only when the remaining data matters to
@@ -67,8 +49,6 @@ An input bundle grants no implicit file lookup: `evidence-file` and historical
graph file selectors are accepted only by contracts advertising them. See the
[CLI reuse guide](https://github.com/morluto/rea/blob/main/docs/getting-started/cli.md#import-export-and-compare-evidence).
## Inventory and decompilation views
Completed `inspect_artifact` inventory also supports a summary and
`view: {kind: "inventory", collection: "occurrences", logical_paths: ["package.json", "main.js"]}`.
Omit `logical_paths` to page all available occurrences; `[]` selects none.
@@ -101,8 +81,6 @@ exact `where.indices`, `addresses`, `procedure_addresses` or `statuses`. Add
`collection: "pseudocode"` with one row's `index` to page a large body. Batch
limitations aggregate every item, so do not attribute them to one successful row.
## Unknowns and comparisons
Use `record_unknown` to track unresolved questions in the user's investigation
and name the authority or environment still required. Supply supporting and
contradicting evidence IDs. It updates the session registry directly.
@@ -133,8 +111,6 @@ or absence:
- `verify_reconstruction` evaluates a finite typed specification. A pass covers
only declared comparable claims, not global equivalence.
## Process captures and environment views
Process captures are opt-in behavioral evidence, not a security sandbox. V3
captures cannot be upgraded to V4; rerun the original scenario. Distinguish root
exit from descendant settlement and require freshness when the task needs it.
@@ -188,8 +164,6 @@ catalog and state size checks. `rea environment-build <capture.har> <dir>`
inspects, projects and exports in one step and prints the same coverage, or
`null` when the bundle serves replay.
## Native export table pages
For a PE/ELF export question, `inspect_native_exports` accepts `detail: "summary"`
and retains the complete table. Query `inspect_analysis_view` with
`view: {kind: "native-exports", collection: "exports", names: ["selected_name"],
@@ -200,69 +174,3 @@ coverage describes selected retained rows; `summary.coverage` or `exports.covera
still describes the source table, so an empty partial page does not prove absence.
CLI `inspect-native-exports --detail summary --evidence_output PATH` saves the
complete bundle for later `inspect-analysis-view --evidence-bundle PATH` requests.
## Recover oversized delivery and find retained Evidence
If MCP reports `resource_constraint` with `details.resource: "transport"`, call
`inspect_analysis_view` with its reported same-session Evidence reference for a
summary, one section/module, or a stable page. Use `trace_application_feature`
once a seed is known, or call `export_evidence_bundle` to write the complete
session to a selected path. The analysis remains complete in the ledger; avoid
repeating it merely to request the same oversized response. Complete CLI JSON
output also streams without a single MCP frame.
When the retained parent ID is unknown, use `get_evidence_bundle` with
`detail: "summary"` and exact filters for operation, target SHA-256,
analysis-profile digest or canonical procedure address. Select its returned
Evidence ID for the next view instead of requesting the complete bundle. When
that record has no supported view (for example an annotation), read it with
`detail: "records"` and an `evidence_id` filter; `missing_evidence_links`
names linked sources this session does not retain.
`retention: "complete-record"` means the whole record is stored; native
value-flow truncation and unknown metadata remain separate. For an exported
portable bundle, use `rea inspect-evidence-bundle` with JSON containing `path`,
`detail: "summary"` and the same `filters`. Reading that file still requires
validating its full contents within the local memory budget.
A client can also truncate a successful tool result without a REA transport
error. Treat that preview as incomplete; use the retained parent ID for smaller
views or export when the full record is needed. Do not parse the preview as
canonical JSON or repeat the producer. For JavaScript, read the
[module-view and trace recipes](javascript-applications.md) before
drilling into a large graph; a broad bidirectional trace can expand unrelated
containment relationships even after summary analysis.
## Plan broader investigations
For requests that span multiple features or subsystems, use a staged workflow:
1. Turn the request into a checklist of questions and the evidence each answer
needs. Resolve target identity and constraints from the conversation and
workspace before asking for information again.
2. Inspect the current REA session, artifact identity, saved analysis database,
bookmarks, and prior evidence. Reuse matching state; do not open duplicate
sessions or repeat identical analysis.
3. Start with the smallest useful overview or inventory. Follow each question
from its entry point through relevant data and state changes to its result.
Batch related operations around a specific hypothesis, then expand only when
the returned evidence leaves a concrete gap.
4. Inspect relevant packaged resources and configuration alongside code when
they affect the question. Use format-aware inventory and parsers; do not
infer behavior from filenames, strings, or layout alone.
5. Corroborate a conclusion with the evidence type it requires. Use runtime
observation only when static evidence cannot answer the question and the
required host runtime and OS access are available. For JavaScript behavior,
run probes against the actual app through the available browser, Electron,
or process capture workflow.
6. Decompose work into independent questions. When parallel workers are
available and the questions do not depend on one another, assign distinct
scopes, point workers to existing evidence, and ask them to return sources,
conclusions, and unresolved gaps. Otherwise, work sequentially.
7. Keep a concise finding ledger linking each conclusion to Evidence IDs,
confidence/evidence type, search boundary, and remaining unknowns. Update
the shared index or investigation report so later passes can reuse results.
Before finishing, revisit the original checklist. Mark each question as
answered, partially answered, or unresolved based on its evidence; keep
bounded negative searches bounded, and do not describe a broad investigation
as complete while required questions remain open.
@@ -1,76 +0,0 @@
# Firmware and raw images
## Firmware regions and extraction
Use `inspect_firmware_regions` when advertised. Use `extract_firmware` when
extraction is requested, with a caller-selected new absolute output directory.
These tools use caller-supplied Binwalk/Unblob on Linux; see the
[firmware guide](https://github.com/morluto/rea/blob/main/docs/guides/firmware/firmware-analysis.md).
## Headerless raw images and firmware containers
For other headerless images (embedded firmware without ELF/Mach-O/PE/MZ
headers), open with `format: "raw-binary"`, `architecture` (`x86`, `x86_64`,
`arm`, `arm64`, `6502`, or `m68k`), and `entry_point` (`0x` hex). File offset 0 is mapped at
that address. Hopper and IDA refuse this interpretation; Ghidra uses
`BinaryLoader` with a generic language: big-endian for `m68k`, little-endian
otherwise. Big-endian ARM, Thumb-only, and Cortex-M variants are unsupported.
`6502` selects NMOS 6502; the entry and complete file mapping must fit within
`0x0000..0xffff`. 65C02, 65816, cartridge containers, bank switching, and mapper
semantics are unsupported. `m68k` requires an even entry and decodes the 68040
superset of the 68000 family; Amiga/Atari containers and vector-table entry
discovery are unsupported. Windows Ghidra P0 admits raw images only for `6502`. See [raw binary guide](https://github.com/morluto/rea/blob/main/docs/providers/raw-binary.md).
Intel HEX, S-record, UF2 and DfuSe files are containers, not raw images:
`inspect_artifact` lists address regions (UF2 per familyID, DfuSe per element
with its alternate setting), and `extract_artifact` writes each region. Open an
extracted region as `raw-binary` with its start address as `entry_point`. UF2
numbering gaps leave coverage unknown; a DfuSe suffix CRC mismatch follows
`integrity_policy`. See [UF2 and DfuSe](https://github.com/morluto/rea/blob/main/docs/uf2-dfuse.md).
## ESP images
For an ESP32-family app or bootloader image, `open_binary` then
`inspect_artifact` with `detail: "summary"`. `root_format_metadata` holds the
observed `header` and `descriptor` (its `kind` is the app or bootloader role)
and the derived `chip`, `checksum`, `appended_sha256` and
`undescribed_trailing` ranges. Segment rows (`segment-NN.bin`) carry
`load_addr`, `classification` (`mapped`, `loaded`, `not-loaded` or `unknown`,
per ESP-IDF `should_map`/`should_load`; RAM ranges are not checked) and
`source_location`; select them with `inspect_analysis_view` and
`logical_paths`. Signature blocks are `unverified`; `public_key_sha256`
matches eFuse key digests.
Unknown `source_coverage` means truncation or undescribed trailing bytes, such
as the flash segments of a `--ram-only-header` image; do not infer absence
from it. Erased flash is described. Integrity mismatches are facts, not
refusals. ESP8266 images are refused, and flash-encrypted images are not
recognized. Segments are not disassembled, and RISC-V or Xtensa segments
cannot be opened as raw-binary. See
[ESP images](https://github.com/morluto/rea/blob/main/docs/esp-image.md).
## ESP flash images
A whole-flash dump opens as `esp-flash-image`. `root_format_metadata` holds
`partition_table` (its `method`: `default`, `caller` or a derived
`scan`), `bootloader` and `apps` ESP image facts, the `otadata` records,
`partitions_outside_dump` and `boot_selection`. Every table has a
`boot_selection`: its `basis` names the bootloader's branch (`no-otadata`
without otadata, `none` when the bootloader rejects the table, `unknown`
when a record is missing), and it lists its `assumptions`;
`running_partition` is always `unknown`. Each image names its entry
`path`. With `detail: "summary"` the root kind is `esp-flash-image-summary`
and each image is a compact `summary` (chip, entry point, segment count,
classified load ranges, signature block count, descriptor identity,
integrity); complete detail keeps every segment. Partition rows
(`partitions/<label>.bin`) carry the declared `type`, `subtype`, names,
`flash_address`, `size` and flags; `encrypted` is a storage flag, not
proof of ciphertext. `written_offset`/`written_end` bound the bytes that
are not erased: an app slot whose facts say `erased` can still hold an older
image, and a limitation says so. For a dump read from another flash address, call
`open_binary` with `format: "esp-flash-image"` and `flash_offset`; an
`esp-image` whose `flash_dump` is set names that offset in a next probe; a
relocated table can be named with `partition_table_offset`. No table found
means unknown, often flash encryption. Extraction writes `nvs_keys` as
ordinary data. See
[ESP flash images](https://github.com/morluto/rea/blob/main/docs/esp-flash-image.md).
@@ -1,449 +0,0 @@
# Offline format and metadata inspection
Read only the sections needed for the selected task.
- [Release exposure](#release-exposure)
- [Go build metadata](#go-build-metadata)
- [Model files](#model-files)
- [IL2CPP metadata](#il2cpp-metadata)
- [Rust dependency metadata](#rust-dependency-metadata)
- [PE Authenticode certificate tables](#pe-authenticode-certificate-tables)
- [PE import and delay-load tables](#pe-import-and-delay-load-tables)
- [Windows Shell Link shortcuts](#windows-shell-link-shortcuts)
- [PE TLS callbacks and load configuration](#pe-tls-callbacks-and-load-configuration)
- [Binary hardening](#binary-hardening)
- [ELF note tables](#elf-note-tables)
- [ELF and Mach-O initializer functions](#elf-and-mach-o-initializer-functions)
- [ELF unwind function ranges](#elf-unwind-function-ranges)
- [Apple IPS crash reports](#apple-ips-crash-reports)
- [Linux kernel modules](#linux-kernel-modules)
- [Linux kernel build config](#linux-kernel-build-config)
- [CPython pyc files](#cpython-pyc-files)
- [Erlang BEAM files](#erlang-beam-files)
- [SQLite snapshots](#sqlite-snapshots)
- [Recorded Linux crashes](#recorded-linux-crashes)
- [Offline ELF layout](#offline-elf-layout)
- [Offline WASM](#offline-wasm)
- [Offline APK signatures](#offline-apk-signatures)
- [Offline PE resource inventory](#offline-pe-resource-inventory)
- [Offline PE and ELF export tables](#offline-pe-and-elf-export-tables)
- [EVM bytecode](#evm-bytecode)
- [Embedded GPU code objects](#embedded-gpu-code-objects)
## Release exposure
Use `audit_release_exposure` (`rea audit-release-exposure PATH`) when the question is what static analysis of a shipped `.app`, install directory, ASAR, or executable recovers. Each check has a stable id, a status, the observation behind it, and fixed remediation. Unknown and unassessed checks stay in the report. The report does not establish that the application is secure.
`text.credential_markers` reports a rule id and location only. Matched bytes, previews, lengths, and hashes of matched bytes are omitted.
`native.hardening`, `macho.hardened_runtime`, and `android.manifest` are not assessed here. Their next probes are `inspect_binary_hardening`, `inspect_signature`, and `inspect_android_package`.
See [release exposure](https://github.com/morluto/rea/blob/main/docs/release-exposure.md).
## Go build metadata
Use `inspect_go_binary` for an explicit local ELF, PE or Mach-O (universal
files need `architecture`) when the
question concerns the embedded compiler version, module/dependency versions,
local replacements, build settings, or static Go string literals packed without
NUL terminators. Pass `include_string_literals: true`
(`--include-string-literals`, optional `max_string_literals` /
`--max-string-literals`) to recover bounded inferred word-aligned `(ptr, len)`
UTF-8 string slices from read-only data and page them through `go`
`string_literals` views. No `open_binary`, Go installation or
target execution is required. The CLI equivalent is `inspect-go-binary PATH`.
Preserve artifact SHA-256, original file ranges, framed module bytes and every
unparsed module-text line. A missing record is an unknown, not proof of a
non-Go binary; observed metadata does not authenticate its claims, and static
`(ptr, len)` string recovery excludes compiler-inlined stack/immediate strings
without proving runtime reachability. Function,
source and type recovery remain a separate native-analysis question.
See the [Go binary guide](https://github.com/morluto/rea/blob/main/docs/guides/native/go-binaries.md).
## Model files
Optional `view` selects one page in the initial call. Batch metadata keys or
tensor names and select only needed fields; do not infer model behavior from
declared architecture or tensor types.
Use `inspect_model_artifact` (`inspect-model-artifact PATH`) for local GGUF v2/v3
and safetensors declarations without a model runtime. Header-only mode does not
read weights or establish full-file identity. `hash: "full"` explicitly streams
all bytes. Retained `model-artifact` views page `metadata`, `tensors` and derived
`types`, with exact batch filters and selected fields. Arrays retain typed counts
and eight-element previews; omitted values, unknown types and runtime behavior
stay explicit. See [model files](https://github.com/morluto/rea/blob/main/docs/model-artifacts.md).
## IL2CPP metadata
Use `inspect_il2cpp_metadata` for an explicit local Unity `global-metadata.dat`
(versions 24, 27, 29, 31 and 39) when the question concerns assembly images,
names, member owners, string literals, namespaces or index ranges. No `open_binary`, Unity installation or target
execution is required. The CLI equivalent is `inspect-il2cpp-metadata PATH`.
MCP defaults to counts with complete retained Evidence. Query `sections`,
`images`, `types`, `methods`, `fields`, `properties`, `events`, `parameters` or
`string_literals` through `inspect_analysis_view`; use exact names,
batch indices and selected fields. Row indices are scoped by artifact SHA-256
and collection; page offsets count filtered matches. Parameters support batch
`method_indices` to read one method's parameters directly. Historical reports
without member collections retain unknown counts rather than empty inventories. Preserve
artifact SHA-256 and index widths. Unrecognized magic may mean encryption and
is unsupported, not decrypted.
Optional matching `binary_path` enables static native addresses and type
references. Query `method_addresses` with indices or addresses/states, preserving
every shared-body method and both artifact identities. Without a linked binary,
native counts remain unknown. Byte-pattern fallback is inferred; no runtime
behavior or same-build pairing is proved.
See [IL2CPP metadata](https://github.com/morluto/rea/blob/main/docs/il2cpp-metadata.md).
## Rust dependency metadata
For selected crates, use `view: {collection: "packages", names: [...],
fields: ["name", "version", "source"]}` on the first call. Include `dependencies`
explicitly for direct index references. Select `detail: "summary"` for counts and
ranges; retained Rust package/edge views support later filters. Keep complete
parent Evidence accessible; CLI compact delivery requires `--evidence_output`.
Use `inspect_rust_binary` for an explicit local ELF, PE, or Mach-O (universal
files need `architecture`) when
the question concerns the cargo-auditable crate list embedded in `.dep-v0`
(Mach-O: `__DATA,.dep-v0`). No `open_binary`, Rust installation, or target
execution is required. The CLI equivalent is `inspect-rust-binary PATH`.
Preserve artifact SHA-256, package order, dependency indices, and the section
file range. A missing section is null metadata, not proof the binary has no
dependencies. An omitted package kind stays null. A rustc commit hash is
inferred from a read-only `/rustc/<40-hex>/` path and does not by itself
identify the compiler. See [Rust binaries](https://github.com/morluto/rea/blob/main/docs/rust-binaries.md).
## PE Authenticode certificate tables
Use `inspect_pe_certificates` for an explicit local PE32/PE32+ image when the
question concerns the Authenticode certificate table, WIN_CERTIFICATE types, or
embedded PKCS#7 signer and X.509 identity fields. No `open_binary`, Windows SDK,
or target execution is required. The CLI equivalent is
`inspect-pe-certificates PATH`. Preserve artifact SHA-256, table file ranges,
payload hashes, original validity-time spellings, and
`subject_key_identifier_hex` when the signer uses that form. A missing
`IMAGE_DIRECTORY_ENTRY_SECURITY` directory is observed absence of that table,
not proof the image is unsigned in every catalog or overlay sense. Signatures,
chains, timestamps, and Authenticode digest coverage remain unobserved. See
[PE Authenticode certificate tables](https://github.com/morluto/rea/blob/main/docs/pe-certificates.md).
## PE import and delay-load tables
Use `inspect_pe_imports` for an explicit local PE32/PE32+ image when the
question concerns imported DLL and function names, ordinals, delay-load
descriptors, or the pefile import hash. No `open_binary`, Windows SDK, or
target execution is required. The CLI equivalent is `inspect-pe-imports PATH`.
Preserve artifact SHA-256, directory file ranges, original DLL spellings, and
ILT/IAT RVAs. A missing `IMAGE_DIRECTORY_ENTRY_IMPORT` directory is observed
absence of that table, not proof the image never imported APIs through another
table or overlay. Bound snapshots, loaded IAT contents, and export-forward
resolution remain unobserved. See
[PE import and delay-load tables](https://github.com/morluto/rea/blob/main/docs/pe-imports.md).
## Windows Shell Link shortcuts
Use `inspect_windows_shortcut` for an explicit local `.lnk` file when the
question concerns the recorded target path, arguments, working directory, icon
location, volume serial, network share names, ExtraData blocks, or Tracker
droid identifiers. No `open_binary`, Windows SDK, COM, or target execution is
required. The CLI equivalent is `inspect-windows-shortcut PATH`. Preserve
artifact SHA-256, FILETIME raw decimals, IDList type bytes, and ExtraData
signatures. Unknown IDList items and ExtraData signatures remain raw with
SHA-256; they are not guessed. Version-1 UUID MAC and timestamp values are
labelled derived. A missing optional section is observed absence of that
structure. See
[Windows Shell Link shortcuts](https://github.com/morluto/rea/blob/main/docs/guides/native/windows-shortcut.md).
## PE TLS callbacks and load configuration
Preserve each callback's RVA, file offset and section alongside its VA and the
size-covered declaration fields. A missing directory is observed absence of
that declaration.
Use `inspect_pe_load_config` for an explicit local PE32/PE32+ image when the
question concerns code that runs before the entry point through TLS callbacks,
or the security cookie, SafeSEH, CFG, EH-continuation and XFG declarations in
the load configuration. No `open_binary`, Windows SDK, or target execution is
required. The CLI equivalent is `inspect-pe-load-config PATH`. Preserve artifact
SHA-256, directory and callback-array file ranges, and callback VAs as
hexadecimal strings. Fields outside the declared `Size` are `null`. Guard-flag
mitigations carry `evidence_kind: "inferred"`; enforcement remains unobserved.
See
[PE TLS callbacks and load configuration](https://github.com/morluto/rea/blob/main/docs/pe-load-config.md).
## Binary hardening
Each property reports `enabled`, `disabled`, `partial`, `unknown`, or
`not_applicable`, with the header field or import behind it.
Use `inspect_binary_hardening` for an explicit local ELF, PE, or Mach-O file
when the question concerns PIE or ASLR, NX, RELRO, stack-protector or FORTIFY
signs, control-flow flags, or stripped state. No `open_binary`, pwntools, or
target execution is required. The CLI equivalent is
`inspect-binary-hardening PATH`. Universal Mach-O files return one image per
slice. A missing import is `unknown`, not proof the protection was omitted.
Authenticode and Mach-O code-signature rows report presence, not validity.
`hardened_runtime` stays unknown and names `inspect_signature`. See
[binary hardening](https://github.com/morluto/rea/blob/main/docs/binary-hardening.md).
## ELF note tables
Retain each note's owner, type, descriptor SHA-256, and PT_NOTE/SHT_NOTE ranges.
Use `inspect_elf_notes` for an explicit local ELF32/ELF64 image when the
question concerns PT_NOTE/SHT_NOTE owners, GNU build-id, or GNU ABI-tag
fields. No `open_binary` or target execution is required. The CLI equivalent
is `inspect-elf-notes PATH`. Preserve artifact SHA-256, table file ranges,
descriptor hashes, and original owner spellings. A missing table is observed
absence of those declarations, not proof the image never carried notes in
another container. See
[ELF note tables](https://github.com/morluto/rea/blob/main/docs/elf-notes.md).
## ELF and Mach-O initializer functions
Use `inspect_native_initializers` for an explicit local ELF or Mach-O image
when the question is which functions run before `main` or at exit: ELF
`DT_PREINIT_ARRAY`, `DT_INIT`, `DT_INIT_ARRAY`, `DT_FINI`, `DT_FINI_ARRAY` and
legacy `.ctors`/`.dtors`, or Mach-O `__mod_init_func`, `__init_offsets` and
`__mod_term_func`. Pass `architecture` for a universal Mach-O. The CLI
equivalent is `inspect-native-initializers PATH`. PIE slots are resolved
through RELATIVE, RELR and section relocations, and Mach-O slots through
chained fixups. Keep each entry's `resolution`: `unresolved` means the value was
not established, and `unresolved_import` names an external symbol. Page with
`offset` and `limit`. See
[ELF and Mach-O initializer functions](https://github.com/morluto/rea/blob/main/docs/guides/native/native-initializers.md).
## ELF unwind function ranges
Use `inspect_elf_unwind_ranges` for an explicit local ELF32/ELF64 image when
the question concerns function starts and lengths that survive `strip`, which
functions have an LSDA, or which ranges have no exact symbol. No
`open_binary` or target execution is required. The CLI equivalent is
`inspect-elf-unwind-ranges PATH`. Preserve artifact SHA-256, FDE start and
end, containing section, exact symbol name, CIE index, LSDA, and personality.
Page later ranges with `offset` and `limit`. A missing `.eh_frame` is
observed absence of that declaration and of `PT_GNU_EH_FRAME`, not proof the
image has no functions. Header disagreement is a limitation and does not drop
decoded ranges. `.debug_frame` is reported present or absent and is not
decoded. CFA instructions are counted and not evaluated. Indirect EH pointers
are not dereferenced. See
[ELF unwind function ranges](https://github.com/morluto/rea/blob/main/docs/guides/native/elf-unwind-ranges.md).
## Apple IPS crash reports
Use `inspect_apple_ips` for an explicit local Apple `.ips` file when the
question concerns crash metadata, exception and termination facts, threads,
usedImages, lossless registers, or documented crash classes. No `open_binary`,
Xcode, atos, or target execution is required. The CLI equivalent is
`inspect-apple-ips PATH`. Preserve artifact SHA-256, `bug_type` as a string,
uppercase image UUIDs, and integer literals that exceed IEEE-754 range. Only
`bug_type` `"309"` yields a crash body; other DiagnosticReports types return
no crash result. Select `join_path` for one local Mach-O or dSYM to match
report images to slices by UUID and architecture. Preserve the selected
artifact identity, slice facts and unjoined reasons; a joined static address
does not resolve function names or dSYM symbols. See
[Apple IPS crash reports](https://github.com/morluto/rea/blob/main/docs/apple-ips.md).
## Linux kernel modules
Use `inspect_kernel_module` for an explicit local ELF32/ELF64 relocatable
object when the question concerns `.modinfo` license, vermagic, alias,
depends, or parm strings, the `__versions` symbol CRCs, or an appended module
signature trailer. No `open_binary`, Linux host, or `modinfo` program is
required. The CLI equivalent is `inspect-kernel-module PATH`. Preserve
artifact SHA-256, key order, duplicate keys, CRC hex, and section file ranges.
A missing section is observed absence when section names are known, not proof
the module never carried that metadata. `.gnu.linkonce.this_module` is reported
as present or absent; `struct module` stays unknown because its layout changes
with the kernel version. `__ksymtab` records are not decoded. The PKCS#7 range
and its SHA-256 are observed; certificate, signer, and digest coverage remain
unvalidated. An absent trailer is not proof the module is unsigned in another
container. gzip, xz, and zstd whole-file containers are unsupported and name
`external_decompression` as the next probe: use an external codec utility to
decompress into a separate file, then call `inspect_kernel_module` on that file. See
[Linux kernel modules](https://github.com/morluto/rea/blob/main/docs/kernel-modules.md).
## Linux kernel build config
Use `inspect_kernel_build_config` for an explicit local kernel image (`vmlinux`
ELF, raw arm64 `Image`, x86 `bzImage`/`zImage`, or `configs.ko`) when the
question is which kernel version it is or how it was configured. No
`open_binary`, Linux host, or `extract-ikconfig` is required. The CLI
equivalent is `inspect-kernel-build-config PATH`; pass `name` for one exact
`CONFIG_*` option and `offset`/`limit` to page the rest. Preserve the banner
version, builder, and compiler, the config SHA-256 and option count, and each
entry's value kind (`y`, `m`, `string`, `number`, `not_set`). A config is
accepted only when `IKCFG_ST` is immediately followed by a valid gzip member
and `IKCFG_ED`, so an ordinary `IKCFG_ST` string is not a config. Offsets with
`space: "inflated_payload"` are positions in an inflated bzImage payload, not
file offsets. An absent config describes only the searched bytes; the kernel
may have been built without `CONFIG_IKCONFIG` or with it as a module. Truncated
payloads are incomplete, not absent. A framed blob that does not decode, has a
bad CRC, or holds non-config text is `malformed`, not absent. xz, lz4, lzma, zstd, bzip2, and lzo
payloads are unsupported and name `external_decompression`: decompress with an
external utility, then call `inspect_kernel_build_config` on the result. See
[Linux kernel build config](https://github.com/morluto/rea/blob/main/docs/guides/native/kernel-build-config.md).
## CPython pyc files
Use `inspect_python_pyc` for an explicit local CPython 3.7–3.13 `.pyc` when the
question concerns the header magic, PEP 552 source hash or timestamp, nested
code-object names, or bytecode identity. No `open_binary`, Python installation,
or target execution is required. The CLI equivalent is `inspect-python-pyc PATH`.
Preserve artifact SHA-256, the 16-byte header fields, nested code names, and
bytecode SHA-256. Bytecode is not decompiled. A decoded header does not prove
the matching `.py` is present. See [CPython pyc inspection](https://github.com/morluto/rea/blob/main/docs/python-pyc.md).
## Erlang BEAM files
Use `inspect_erlang_beam` for an explicit local `FOR1`/`BEAM` file when the
question concerns IFF chunks, atom names, exports, imports, lambdas, or the
Code header. No `open_binary`, Erlang installation, or bytecode execution is
required. The CLI equivalent is `inspect-erlang-beam PATH`. Preserve artifact
SHA-256, chunk file ranges, 1-based atom indices, and original atom spellings.
Missing optional tables stay null. For a few facts, use `detail: "summary"` with
a `beam` view selecting a collection, exact names/indices and fields; read later
pages from `parent_evidence_id` or the complete Evidence bundle. Same-name
exports retain every arity and original atom index. CLI summary delivery requires
`--evidence_output`. Null table counts remain distinct from decoded empty tables. See
[Erlang BEAM files](https://github.com/morluto/rea/blob/main/docs/erlang-beam.md).
## SQLite snapshots
Use `inspect_sqlite_database` with an explicit local database snapshot for its
schema and optional rows from one exact ordinary table. It requires Node.js
24.x >=24.15 or 26+ with the native `DatabaseSync.limits` API. It also consumes the
existing sibling `-wal`; retain both original digests when citing its result.
Default output is a compact inventory; a selected table expands only its own
schema and samples 20 rows. Select `columns` to avoid irrelevant or large cells,
and request `schema_scope: "database"` only for complete schema details.
Choose `row_limit` when a finite sample is useful and keep truncation and
unspecified row order explicit. Virtual tables, shadow tables and views are
schema facts, not row-read targets. REA inspects private copies and leaves
the source files untouched; obtain a coherent quiescent database/WAL snapshot
before inspection. See [SQLite snapshots](https://github.com/morluto/rea/blob/main/docs/guides/data/sqlite-databases.md).
## Recorded Linux crashes
Use `inspect_recorded_crash` with an explicit Linux x86-64 ELF core path to
inspect historical thread registers, signals, raw notes and original file
ranges. It is independent of the active disassembler target. Source-bound
register values and note bytes are observations; missing notes, signal-thread
association and current executable/library identity remain unknown. PIDs are
historical metadata and never authorize live attach or control.
The optional `include_debugger_context` facet adds mapping candidates through
caller-supplied GDB/pwndbg in an owned core-only session. Display names do not
establish current file identity; zero reported flags leave permissions unknown.
Requested unavailable context fails with setup guidance. See
[recorded crashes](https://github.com/morluto/rea/blob/main/docs/guides/native/recorded-crashes.md)
for exact upstream profiles, bounds and verification coverage.
## Offline ELF layout
Use `inspect_binary_layout` with an explicit local ELF path for file-backed
sections, symbols, relocations, linkage names, and static mitigation inferences
without opening a disassembler target. When that complete record is already
retained, call `inspect_analysis_view` for a summary, mitigations or linkage
facet, one section or symbol, or a stable page. Do not re-run the decoder to
read a single object.
## Offline WASM
Supply WABT 1.0.42 through `REA_WABT_BIN_DIRECTORY`. Select JavaScript glue and
candidate paths explicitly, preserving URL/path ambiguity and exact byte/tool
identity; no implicit fetch is performed.
Use `inspect_wasm_artifact` with caller-supplied WABT for selected local bytes.
For counts, validation and WAT identity, select `detail: "summary"` on the first
MCP call. The complete original Evidence stays retained; use
`normalized_result.parent_evidence_id` for exact exports or text pages. Omitted
or complete detail keeps the existing complete result.
For a completed result, use `inspect_analysis_view` with its reported retained
reference for a summary or `view: {kind: "wasm", facet: "exports", offset: 0, limit: 32}`.
Facets also include sections, imports, candidates, glue, WAT, headers, details
and tool profile. Text offsets count UTF-16 code units; follow `next_offset`.
The complete source remains retained after an oversized MCP reply. No target
execution or network acquisition is implied, and WAT is decoded representation.
For exact core-module import/export differences, use `compare_wasm_interfaces`
with two complete inspection Evidence records (or current-session retained
references) and explicit scope. Preserve unknown types/duplicate names; static
interface equality is not runtime compatibility. CLI accepts complete inline
Evidence in `rea compare-wasm-interfaces input.json --json`, or exact references
with `--evidence-bundle /absolute/path/evidence.json`. Use the complete parent
ID when the inspection returned a summary.
See the [WASM guide](https://github.com/morluto/rea/blob/main/docs/guides/wasm/wasm-artifacts.md).
## Offline APK signatures
Use `inspect_apk_signature` for a selected local APK/ZIP/AAB. Default MCP summaries
retain complete Evidence. Select `view.collection: signers/digests/lineage`,
with scheme or ID batches, fields and paging; use `inspect_analysis_view` kind
`apk-signature` for retained reads after deletion. IDs retain artifact/scheme
scope and signer links; `fields: []` keeps identity links. Empty pages preserve
partial decoding reasons and unknown authenticity. CLI compact delivery needs
`--evidence_output` to preserve the complete parent.
It reads the APK
Signing Block (v2/v3/v3.1) and v1 `META-INF` signing without executing anything,
and reports each signer's certificate facts (for v1, the certificate the PKCS#7
block's SignerInfo names), the declared v3 rotation lineage,
the declared source-stamp certificate and, for SHA-256/SHA-512 digests, whether
the declared content digest matches the current bytes. That match is not a
signature verification: anyone who changes the bytes can recompute the digest.
`posture` summarises v1-only signing (which cannot tell a v1-only APK from one
whose v2/v3 block was removed), observed signer agreement across schemes,
whether the v1 signer equals the block-scheme signer (`false` is notable),
whether every differing signer appears in a declared (unverified) rotation
lineage, and the strongest scheme.
It never claims authenticity: no signature or lineage is cryptographically
verified.
See the [APK signature guide](https://github.com/morluto/rea/blob/main/docs/apk-signature.md).
## Offline PE resource inventory
Use `inspect_pe_resources` with an absolute `path`, or
`rea inspect-pe-resources ./selected.exe --json`, for portable PE32/PE32+
resource inspection without opening a native target. Preserve returned Evidence:
type/name/language IDs and UTF-16 names, original directory/data/payload ranges,
code pages, payload hashes and static icon-group references. Same-language icon
resolution is explicit; missing references and size mismatch remain visible.
Other payloads are opaque, and this operation does not decode images or simulate
Windows resource loading. Consult the budgets and supported tree profile in
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/guides/native/binary-diagnostics.md).
## Offline PE and ELF export tables
Use `inspect_native_exports` with an absolute `path`, or
`rea inspect-native-exports ./libexample.so --json`, to read a PE export
directory or ELF dynamic symbol table on any host without opening a native
target. Ordinal-only, forwarded and hidden-version exports stay distinct;
partial or unsupported coverage means the inventory is incomplete, not that
exports are absent. Use `inspect_macho` for Mach-O. See
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/guides/native/binary-diagnostics.md).
## EVM bytecode
Explicit local EVM bytecode carrier: `inspect_evm_interface` with caller-selected
`raw` or `hex` encoding. The owned worker deadline is `timeout_ms` (default
30000 ms, range 1..2147483647, next call). Select byte, address-space and CPU
allowances separately through `producer_limits`; tighter inherited OS limits
remain enforced, and the 256 MiB worker heap is independent. Preserve carrier/decoded digests and treat selectors,
argument strings and mutability as inferred candidates; this performs no chain
lookup or target execution. For the complete inferred interface without hex or
duplicate stdout, discover support and select `detail: "summary"`; complete
Evidence stays retained under its original identity. Use the returned
`parent_evidence_id` for exact function or `bytecode_hex`/stdout/stderr pages.
Candidate coverage does not establish interface completeness. See the
[EVM bytecode guide](https://github.com/morluto/rea/blob/main/docs/guides/evm/evm-bytecode.md).
## Embedded GPU code objects
Embedded CUDA/HIP code objects: `inspect_gpu_code_objects`; inventory targets
first, then decode selected payloads and query retained GPU Evidence.
@@ -1,13 +1,5 @@
# JavaScript and Electron application artifacts
Read only the sections needed for the selected task.
- [Inventory route candidates without returning the application graph](#inventory-route-candidates-without-returning-the-application-graph)
- [Read a module without repeating analysis](#read-a-module-without-repeating-analysis)
- [Trace only the remaining question](#trace-only-the-remaining-question)
- [Recover an oversized display](#recover-an-oversized-display)
- [Reusing application Evidence](#reusing-application-evidence)
For captured or bundled JavaScript that needs readable source recovery, use
`recover_javascript_sources` with an absolute `path` and absent
`output_directory`. Supply related runtime/shared/lazy files in
@@ -98,9 +90,6 @@ Endpoint candidates remain inferences rather than confirmed network behavior.
## Read a module without repeating analysis
Use the actual returned parent ID, on the same connection, as `source.evidence_id`.
For CLI summary exports, take that ID from
`summary.normalized_result.parent_evidence_id` and pass `--evidence-bundle` with
the exported bundle's path to each later `inspect-analysis-view` invocation.
The following IDs are placeholders to replace with returned values. When the
module's node ID is not already known, page identities before selecting an item.
Pages return only node IDs, paths and roles; choose a useful page size and follow
@@ -1,23 +1,4 @@
# Native and managed analysis
<a id="native-managed-and-packaged-artifacts"></a>
Read only the sections needed for the selected task.
- [Other artifact guides](#other-artifact-guides)
- [Apple app resources](#apple-app-resources)
- [Desktop installation directories](#desktop-installation-directories)
- [Native targets](#native-targets)
- [Continue editable Ghidra analysis across processes](#continue-editable-ghidra-analysis-across-processes)
- [Reuse caller-selected Ghidra analysis](#reuse-caller-selected-ghidra-analysis)
- [Ghidra startup deadline recovery](#ghidra-startup-deadline-recovery)
- [Bridge request ceiling](#bridge-request-ceiling)
- [Selected native facts and call graphs](#selected-native-facts-and-call-graphs)
- [Static memory and original file bytes](#static-memory-and-original-file-bytes)
- [DOS MZ with Ghidra](#dos-mz-with-ghidra)
- [Managed PE/CLI](#managed-pe-cli-guide)
- [DOS COM loader interpretation](#dos-com-loader-interpretation)
- [Function annotations in Ghidra](#function-annotations-in-ghidra)
# Native, managed, and packaged artifacts
For release comparisons on one MCP connection, retain `list_procedures` Evidence
from each target and reuse its exact IDs after target switches or `close_binary`.
@@ -81,39 +62,342 @@ runtime presence. For compact CLI output, use
`inspect-desktop-application PATH --detail summary --evidence_output BUNDLE`.
See [desktop installations](https://github.com/morluto/rea/blob/main/docs/desktop-applications.md).
## Release exposure
Use `audit_release_exposure` (`rea audit-release-exposure PATH`) when the question is what static analysis of a shipped `.app`, install directory, ASAR, or executable recovers. Each check has a stable id, a status, the observation behind it, and fixed remediation. Unknown and unassessed checks stay in the report. The report does not establish that the application is secure.
`text.credential_markers` reports a rule id and location only. Matched bytes, previews, lengths, and hashes of matched bytes are omitted.
`native.hardening`, `macho.hardened_runtime`, and `android.manifest` are not assessed here. Their next probes are `inspect_binary_hardening`, `inspect_signature`, and `inspect_android_package`.
See [release exposure](https://github.com/morluto/rea/blob/main/docs/release-exposure.md).
## Go build metadata
Use `inspect_go_binary` for an explicit local ELF, PE or Mach-O (universal
files need `architecture`) when the
question concerns the embedded compiler version, module/dependency versions,
local replacements, build settings, or static Go string literals packed without
NUL terminators. Pass `include_string_literals: true`
(`--include-string-literals`, optional `max_string_literals` /
`--max-string-literals`) to recover bounded inferred word-aligned `(ptr, len)`
UTF-8 string slices from read-only data and page them through `go`
`string_literals` views. No `open_binary`, Go installation or
target execution is required. The CLI equivalent is `inspect-go-binary PATH`.
Preserve artifact SHA-256, original file ranges, framed module bytes and every
unparsed module-text line. A missing record is an unknown, not proof of a
non-Go binary; observed metadata does not authenticate its claims, and static
`(ptr, len)` string recovery excludes compiler-inlined stack/immediate strings
without proving runtime reachability. Function,
source and type recovery remain a separate native-analysis question.
## Model files
Use `inspect_model_artifact` (`inspect-model-artifact PATH`) for local GGUF v2/v3
and safetensors declarations without a model runtime. Header-only mode does not
read weights or establish full-file identity. `hash: "full"` explicitly streams
all bytes. Retained `model-artifact` views page `metadata`, `tensors` and derived
`types`, with exact batch filters and selected fields. Arrays retain typed counts
and eight-element previews; omitted values, unknown types and runtime behavior
stay explicit. See [model files](https://github.com/morluto/rea/blob/main/docs/model-artifacts.md).
## IL2CPP metadata
Use `inspect_il2cpp_metadata` for an explicit local Unity `global-metadata.dat`
(versions 24, 27, 29, 31 and 39) when the question concerns assembly images,
names, member owners, string literals, namespaces or index ranges. No `open_binary`, Unity installation or target
execution is required. The CLI equivalent is `inspect-il2cpp-metadata PATH`.
MCP defaults to counts with complete retained Evidence. Query `sections`,
`images`, `types`, `methods`, `fields`, `properties`, `events`, `parameters` or
`string_literals` through `inspect_analysis_view`; use exact names,
batch indices and selected fields. Row indices are scoped by artifact SHA-256
and collection; page offsets count filtered matches. Parameters support batch
`method_indices` to read one method's parameters directly. Historical reports
without member collections retain unknown counts rather than empty inventories. Preserve
artifact SHA-256 and index widths. Unrecognized magic may mean encryption and
is unsupported, not decrypted. Linking names to `GameAssembly` or
`UnityFramework` code addresses remains a separate native-analysis question.
See [IL2CPP metadata](https://github.com/morluto/rea/blob/main/docs/il2cpp-metadata.md).
## Rust dependency metadata
Use `inspect_rust_binary` for an explicit local ELF, PE, or Mach-O (universal
files need `architecture`) when
the question concerns the cargo-auditable crate list embedded in `.dep-v0`
(Mach-O: `__DATA,.dep-v0`). No `open_binary`, Rust installation, or target
execution is required. The CLI equivalent is `inspect-rust-binary PATH`.
Preserve artifact SHA-256, package order, dependency indices, and the section
file range. A missing section is null metadata, not proof the binary has no
dependencies. An omitted package kind stays null. A rustc commit hash is
inferred from a read-only `/rustc/<40-hex>/` path and does not by itself
identify the compiler. See [Rust binaries](https://github.com/morluto/rea/blob/main/docs/rust-binaries.md).
## PE Authenticode certificate tables
Use `inspect_pe_certificates` for an explicit local PE32/PE32+ image when the
question concerns the Authenticode certificate table, WIN_CERTIFICATE types, or
embedded PKCS#7 signer and X.509 identity fields. No `open_binary`, Windows SDK,
or target execution is required. The CLI equivalent is
`inspect-pe-certificates PATH`. Preserve artifact SHA-256, table file ranges,
payload hashes, original validity-time spellings, and
`subject_key_identifier_hex` when the signer uses that form. A missing
`IMAGE_DIRECTORY_ENTRY_SECURITY` directory is observed absence of that table,
not proof the image is unsigned in every catalog or overlay sense. Signatures,
chains, timestamps, and Authenticode digest coverage remain unobserved. See
[PE Authenticode certificate tables](https://github.com/morluto/rea/blob/main/docs/pe-certificates.md).
## PE import and delay-load tables
Use `inspect_pe_imports` for an explicit local PE32/PE32+ image when the
question concerns imported DLL and function names, ordinals, delay-load
descriptors, or the pefile import hash. No `open_binary`, Windows SDK, or
target execution is required. The CLI equivalent is `inspect-pe-imports PATH`.
Preserve artifact SHA-256, directory file ranges, original DLL spellings, and
ILT/IAT RVAs. A missing `IMAGE_DIRECTORY_ENTRY_IMPORT` directory is observed
absence of that table, not proof the image never imported APIs through another
table or overlay. Bound snapshots, loaded IAT contents, and export-forward
resolution remain unobserved. See
[PE import and delay-load tables](https://github.com/morluto/rea/blob/main/docs/pe-imports.md).
## Windows Shell Link shortcuts
Use `inspect_windows_shortcut` for an explicit local `.lnk` file when the
question concerns the recorded target path, arguments, working directory, icon
location, volume serial, network share names, ExtraData blocks, or Tracker
droid identifiers. No `open_binary`, Windows SDK, COM, or target execution is
required. The CLI equivalent is `inspect-windows-shortcut PATH`. Preserve
artifact SHA-256, FILETIME raw decimals, IDList type bytes, and ExtraData
signatures. Unknown IDList items and ExtraData signatures remain raw with
SHA-256; they are not guessed. Version-1 UUID MAC and timestamp values are
labelled derived. A missing optional section is observed absence of that
structure. See
[Windows Shell Link shortcuts](https://github.com/morluto/rea/blob/main/docs/guides/native/windows-shortcut.md).
## PE TLS callbacks and load configuration
Use `inspect_pe_load_config` for an explicit local PE32/PE32+ image when the
question concerns code that runs before the entry point through TLS callbacks,
or the security cookie, SafeSEH, CFG, EH-continuation and XFG declarations in
the load configuration. No `open_binary`, Windows SDK, or target execution is
required. The CLI equivalent is `inspect-pe-load-config PATH`. Preserve artifact
SHA-256, directory and callback-array file ranges, and callback VAs as
hexadecimal strings. Fields outside the declared `Size` are `null`. Guard-flag
mitigations carry `evidence_kind: "inferred"`; enforcement remains unobserved.
See
[PE TLS callbacks and load configuration](https://github.com/morluto/rea/blob/main/docs/pe-load-config.md).
## Binary hardening
Use `inspect_binary_hardening` for an explicit local ELF, PE, or Mach-O file
when the question concerns PIE or ASLR, NX, RELRO, stack-protector or FORTIFY
signs, control-flow flags, or stripped state. No `open_binary`, pwntools, or
target execution is required. The CLI equivalent is
`inspect-binary-hardening PATH`. Universal Mach-O files return one image per
slice. A missing import is `unknown`, not proof the protection was omitted.
Authenticode and Mach-O code-signature rows report presence, not validity.
`hardened_runtime` stays unknown and names `inspect_signature`. See
[binary hardening](https://github.com/morluto/rea/blob/main/docs/binary-hardening.md).
## ELF note tables
Use `inspect_elf_notes` for an explicit local ELF32/ELF64 image when the
question concerns PT_NOTE/SHT_NOTE owners, GNU build-id, or GNU ABI-tag
fields. No `open_binary` or target execution is required. The CLI equivalent
is `inspect-elf-notes PATH`. Preserve artifact SHA-256, table file ranges,
descriptor hashes, and original owner spellings. A missing table is observed
absence of those declarations, not proof the image never carried notes in
another container. See
[ELF note tables](https://github.com/morluto/rea/blob/main/docs/elf-notes.md).
## ELF and Mach-O initializer functions
Use `inspect_native_initializers` for an explicit local ELF or Mach-O image
when the question is which functions run before `main` or at exit: ELF
`DT_PREINIT_ARRAY`, `DT_INIT`, `DT_INIT_ARRAY`, `DT_FINI`, `DT_FINI_ARRAY` and
legacy `.ctors`/`.dtors`, or Mach-O `__mod_init_func`, `__init_offsets` and
`__mod_term_func`. Pass `architecture` for a universal Mach-O. The CLI
equivalent is `inspect-native-initializers PATH`. PIE slots are resolved
through RELATIVE, RELR and section relocations, and Mach-O slots through
chained fixups. Keep each entry's `resolution`: `unresolved` means the value was
not established, and `unresolved_import` names an external symbol. Page with
`offset` and `limit`. See
[ELF and Mach-O initializer functions](https://github.com/morluto/rea/blob/main/docs/guides/native/native-initializers.md).
## ELF unwind function ranges
Use `inspect_elf_unwind_ranges` for an explicit local ELF32/ELF64 image when
the question concerns function starts and lengths that survive `strip`, which
functions have an LSDA, or which ranges have no exact symbol. No
`open_binary` or target execution is required. The CLI equivalent is
`inspect-elf-unwind-ranges PATH`. Preserve artifact SHA-256, FDE start and
end, containing section, exact symbol name, CIE index, LSDA, and personality.
Page later ranges with `offset` and `limit`. A missing `.eh_frame` is
observed absence of that declaration and of `PT_GNU_EH_FRAME`, not proof the
image has no functions. Header disagreement is a limitation and does not drop
decoded ranges. `.debug_frame` is reported present or absent and is not
decoded. CFA instructions are counted and not evaluated. Indirect EH pointers
are not dereferenced. See
[ELF unwind function ranges](https://github.com/morluto/rea/blob/main/docs/guides/native/elf-unwind-ranges.md).
## Apple IPS crash reports
Use `inspect_apple_ips` for an explicit local Apple `.ips` file when the
question concerns crash metadata, exception and termination facts, threads,
usedImages, lossless registers, or documented crash classes. No `open_binary`,
Xcode, atos, or target execution is required. The CLI equivalent is
`inspect-apple-ips PATH`. Preserve artifact SHA-256, `bug_type` as a string,
uppercase image UUIDs, and integer literals that exceed IEEE-754 range. Only
`bug_type` `"309"` yields a crash body; other DiagnosticReports types return
no crash result. Select `join_path` for one local Mach-O or dSYM to match
report images to slices by UUID and architecture. Preserve the selected
artifact identity, slice facts and unjoined reasons; a joined static address
does not resolve function names or dSYM symbols. See
[Apple IPS crash reports](https://github.com/morluto/rea/blob/main/docs/apple-ips.md).
## Linux kernel modules
Use `inspect_kernel_module` for an explicit local ELF32/ELF64 relocatable
object when the question concerns `.modinfo` license, vermagic, alias,
depends, or parm strings, the `__versions` symbol CRCs, or an appended module
signature trailer. No `open_binary`, Linux host, or `modinfo` program is
required. The CLI equivalent is `inspect-kernel-module PATH`. Preserve
artifact SHA-256, key order, duplicate keys, CRC hex, and section file ranges.
A missing section is observed absence when section names are known, not proof
the module never carried that metadata. `.gnu.linkonce.this_module` is reported
as present or absent; `struct module` stays unknown because its layout changes
with the kernel version. `__ksymtab` records are not decoded. The PKCS#7 range
and its SHA-256 are observed; certificate, signer, and digest coverage remain
unvalidated. An absent trailer is not proof the module is unsigned in another
container. gzip, xz, and zstd whole-file containers are unsupported and name
`external_decompression` as the next probe: use an external codec utility to
decompress into a separate file, then call `inspect_kernel_module` on that file. See
[Linux kernel modules](https://github.com/morluto/rea/blob/main/docs/kernel-modules.md).
## Linux kernel build config
Use `inspect_kernel_build_config` for an explicit local kernel image (`vmlinux`
ELF, raw arm64 `Image`, x86 `bzImage`/`zImage`, or `configs.ko`) when the
question is which kernel version it is or how it was configured. No
`open_binary`, Linux host, or `extract-ikconfig` is required. The CLI
equivalent is `inspect-kernel-build-config PATH`; pass `name` for one exact
`CONFIG_*` option and `offset`/`limit` to page the rest. Preserve the banner
version, builder, and compiler, the config SHA-256 and option count, and each
entry's value kind (`y`, `m`, `string`, `number`, `not_set`). A config is
accepted only when `IKCFG_ST` is immediately followed by a valid gzip member
and `IKCFG_ED`, so an ordinary `IKCFG_ST` string is not a config. Offsets with
`space: "inflated_payload"` are positions in an inflated bzImage payload, not
file offsets. An absent config describes only the searched bytes; the kernel
may have been built without `CONFIG_IKCONFIG` or with it as a module. Truncated
payloads are incomplete, not absent. A framed blob that does not decode, has a
bad CRC, or holds non-config text is `malformed`, not absent. xz, lz4, lzma, zstd, bzip2, and lzo
payloads are unsupported and name `external_decompression`: decompress with an
external utility, then call `inspect_kernel_build_config` on the result. See
[Linux kernel build config](https://github.com/morluto/rea/blob/main/docs/guides/native/kernel-build-config.md).
## CPython pyc files
Use `inspect_python_pyc` for an explicit local CPython 3.7–3.13 `.pyc` when the
question concerns the header magic, PEP 552 source hash or timestamp, nested
code-object names, or bytecode identity. No `open_binary`, Python installation,
or target execution is required. The CLI equivalent is `inspect-python-pyc PATH`.
Preserve artifact SHA-256, the 16-byte header fields, nested code names, and
bytecode SHA-256. Bytecode is not decompiled. A decoded header does not prove
the matching `.py` is present. See [CPython pyc inspection](https://github.com/morluto/rea/blob/main/docs/python-pyc.md).
## Erlang BEAM files
Use `inspect_erlang_beam` for an explicit local `FOR1`/`BEAM` file when the
question concerns IFF chunks, atom names, exports, imports, lambdas, or the
Code header. No `open_binary`, Erlang installation, or bytecode execution is
required. The CLI equivalent is `inspect-erlang-beam PATH`. Preserve artifact
SHA-256, chunk file ranges, 1-based atom indices, and original atom spellings.
Missing optional tables stay null. For a few facts, use `detail: "summary"` with
a `beam` view selecting a collection, exact names/indices and fields; read later
pages from `parent_evidence_id` or the complete Evidence bundle. Same-name
exports retain every arity and original atom index. CLI summary delivery requires
`--evidence_output`. Null table counts remain distinct from decoded empty tables. See
[Erlang BEAM files](https://github.com/morluto/rea/blob/main/docs/erlang-beam.md).
## SQLite snapshots
Use `inspect_sqlite_database` with an explicit local database snapshot for its
schema and optional rows from one exact ordinary table. It requires Node.js
24.x >=24.15 or 26+ with the native `DatabaseSync.limits` API. It also consumes the
existing sibling `-wal`; retain both original digests when citing its result.
Default output is a compact inventory; a selected table expands only its own
schema and samples 20 rows. Select `columns` to avoid irrelevant or large cells,
and request `schema_scope: "database"` only for complete schema details.
Choose `row_limit` when a finite sample is useful and keep truncation and
unspecified row order explicit. Virtual tables, shadow tables and views are
schema facts, not row-read targets. REA inspects private copies and leaves
the source files untouched; obtain a coherent quiescent database/WAL snapshot
before inspection. See [SQLite snapshots](https://github.com/morluto/rea/blob/main/docs/guides/data/sqlite-databases.md).
## Recorded Linux crashes
Use `inspect_recorded_crash` with an explicit Linux x86-64 ELF core path to
inspect historical thread registers, signals, raw notes and original file
ranges. It is independent of the active disassembler target. Source-bound
register values and note bytes are observations; missing notes, signal-thread
association and current executable/library identity remain unknown. PIDs are
historical metadata and never authorize live attach or control.
The optional `include_debugger_context` facet adds mapping candidates through
caller-supplied GDB/pwndbg in an owned core-only session. Display names do not
establish current file identity; zero reported flags leave permissions unknown.
Requested unavailable context fails with setup guidance. See
[recorded crashes](https://github.com/morluto/rea/blob/main/docs/guides/native/recorded-crashes.md)
for exact upstream profiles, bounds and verification coverage.
## Offline ELF layout
Use `inspect_binary_layout` with an explicit local ELF path for file-backed
sections, symbols, relocations, linkage names, and static mitigation inferences
without opening a disassembler target. When that complete record is already
retained, call `inspect_analysis_view` for a summary, mitigations or linkage
facet, one section or symbol, or a stable page. Do not re-run the decoder to
read a single object.
## Offline WASM
Use `inspect_wasm_artifact` with caller-supplied WABT for selected local bytes.
For counts, validation and WAT identity, select `detail: "summary"` on the first
MCP call. The complete original Evidence stays retained; use
`normalized_result.parent_evidence_id` for exact exports or text pages. Omitted
or complete detail keeps the existing complete result.
For a completed result, use `inspect_analysis_view` with its reported retained
reference for a summary or `view: {kind: "wasm", facet: "exports", offset: 0, limit: 32}`.
Facets also include sections, imports, candidates, glue, WAT, headers, details
and tool profile. Text offsets count UTF-16 code units; follow `next_offset`.
The complete source remains retained after an oversized MCP reply. No target
execution or network acquisition is implied, and WAT is decoded representation.
## Offline APK signatures
Use `inspect_apk_signature` for a selected local APK/ZIP/AAB. Default MCP summaries
retain complete Evidence. Select `view.collection: signers/digests/lineage`,
with scheme or ID batches, fields and paging; use `inspect_analysis_view` kind
`apk-signature` for retained reads after deletion. IDs retain artifact/scheme
scope and signer links; `fields: []` keeps identity links. Empty pages preserve
partial decoding reasons and unknown authenticity. CLI compact delivery needs
`--evidence_output` to preserve the complete parent.
It reads the APK
Signing Block (v2/v3/v3.1) and v1 `META-INF` signing without executing anything,
and reports each signer's certificate facts (for v1, the certificate the PKCS#7
block's SignerInfo names), the declared v3 rotation lineage,
the declared source-stamp certificate and, for SHA-256/SHA-512 digests, whether
the declared content digest matches the current bytes. That match is not a
signature verification: anyone who changes the bytes can recompute the digest.
`posture` summarises v1-only signing (which cannot tell a v1-only APK from one
whose v2/v3 block was removed), observed signer agreement across schemes,
whether the v1 signer equals the block-scheme signer (`false` is notable),
whether every differing signer appears in a declared (unverified) rotation
lineage, and the strongest scheme.
It never claims authenticity: no signature or lineage is cryptographically
verified.
## Native targets
For an existing IDA MCP configuration, use `open_binary` with the original
input binary and `provider_id: "ida"`; do not pass an `.idb` or `.i64` database.
The legacy attached profile binds the already-open GUI input by SHA-256 and
leaves its database open. The modern headless profile creates and closes an
owned private database without saving. Installation and registration reuse
[mrexodia/ida-pro-mcp](https://github.com/mrexodia/ida-pro-mcp); see the
[IDA provider guide](https://github.com/morluto/rea/blob/main/docs/providers/ida-provider.md).
For migration to the separate native IDA MCP plugin, follow the guide's
[legacy attached migration](https://github.com/morluto/rea/blob/main/docs/providers/ida-provider.md#migrate-a-legacy-attached-registration).
Replace the upstream registration explicitly; modern mrexodia tools are not the
native profile. Native attachment preserves the database but does not provide
direct callers; check session availability and the guide's verification limits.
Proceed directly to `analyze_function`, pseudocode, or function/string searches
after opening; IDA does not supply `binary_overview`. Consult session availability
when composing broader workflows. Modern direct callers and unsupported dossier
facets remain unknown; live IDA results are not replayed from snapshots.
With `REA_KUNA_COMMAND` configured, `provider_id: "kuna"` answers procedure
lists, lookups, pseudocode, assembly and `analyze_function` from Kuna's cached
whole-program export. Callers/callees use a separate body-free graph shared by
the session; `xrefs` returns exact-address incoming sites and filters forwarding
aliases. These queries and inventory lookups do not trigger a C export. Indirect
targets remain unknown and can make relationship coverage partial. Reference
caches expire on close; a fresh CLI process repeats the native reference walk.
Typed references, CFG, raw-memory tools and annotations remain unsupported. A cold first
answer can come from a single-function run (its limitations say so); assembly
waits for the export. For pseudocode and assembly, `raw_result.source` names the
cached `.c`/`.asm` file and byte span, which you may search directly instead of
calling per function. See the
[Kuna provider guide](https://github.com/morluto/rea/blob/main/docs/kuna-provider.md).
After `open_binary`, use focused search, procedure, or function tools directly.
Use `binary_overview` when target metadata or inventory context helps answer the
question. Prefer literal search, names, decompilation, callers, callees, and
@@ -348,8 +632,6 @@ that a class is a root class or has no ivars.
## Managed PE/CLI
<a id="managed-pe-cli-guide"></a>
Start with `inspect_managed_artifact`. REA's canonical managed inspection is
execution-free: do not claim it loaded, reflected, executed, or resolved the
assembly. Keep managed/native boundaries and unavailable reconstruction facts
@@ -408,17 +690,81 @@ and distinguish derived metadata from Ghidra database observations. Recovered
types and native pseudocode are not original C# source. Windows Ghidra P0 has
no database mutation authority.
For headerless firmware and address-bearing containers, read
[raw images](firmware-and-raw-images.md#headerless-raw-images-and-firmware-containers).
## Packages and extraction
## DOS COM loader interpretation
Use `inspect_artifact` for application bundles, archives, ZIP/APK/IPA/MSIX/AppX,
ASAR, or DMG inputs when the artifact graph and findings help answer the
question. It returns the complete artifact graph inline. Cite graph manifest
IDs when using them.
For an IPA or a macOS `.app`, ZIP, or DMG, pass the inventory Evidence to
`project_apple_application_graph`. It reports application roots and nested
bundles by path convention: app extensions, XPC services, frameworks, login
items, system and driver extensions, and plug-ins. It also lists privileged
helpers, launchd plists, symlinks, and each bundle's `info_plist_path` and
executable candidates. Roles are path conventions, not parsed plists; read the
listed plists with `inspect_plist`.
After `open_binary` on a `.app` or Mach-O, use `trace_dylib_resolution` to see
which file each `@rpath`, `@loader_path` and `@executable_path` load reaches for
every executable in the bundle. It also lists missing or weak loads and earlier
`@rpath` candidates that are absent. Narrow large bundles with `roots` or
`architecture`. System paths stay undetermined because the dyld shared cache
provides them.
Use `extract_artifact` when materialized files from a supported format are needed.
It extracts the active target and accepts optional `integrity_policy`; see the [MCP contracts](https://github.com/morluto/rea/blob/main/docs/reference/mcp-contracts.md).
It materializes all regular files into a fresh temporary directory chosen by
REA. Symlinks and encrypted entries are inventory facts, not extractable files.
The result reports the materialized directory; callers do not choose the
destination. No separate permission grant is needed.
For classic iNES cartridges, `open_binary` recognizes the byte signature without
an executable architecture. `inspect_artifact` retains header facts and each
PRG/CHR file unit's offset, length and SHA-256; its nested inventory's
`ines.reconstruction` verifies the actual ordered region bytes against the
whole-file digest. Extraction includes the original header, optional trainer
and uninterpreted trailing bytes. These file units are not mapper/CPU bank
maps. NES 2.0, UNIF, dirty legacy headers and truncated payloads are explicitly
refused. No emulator or 6502 analysis is performed. See the
[iNES guide](https://github.com/morluto/rea/blob/main/docs/ines-containers.md).
Native DMG traversal automatically uses a read-only mount on supported macOS
hosts, with an owned temporary mount directory and cleanup. It needs no approval
flag. Unsupported hosts or mount failures remain explicit limitations; do not
claim child inventory when only root identity is available. DMG extraction is
unavailable through `extract_artifact`. To access DMG files on macOS, create a
read-only mount with your own tools, select files from that mount for analysis,
and detach the mount you created when finished. REA's inspection mount is
temporary, not a retained export of the image's contents.
For headerless DOS COM, explicitly open with `format: "dos-com"` (CLI
`--target-format dos-com`). Inspect the load image before trusting function
analysis: the entry is `0x10100`, file offset zero, with imposed real-mode segment
context. PSP/stack/device state remains unmodeled. See [DOS guide](https://github.com/morluto/rea/blob/main/docs/providers/ghidra-dos.md).
## Function annotations in Ghidra
Use `inspect_byte_entropy` to triage high- and low-entropy ranges before extraction.
For other headerless images (embedded firmware without ELF/Mach-O/PE/MZ
headers), open with `format: "raw-binary"`, `architecture` (`x86`, `x86_64`,
`arm`, `arm64`, `6502`, or `m68k`), and `entry_point` (`0x` hex). File offset 0 is mapped at
that address. Hopper and IDA refuse this interpretation; Ghidra uses
`BinaryLoader` with a generic language: big-endian for `m68k`, little-endian
otherwise. Big-endian ARM, Thumb-only, and Cortex-M variants are unsupported.
`6502` selects NMOS 6502; the entry and complete file mapping must fit within
`0x0000..0xffff`. 65C02, 65816, cartridge containers, bank switching, and mapper
semantics are unsupported. `m68k` requires an even entry and decodes the 68040
superset of the 68000 family; Amiga/Atari containers and vector-table entry
discovery are unsupported. Windows Ghidra P0 admits raw images only for `6502`. See [raw binary guide](https://github.com/morluto/rea/blob/main/docs/providers/raw-binary.md).
Intel HEX, S-record, UF2 and DfuSe files are containers, not raw images:
`inspect_artifact` lists address regions (UF2 per familyID, DfuSe per element
with its alternate setting), and `extract_artifact` writes each region. Open an
extracted region as `raw-binary` with its start address as `entry_point`. UF2
numbering gaps leave coverage unknown; a DfuSe suffix CRC mismatch follows
`integrity_policy`. See [UF2 and DfuSe](https://github.com/morluto/rea/blob/main/docs/uf2-dfuse.md).
### Function annotations in Ghidra
Fresh-import sessions admit annotations; existing-project reuse is read-only.
Use `annotate_native_function` for one function name and/or entry comments, with at least one explicit change. Review its annotation readback and refreshed dossier inline. Changes are atomic; empty comments clear them, omitted fields preserve them. Later MCP calls observe edits. Normal close saves them when owned Program checkpoint mode is enabled; ephemeral mode discards them. CLI `annotate-native-function` follows the same setting when closing after its updated result. Select the [checkpoint workflow](#continue-editable-ghidra-analysis-across-processes) before edits that must survive a later process. Original executable bytes are unchanged; edits invalidate immutable snapshots. Windows P0 does not admit database mutations. For naming only, `set_address_name` / `set_addresses_names` also work with Ghidra: they name function entries or data addresses (primary labels) and return compact per-address success booleans instead of a dossier. Ghidra rejects batch addresses that request the same name or a name another selected address holds; `raw_result.failures` gives each failure's reason.
@@ -438,39 +784,160 @@ architecture, provider-owned mapping and selected-image commitments. Equal
numeric addresses alone cannot establish correspondence; inspect retained
source Evidence and coverage when the match remains ambiguous.
## Other artifact guides
## Offline PE resource inventory
Read the owning page for offline formats, containers or firmware. These explicit
anchors preserve older links without creating duplicate searchable sections.
Use `inspect_pe_resources` with an absolute `path`, or
`rea inspect-pe-resources ./selected.exe --json`, for portable PE32/PE32+
resource inspection without opening a native target. Preserve returned Evidence:
type/name/language IDs and UTF-16 names, original directory/data/payload ranges,
code pages, payload hashes and static icon-group references. Same-language icon
resolution is explicit; missing references and size mismatch remain visible.
Other payloads are opaque, and this operation does not decode images or simulate
Windows resource loading. Consult the budgets and supported tree profile in
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/guides/native/binary-diagnostics.md).
- <a id="go-build-metadata"></a>[Go build metadata](format-inspectors.md#go-build-metadata)
- <a id="model-files"></a>[Model files](format-inspectors.md#model-files)
- <a id="il2cpp-metadata"></a>[IL2CPP metadata](format-inspectors.md#il2cpp-metadata)
- <a id="rust-dependency-metadata"></a>[Rust dependency metadata](format-inspectors.md#rust-dependency-metadata)
- <a id="pe-authenticode-certificate-tables"></a>[PE Authenticode certificate tables](format-inspectors.md#pe-authenticode-certificate-tables)
- <a id="pe-import-and-delay-load-tables"></a>[PE import and delay-load tables](format-inspectors.md#pe-import-and-delay-load-tables)
- <a id="pe-tls-callbacks-and-load-configuration"></a>[PE TLS callbacks and load configuration](format-inspectors.md#pe-tls-callbacks-and-load-configuration)
- <a id="binary-hardening"></a>[Binary hardening](format-inspectors.md#binary-hardening)
- <a id="elf-note-tables"></a>[ELF note tables](format-inspectors.md#elf-note-tables)
- <a id="apple-ips-crash-reports"></a>[Apple IPS crash reports](format-inspectors.md#apple-ips-crash-reports)
- <a id="linux-kernel-modules"></a>[Linux kernel modules](format-inspectors.md#linux-kernel-modules)
- <a id="cpython-pyc-files"></a>[CPython pyc files](format-inspectors.md#cpython-pyc-files)
- <a id="erlang-beam-files"></a>[Erlang BEAM files](format-inspectors.md#erlang-beam-files)
- <a id="sqlite-snapshots"></a>[SQLite snapshots](format-inspectors.md#sqlite-snapshots)
- <a id="recorded-linux-crashes"></a>[Recorded Linux crashes](format-inspectors.md#recorded-linux-crashes)
- <a id="offline-elf-layout"></a>[Offline ELF layout](format-inspectors.md#offline-elf-layout)
- <a id="offline-wasm"></a>[Offline WASM](format-inspectors.md#offline-wasm)
- <a id="offline-apk-signatures"></a>[Offline APK signatures](format-inspectors.md#offline-apk-signatures)
- <a id="packages-and-extraction"></a>[Packages and extraction](packages-and-archives.md#packages-and-extraction)
- <a id="offline-pe-resource-inventory"></a>[Offline PE resource inventory](format-inspectors.md#offline-pe-resource-inventory)
- <a id="offline-pe-and-elf-export-tables"></a>[Offline PE and ELF export tables](format-inspectors.md#offline-pe-and-elf-export-tables)
- <a id="godot-pck"></a>[Godot PCK](packages-and-archives.md#godot-pck)
- <a id="esp-images"></a>[ESP images](firmware-and-raw-images.md#esp-images)
- <a id="esp-flash-images"></a>[ESP flash images](firmware-and-raw-images.md#esp-flash-images)
- <a id="bun-standalone-executables"></a>[Bun standalone executables](packages-and-archives.md#bun-standalone-executables)
- <a id="release-exposure"></a>[Release exposure](format-inspectors.md#release-exposure)
- <a id="windows-shell-link-shortcuts"></a>[Windows Shell Link shortcuts](format-inspectors.md#windows-shell-link-shortcuts)
- <a id="elf-and-mach-o-initializer-functions"></a>[ELF and Mach-O initializer functions](format-inspectors.md#elf-and-mach-o-initializer-functions)
- <a id="elf-unwind-function-ranges"></a>[ELF unwind function ranges](format-inspectors.md#elf-unwind-function-ranges)
- <a id="linux-kernel-build-config"></a>[Linux kernel build config](format-inspectors.md#linux-kernel-build-config)
- <a id="deno-compile-executables"></a>[Deno compile executables](packages-and-archives.md#deno-compile-executables)
## Offline PE and ELF export tables
Use `inspect_native_exports` with an absolute `path`, or
`rea inspect-native-exports ./libexample.so --json`, to read a PE export
directory or ELF dynamic symbol table on any host without opening a native
target. Ordinal-only, forwarded and hidden-version exports stay distinct;
partial or unsupported coverage means the inventory is incomplete, not that
exports are absent. Use `inspect_macho` for Mach-O. See
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/guides/native/binary-diagnostics.md).
For exact core-module import/export differences, use `compare_wasm_interfaces`
with two complete inspection Evidence records (or current-session retained
references) and explicit scope. Preserve unknown types/duplicate names; static
interface equality is not runtime compatibility. CLI accepts complete inline
Evidence in `rea compare-wasm-interfaces input.json --json`, or exact references
with `--evidence-bundle /absolute/path/evidence.json`. Use the complete parent
ID when the inspection returned a summary.
## Godot PCK
For a standalone PCK, `open_binary` then `inspect_artifact` with
`detail: "summary"` retains the complete static inventory. Use the returned
`parent_evidence_id` with `inspect_analysis_view` inventory occurrences filtered
by exact `logical_paths` or `occurrence_ids`, and join `artifact_id` to nodes
filtered by `artifact_ids`. Select only needed fields; identity links remain.
Prefer `inspect-artifact --detail summary --evidence_output PATH` for CLI tasks;
the same CLI discovers embedded packs via their EOF trailer without opening a
native analysis provider. Normal native opening remains unchanged.
Distinguish declared MD5 from observed SHA-256 and graph/page completeness from
`source_coverage`. Encrypted directories and sparse bundles have unknown entry
coverage; encrypted members have unknown content identity. An empty filtered
page cannot prove absence in unknown coverage. Retained views are historical,
not fresh filesystem checks. No decryption, Godot resource decoding, GDScript
decompilation or target execution is provided. See
[Godot PCK](https://github.com/morluto/rea/blob/main/docs/godot-pck.md).
UnityFS AssetBundle containers (`.bundle`, `.ab`, YooAsset packages and other
byte-identified files) list `CAB-*` SerializedFiles and `.resS` members with
header and node facts; none/LZ4/LZ4HC member blocks stream on demand. Bundles
declaring asset-bundle encryption keep header facts with unknown directory
coverage; LZMA member bytes retain named occurrences with unavailable hashes.
Virtual member offsets are not physical carrier locations. Format 6–8 readers
cache one decoded block and support retained filtered/paged views. No
decryption or SerializedFile object decoding is provided. See
[UnityFS](https://github.com/morluto/rea/blob/main/docs/unityfs.md).
WeChat mini-program packages (`.wxapkg` files with `0xBE`/`0xED` marks) list
members with declared names and absolute carrier offsets; member bytes are
stored verbatim. `V1MMWX` PC-client packages report `encrypted` directory
status with unknown coverage and are refused on extraction; no decryption is
attempted. See
[wxapkg](https://github.com/morluto/rea/blob/main/docs/wxapkg.md).
U-Boot legacy uImages use the same inspection/extraction workflows. Prefer a
retained summary and selected inventory views for header facts or component
IDs. Header addresses/CPU/OS are declarations; payloads remain as stored.
Multi-file components retain their aligned physical ranges; the full payload
is also materialized. Unknown name text retains its original 32 bytes. See
[U-Boot uImage](https://github.com/morluto/rea/blob/main/docs/uimage.md).
## ESP images
For an ESP32-family app or bootloader image, `open_binary` then
`inspect_artifact` with `detail: "summary"`. `root_format_metadata` holds the
observed `header` and `descriptor` (its `kind` is the app or bootloader role)
and the derived `chip`, `checksum`, `appended_sha256` and
`undescribed_trailing` ranges. Segment rows (`segment-NN.bin`) carry
`load_addr`, `classification` (`mapped`, `loaded`, `not-loaded` or `unknown`,
per ESP-IDF `should_map`/`should_load`; RAM ranges are not checked) and
`source_location`; select them with `inspect_analysis_view` and
`logical_paths`. Signature blocks are `unverified`; `public_key_sha256`
matches eFuse key digests.
Unknown `source_coverage` means truncation or undescribed trailing bytes, such
as the flash segments of a `--ram-only-header` image; do not infer absence
from it. Erased flash is described. Integrity mismatches are facts, not
refusals. ESP8266 images are refused, and flash-encrypted images are not
recognized. Segments are not disassembled, and RISC-V or Xtensa segments
cannot be opened as raw-binary. See
[ESP images](https://github.com/morluto/rea/blob/main/docs/esp-image.md).
## ESP flash images
A whole-flash dump opens as `esp-flash-image`. `root_format_metadata` holds
`partition_table` (its `method`: `default`, `caller` or a derived
`scan`), `bootloader` and `apps` ESP image facts, the `otadata` records,
`partitions_outside_dump` and `boot_selection`. Every table has a
`boot_selection`: its `basis` names the bootloader's branch (`no-otadata`
without otadata, `none` when the bootloader rejects the table, `unknown`
when a record is missing), and it lists its `assumptions`;
`running_partition` is always `unknown`. Each image names its entry
`path`. With `detail: "summary"` the root kind is `esp-flash-image-summary`
and each image is a compact `summary` (chip, entry point, segment count,
classified load ranges, signature block count, descriptor identity,
integrity); complete detail keeps every segment. Partition rows
(`partitions/<label>.bin`) carry the declared `type`, `subtype`, names,
`flash_address`, `size` and flags; `encrypted` is a storage flag, not
proof of ciphertext. `written_offset`/`written_end` bound the bytes that
are not erased: an app slot whose facts say `erased` can still hold an older
image, and a limitation says so. For a dump read from another flash address, call
`open_binary` with `format: "esp-flash-image"` and `flash_offset`; an
`esp-image` whose `flash_dump` is set names that offset in a next probe; a
relocated table can be named with `partition_table_offset`. No table found
means unknown, often flash encryption. Extraction writes `nvs_keys` as
ordinary data. See
[ESP flash images](https://github.com/morluto/rea/blob/main/docs/esp-flash-image.md).
## Bun standalone executables
A `bun build --compile` executable stores its module graph in Mach-O
`__BUN,__bun`, a PE or ELF `.bun` section, or an appended trailer.
`open_binary` then `inspect_artifact` reports root format `bun-standalone` with
the carrier, structural layout, module count, entry point, raw flags and
compile exec-argv; each module keeps its `/$bunfs/...` source name, loader,
encoding, sizes and SHA-256, with sourcemap, bytecode and module-info regions as
separate children. Use summary root metadata `entry_point_logical_path` as the
exact inventory `logical_paths` selector, then read the selected content node by
its artifact ID; the reader owns virtual-name normalization and indexed fallback. `extract-artifact` writes stored contents through the safe
output tree; route extracted JavaScript to `analyze_javascript_application`.
Layout labels describe record shapes, not a detected compiler version; unknown
or ambiguous layouts are unsupported. Bytecode is retained, not decoded, and the
executable is never run. See
[Bun standalone executables](https://github.com/morluto/rea/blob/main/docs/bun-standalone.md).
## Deno compile executables
A `deno compile` executable stores its application in a Mach-O `__SUI,d3n0l4nd`
section, an Intel Mach-O `<~sui-data~>` trailer, an ELF `SUI` note of type
`0x53554901`, or a PE `RT_RCDATA` resource named `D3N0L4ND`.
`open_binary` then `inspect_artifact` reports root format `deno-standalone`
with the carrier, entry point, recorded options, and one occurrence per virtual
file, including size and SHA-256. Transpiled output, CJS export analysis, and
source maps are separate occurrences with the file's `index` and their own
`role`. They normally use a `.deno-*` suffix on the file path and move to an
indexed path on a collision, so use the reported `logical_path`. Symlink targets
stay recorded path parts. A virtual filesystem that cannot be listed leaves
coverage unknown.
Environment-file values and CA certificate bytes are retained in local Evidence
with their counts and byte lengths. The npm snapshot length is reported and not
decoded. `extract-artifact` writes
stored files through the safe output tree; route extracted JavaScript to
`analyze_javascript_application`. Other layouts are unsupported, and the
executable is never run. See
[Deno compile executables](https://github.com/morluto/rea/blob/main/docs/deno-standalone.md).
@@ -1,146 +0,0 @@
# Packages and archives
Read only the sections needed for the selected task.
- [Packages and extraction](#packages-and-extraction)
- [Godot PCK](#godot-pck)
- [Bun standalone executables](#bun-standalone-executables)
- [Deno compile executables](#deno-compile-executables)
## Packages and extraction
For PyInstaller executables, the CArchive is the application layer; the native
bootloader is only its prefix. Prefer summary delivery for large inventories,
then retained inventory views with selected member fields/pages. Runtime options
are metadata, not files. Compact delivery still inventories and hashes members.
A small complete inventory can cost less than extra calls and schemas. See the
[Python packaged applications guide](https://github.com/morluto/rea/blob/main/docs/python-packaged-applications.md).
Use `inspect_artifact` for application bundles, archives, ZIP/APK/IPA/MSIX/AppX,
ASAR, or DMG inputs when the artifact graph and findings help answer the
question. It returns the complete artifact graph inline. Cite graph manifest
IDs when using them.
For an IPA or a macOS `.app`, ZIP, or DMG, pass the inventory Evidence to
`project_apple_application_graph`. It reports application roots and nested
bundles by path convention: app extensions, XPC services, frameworks, login
items, system and driver extensions, and plug-ins. It also lists privileged
helpers, launchd plists, symlinks, and each bundle's `info_plist_path` and
executable candidates. Roles are path conventions, not parsed plists; read the
listed plists with `inspect_plist`.
After `open_binary` on a `.app` or Mach-O, use `trace_dylib_resolution` to see
which file each `@rpath`, `@loader_path` and `@executable_path` load reaches for
every executable in the bundle. It also lists missing or weak loads and earlier
`@rpath` candidates that are absent. Narrow large bundles with `roots` or
`architecture`. System paths stay undetermined because the dyld shared cache
provides them.
Use `extract_artifact` when materialized files from a supported format are needed.
It extracts the active target and accepts optional `integrity_policy`; see the [MCP contracts](https://github.com/morluto/rea/blob/main/docs/reference/mcp-contracts.md).
It materializes all regular files into a fresh temporary directory chosen by
REA. Symlinks and encrypted entries are inventory facts, not extractable files.
The result reports the materialized directory; callers do not choose the
destination. No separate permission grant is needed.
For classic iNES cartridges, `open_binary` recognizes the byte signature without
an executable architecture. `inspect_artifact` retains header facts and each
PRG/CHR file unit's offset, length and SHA-256; its nested inventory's
`ines.reconstruction` verifies the actual ordered region bytes against the
whole-file digest. Extraction includes the original header, optional trainer
and uninterpreted trailing bytes. These file units are not mapper/CPU bank
maps. NES 2.0, UNIF, dirty legacy headers and truncated payloads are explicitly
refused. No emulator or 6502 analysis is performed. See the
[iNES guide](https://github.com/morluto/rea/blob/main/docs/ines-containers.md).
Native DMG traversal automatically uses a read-only mount on supported macOS
hosts, with an owned temporary mount directory and cleanup. It needs no approval
flag. Unsupported hosts or mount failures remain explicit limitations; do not
claim child inventory when only root identity is available. DMG extraction is
unavailable through `extract_artifact`. To access DMG files on macOS, create a
read-only mount with your own tools, select files from that mount for analysis,
and detach the mount you created when finished. REA's inspection mount is
temporary, not a retained export of the image's contents.
Use `inspect_byte_entropy` to triage high- and low-entropy ranges before extraction.
## Godot PCK
For a standalone PCK, `open_binary` then `inspect_artifact` with
`detail: "summary"` retains the complete static inventory. Use the returned
`parent_evidence_id` with `inspect_analysis_view` inventory occurrences filtered
by exact `logical_paths` or `occurrence_ids`, and join `artifact_id` to nodes
filtered by `artifact_ids`. Select only needed fields; identity links remain.
Prefer `inspect-artifact --detail summary --evidence_output PATH` for CLI tasks;
the same CLI discovers embedded packs via their EOF trailer without opening a
native analysis provider. Normal native opening remains unchanged.
Distinguish declared MD5 from observed SHA-256 and graph/page completeness from
`source_coverage`. Encrypted directories and sparse bundles have unknown entry
coverage; encrypted members have unknown content identity. An empty filtered
page cannot prove absence in unknown coverage. Retained views are historical,
not fresh filesystem checks. No decryption, Godot resource decoding, GDScript
decompilation or target execution is provided. See
[Godot PCK](https://github.com/morluto/rea/blob/main/docs/godot-pck.md).
UnityFS AssetBundle containers (`.bundle`, `.ab`, YooAsset packages and other
byte-identified files) list `CAB-*` SerializedFiles and `.resS` members with
header and node facts; none/LZ4/LZ4HC member blocks stream on demand. Bundles
declaring asset-bundle encryption keep header facts with unknown directory
coverage; LZMA member bytes retain named occurrences with unavailable hashes.
Virtual member offsets are not physical carrier locations. Format 6–8 readers
cache one decoded block and support retained filtered/paged views. No
decryption or SerializedFile object decoding is provided. See
[UnityFS](https://github.com/morluto/rea/blob/main/docs/unityfs.md).
WeChat mini-program packages (`.wxapkg` files with `0xBE`/`0xED` marks) list
members with declared names and absolute carrier offsets; member bytes are
stored verbatim. `V1MMWX` PC-client packages report `encrypted` directory
status with unknown coverage and are refused on extraction; no decryption is
attempted. See
[wxapkg](https://github.com/morluto/rea/blob/main/docs/wxapkg.md).
U-Boot legacy uImages use the same inspection/extraction workflows. Prefer a
retained summary and selected inventory views for header facts or component
IDs. Header addresses/CPU/OS are declarations; payloads remain as stored.
Multi-file components retain their aligned physical ranges; the full payload
is also materialized. Unknown name text retains its original 32 bytes. See
[U-Boot uImage](https://github.com/morluto/rea/blob/main/docs/uimage.md).
## Bun standalone executables
A `bun build --compile` executable stores its module graph in Mach-O
`__BUN,__bun`, a PE or ELF `.bun` section, or an appended trailer.
`open_binary` then `inspect_artifact` reports root format `bun-standalone` with
the carrier, structural layout, module count, entry point, raw flags and
compile exec-argv; each module keeps its `/$bunfs/...` source name, loader,
encoding, sizes and SHA-256, with sourcemap, bytecode and module-info regions as
separate children. Use summary root metadata `entry_point_logical_path` as the
exact inventory `logical_paths` selector, then read the selected content node by
its artifact ID; the reader owns virtual-name normalization and indexed fallback. `extract-artifact` writes stored contents through the safe
output tree; route extracted JavaScript to `analyze_javascript_application`.
Layout labels describe record shapes, not a detected compiler version; unknown
or ambiguous layouts are unsupported. Bytecode is retained, not decoded, and the
executable is never run. See
[Bun standalone executables](https://github.com/morluto/rea/blob/main/docs/bun-standalone.md).
## Deno compile executables
A `deno compile` executable stores its application in a Mach-O `__SUI,d3n0l4nd`
section, an Intel Mach-O `<~sui-data~>` trailer, an ELF `SUI` note of type
`0x53554901`, or a PE `RT_RCDATA` resource named `D3N0L4ND`.
`open_binary` then `inspect_artifact` reports root format `deno-standalone`
with the carrier, entry point, recorded options, and one occurrence per virtual
file, including size and SHA-256. Transpiled output, CJS export analysis, and
source maps are separate occurrences with the file's `index` and their own
`role`. They normally use a `.deno-*` suffix on the file path and move to an
indexed path on a collision, so use the reported `logical_path`. Symlink targets
stay recorded path parts. A virtual filesystem that cannot be listed leaves
coverage unknown.
Environment-file values and CA certificate bytes are retained in local Evidence
with their counts and byte lengths. The npm snapshot length is reported and not
decoded. `extract-artifact` writes
stored files through the safe output tree; route extracted JavaScript to
`analyze_javascript_application`. Other layouts are unsupported, and the
executable is never run. See
[Deno compile executables](https://github.com/morluto/rea/blob/main/docs/deno-standalone.md).
@@ -1,58 +0,0 @@
# Recorded captures and decoder discovery
## Historical web captures
Retained HAR/SAZ/native mitmproxy/aioquic qlog capture: `inspect_web_network_capture`. Select
original record ordinals when useful; inspect producer fields and byte/number
sidecars without fetching recorded URLs or claiming live attribution. For a decoder heap refusal, follow the reported `capture.decoder_old_generation_mib`
setting and bounds with `update_analysis_settings` (default 192 MiB, floor 8,
maximum 16384); the launch seed
`REA_CAPTURE_DECODER_OLD_GENERATION_MIB` affects future REA processes. Host memory
and `NODE_OPTIONS` do not raise that decoder allowance. See
[historical captures](https://github.com/morluto/rea/blob/main/docs/guides/web/web-network-captures.md) for the exact upstream
profile and credential exclusions. Qlog support starts with aioquic 1.5.0
JSON 0.3; stream coverage and endpoint loss declarations do not recover bodies
or establish network loss. Prefer detail summary, then kind qlog trace/stream
pages; select one trace, stream ID and direction for contribution/range pages.
Facts retain all-recorded-events-before-selection scope. Other qlog layouts
and PCAP correlation are unsupported.
## Packet fields and packet captures
Packet-field discovery: `inspect_packet_fields` searches installed protocols
and field names/types before choosing fields for `inspect_packet_capture`.
Query/page the relevant records; registrations do not establish observed traffic.
## Packet capture inspection
Recorded packet captures: `inspect_packet_capture` with selected TShark fields,
display filters and decode-as rules. Requires Linux, util-linux prlimit and
TShark 4.x on an absolute `PATH` entry or via the optional absolute
`REA_TSHARK_COMMAND` override; see
[capture guide](https://github.com/morluto/rea/blob/main/docs/guides/data/packet-captures.md).
Packet limits count input packets before filtering. Output defaults to 50 matching
frames; use `match_offset` / `match_limit` for later pages, or explicit null
limit for complete matching output. Preserve the source digest across pages.
This does not perform live capture.
For address/DNS/SNI/protocol summaries use `inspect_pcap_capture` with MCP
detail summary, then retained kind pcap pages. Requires Wireshark 4.x tshark
and capinfos, with Linux prlimit or Windows Job Objects; see
[packet summaries](https://github.com/morluto/rea/blob/main/docs/guides/data/pcap-analysis.md).
Select an exact DNS question for address pages; page exhaustion does not prove
network completeness. This reads recordings and does not acquire traffic.
## Signal decoder discovery
Decoder discovery: `inspect_signal_decoders` searches installed decoder IDs
or selected decoder help, including channels and options. Query and page the
relevant details before choosing settings; this does not scan hardware.
## Recorded logic and signals
Recorded logic/signal traces: `decode_signal_recording` with explicit decoder,
channel and protocol settings. On Linux with util-linux prlimit, supply sigrok-cli 0.7.2 through
`REA_SIGROK_COMMAND`; see [recording guide](https://github.com/morluto/rea/blob/main/docs/guides/signals/signal-recordings.md).
Output defaults to 50 literal lines; select annotation classes and page with
`line_offset` / `line_limit`, or explicit null for all remaining output.
Window counts are display lines, not annotations or samples. Preserve source digests across pages.
This reads existing files; it does not acquire hardware signals.
@@ -1,12 +1,5 @@
# Runtime observation
Read only the sections needed for the selected task.
- [Native runtime questions](#native-runtime-questions)
- [Browser and Electron](#browser-and-electron)
- [Who performs the action](#who-performs-the-action)
- [Fiddler Everywhere traffic debugging](#fiddler-everywhere-traffic-debugging)
## Native runtime questions
Start from the requested observation. State the selected target, input, expected
@@ -61,18 +54,6 @@ and [native calls/UI](https://github.com/morluto/rea/blob/main/docs/guides/nativ
## Browser and Electron
Passive traffic capture of a SPA: the default scope is the page's own origin,
and data APIs usually live on another origin. Discover API origins from
absolute URLs in loaded bundles or script text via JavaScript analysis tools
(another passive capture without those origins still scopes to the page
origin, and `completeness.excluded` counts lack URLs). Pass both the page
origin and those API origins in `allowed_origins` — the list replaces the
default scope — and read `completeness.excluded` before reporting that an
API is absent. For long captures with body shapes, use
`export_evidence_bundle` instead of expecting an inline result.
User-owned Electron runtime already open: `list_electron_targets`. Before an electron-main Inspector attempt, check packaged fuses with `inspect_electron_fuses`; disabled `EnableNodeCliInspectArguments` means the app ignores `--inspect`.
Supply the existing browser or Electron instance's literal-port loopback CDP
endpoint in the request. No separate permission configuration or per-call
approval flag is needed. Browser discovery lists eligible HTTP(S) pages;
@@ -2,123 +2,607 @@
name: reverse-engineer-anything
description: Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA, and build agent environments (AgentEnv bundles) of web applications from a live instance or a capture, and of Linux desktop apps with a task from a request. Use shipped-artifact or requested runtime evidence to explain features, compare versions, decompile code, or guide a reconstruction. Skip REA for ordinary source-repository architecture analysis.
metadata:
version: "64"
version: "62"
---
# REA
Use REA when a claim depends on a shipped artifact, decompilation, requested
runtime evidence, or behavior not established by available source. For ordinary
analysis of a complete source repository, use normal repository tools and do not run REA
Use REA when a claim depends on a shipped binary or package, decompilation,
requested runtime evidence, or comparison with
behavior not established by available source. For ordinary analysis of a
complete source repository, use normal repository tools and do not run REA
readiness or provider commands.
## Select the target and authority
Use the target selected for this task. If the user says only "my app", ask which
app before searching artifacts. Resolve a named app to one clear installed
artifact; ask when ambiguous. Unrelated reports and nearby fixtures do not select
it. Never substitute an example or broaden the selected target or runtime actions.
Distinguish observations, derivations, inferences, and unknowns. Cite Evidence
IDs and preserve source identity, coverage, limitations and partial facts.
Static code, strings and declarations do not establish observed execution.
Within the requested investigation, call available analysis tools directly;
REA requires no permission grants or per-call approval flags. The client may
independently require approval; preserve its actual access requirements.
## Connect only when needed
If REA tools are available and registration is not known stale, proceed directly.
Use the connected server's actual tool list and schemas: repository-main guidance
may describe features absent from an older release. Keep complete inline Evidence
when retained references are not advertised. Skill installation alone adds no
MCP server or engines; a Claude Code, Codex or Cursor Marketplace plugin bundles its own server.
Installing this skill supplies instructions; it does not register the REA MCP
server, install analysis engines, or add tools to an already-running agent.
The exception is a Claude Code, Codex, or Cursor Marketplace plugin install:
the plugin bundles this skill and registers its own `rea` server, outside the
state that `rea setup` and `rea doctor` manage.
For absent tools, blocked calls, unavailable providers, host timeouts or startup
failures, read [connection and recovery](references/connection-and-recovery.md)
before repair. Diagnose the observed blocker, continue useful supported work,
and resume the original task after repair. Do not repeat unchanged failed calls.
A host timeout without a REA result does not establish cancellation or cleanup.
Retry a dropped connection once only if safe to repeat or known not started;
if it may have changed the target, or fails again, ask for a client restart.
Retry failed connection startup once; do not retry the same unchanged failure.
If REA tools are available and their registration is not known to be stale,
proceed directly to the target. Do not run diagnostics or setup before every
investigation. Use the connected server's actual tool list and input schemas;
a skill installed from repository main may describe capabilities absent from
an older npm release. Keep complete inline Evidence when that server does not
advertise retained references.
Choose the registration owner: plugin recovery differs from setup-owned repair.
If tools are absent, a call is blocked by the client, or a required provider is
unavailable, read [connection and recovery](references/connection-and-recovery.md).
Diagnose the observed blocker, continue useful supported work, and resume the
original task after repair. Client approval, shell PATH, registration and engine
availability have different remedies; avoid repeating setup for all of them.
If the host times out without a REA result, do not repeat the unchanged call or
assume work stopped. Read the recovery guide's deadline distinction before retrying.
If the MCP connection drops, retry that call once only when it is safe to
repeat or known not to have started. If it may already have changed the
target, or it fails again, tell the user and ask them to restart the MCP
client. If REA could not start its connection, retry once, then tell the user
and ask them to restart the MCP client. Do not retry a startup failure that
returns the same unchanged result. For plugin installs, follow
[plugin recovery](references/connection-and-recovery.md#rea-installed-as-a-client-plugin).
For setup-owned registrations, run `rea doctor` when REA could not start
or tools stay missing after that restart. For `cleanup_incomplete`, a retryable
capacity limit, or `configuration_invalid`, follow the recovery guide's ladder
before asking for a restart. Read that guide before a second repair attempt.
Choose the installation owner before repairing registration. Plugin installs use
[plugin recovery](references/connection-and-recovery.md#rea-installed-as-a-client-plugin);
only setup-owned registrations use the guide's
[registration repair plan](references/connection-and-recovery.md#repair-registration-when-needed).
A plugin-only doctor result of `missing` does not call for `rea setup`.
Setup-owned repair starts with a read-only plan showing paths, backups and skill
replacement before authorized writes. Do not install unrelated software to repair
registration. REA setup never installs/upgrades Node, npm, Homebrew, Java,
Ghidra, IDA, JADX, Kuna, Binwalk or Unblob.
For an unsupported client, use manual stdio with an existing absolute runtime
and entry script, or the CLI fallback in the recovery guide.
## Route before analysis
REA setup never installs or upgrades Node.js, npm, Homebrew, Java, Ghidra, IDA,
JADX, Kuna, Binwalk, or Unblob. Use existing prerequisites; do not install unrelated
software to repair MCP registration. For an unsupported client, use manual stdio
registration with an existing installation’s absolute runtime and entry script, or continue through the CLI.
Read the matching guide before its workflow; read only the relevant sections.
Target-free tools take their explicit paths/endpoints without `open_binary`.
Active-target native/archive tools require it. Archive members and executable
code are different questions; follow the selected artifact's application layer.
A concrete CLI fallback for an operator-supplied JavaScript tree or ASAR is:
| Supplied target or question | Guide and first operation |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Native executable/library, Ghidra/IDA/Kuna, managed PE/CLI or NativeAOT | [Native and managed](references/native-and-artifacts.md): `open_binary`; managed CLI metadata: `inspect_managed_artifact`. Read provider/lifecycle sections before reuse or edits. |
| Headerless/raw firmware, Cortex-M, ESP app/bootloader or whole-flash dump, HEX/S-record/UF2/DfuSe | [Firmware and raw images](references/firmware-and-raw-images.md): check loader support first; raw: explicit `open_binary` format/mapping; ESP/container: `open_binary` then `inspect_artifact`; region scan/extraction: advertised firmware tools. |
| Archive/package inventory or extraction, PyInstaller, Bun/Deno executable, Godot PCK, UnityFS or wxapkg | [Packages and archives](references/packages-and-archives.md): `open_binary`, then `inspect_artifact`; read extraction/mount limits before materializing. |
| ASAR/extracted JavaScript or Electron tree, feature trace or version comparison | [JavaScript applications](references/javascript-applications.md): `analyze_javascript_application`. |
| APK code, manifest, classes, methods or incoming references | [Android applications](references/android-applications.md): `inspect_android_package`; signing/inventory use their separate routes. |
| PE/ELF/Mach-O tables or hardening, Go/Rust/IL2CPP/GPU/model metadata, Windows shortcuts, kernel config, pyc/BEAM, SQLite, WASM, EVM, APK signing or recorded crash | [Offline format inspectors](references/format-inspectors.md): select the named target-free inspector in that format's section. |
| Shipped application exposure for a local app, install directory, ASAR or executable | [Offline format inspectors](references/format-inspectors.md#release-exposure): `audit_release_exposure`; preserve unknown/unassessed checks and marker-only credential findings. |
| Apple plist, Interface Builder, asset catalog or keyed archive resources; unknown desktop installation directory | [Resource and directory inspection](references/native-and-artifacts.md#apple-app-resources): `open_bundle_resources` or `inspect_desktop_application`; no executable/provider required for resource sessions. |
| Native execution, live calls, instrumentation, process effects; browser/Electron runtime or agent-performed actions | [Runtime observation](references/runtime-observation.md): select the observation and declared lifecycle first. Already-open page: `list_browser_targets`; Electron: `list_electron_targets`; agent actions: `capture_browser_scenario`. |
| Recorded HAR/SAZ/mitmproxy/qlog, packet capture, protocol-field discovery or logic/signal recording | [Recorded captures](references/recorded-captures.md): `inspect_web_network_capture`, packet/signal inspection or decoder discovery as appropriate; no live acquisition or recorded-URL fetch. |
| Web AgentEnv bundle from a live instance or capture; task for an exported desktop environment | [Environments](references/environments.md), including its desktop-task section: read before recording/export; `capture_browser_scenario` then `export_environment_bundle` for a live web bundle. |
| Linux desktop application environment built from a user request | [Desktop builds](references/desktop-build.md): drive the selected app on a build computer, then export and witness. |
| Retained Evidence paging/composition, comparisons, unknowns or a broader investigation | [Evidence workflows](references/evidence-workflows.md): use existing source IDs and the smallest view that answers the question. |
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
If a needed tool is unadvertised, inspect `binary_session {}` and
`result.tool_availability` for availability reasons/remediation; never call an
unadvertised tool. Expand group `names`, inheriting inventory client requirements
unless overridden. Their union is the complete in-scope inventory; `tools/list`
owns canonical schemas. The default summary keeps provider candidate status,
code and reason; `omitted_fields` names exclusions. Request `detail: "complete"`
only for remediation/diagnostics, candidate capabilities or tool effect flags.
Candidate facts describe possible providers; top-level capabilities and
`analysis_provider_binding` describe the current route. Keep those scopes distinct.
No MCP registration or native engine is required. Read the returned Evidence,
graph, limitations, and unknowns with the same care as an MCP result; the CLI
returns the Evidence record directly. This fallback does not establish that
MCP is configured. Native CLI tasks still require their selected engine.
Prefer an existing `rea` CLI. When none is installed and REA package acquisition
is within the authorized scope, use `npx -y rea-agents@latest` in its place from a
trusted directory outside the target project, with absolute target paths. Managed
setup binds the current installation directly; retain its runtime and package
cache entry, or restore that installation before repairing its registration.
For setup-owned registration, configuration repair starts with a read-only setup plan; show paths, backups and
skill replacement before applying authorized changes. Preserve existing approval
and target-execution boundaries.
## Route the target first
Choose the first tool from the target the user supplied. Use `open_binary` for
active-target native or archive workflows; target-free tools take their own
explicit path or endpoint and do not need it.
- ASAR or extracted JavaScript/Electron tree:
`analyze_javascript_application`.
- Archive/package member inventory (ZIP/APK/IPA/MSIX/AppX, DMG, or a PyInstaller
executable): `open_binary` with the supplied local path; use `inspect_artifact`
when its graph and findings help answer the question. A PyInstaller CArchive
is the application layer; the bootloader is only the native prefix. Start with
`detail: "summary"` for large inventories, then use retained inventory views
for selected member fields/pages. A complete small inventory can cost less
than extra calls and schemas. Runtime options are metadata, not files.
Compact delivery still inventories and hashes members. See the
[Python packaged applications guide](https://github.com/morluto/rea/blob/main/docs/python-packaged-applications.md).
- Bun `--compile` executable: `open_binary`, then `inspect_artifact`; the Bun
module graph is the application layer. Extract stored modules and analyze the
JavaScript. See [Bun standalone executables](https://github.com/morluto/rea/blob/main/docs/bun-standalone.md).
- Deno `deno compile` executable: `open_binary`, then `inspect_artifact`; the
embedded virtual filesystem is the application layer. Extract stored files and
analyze compatible JavaScript. Environment-file values and CA certificate bytes
are retained in local Evidence with their counts and byte lengths. The npm
snapshot length is reported and not decoded, and the executable is not run. See
[Deno compile executables](https://github.com/morluto/rea/blob/main/docs/deno-standalone.md).
- Apple app plist, Interface Builder, asset catalog, or keyed archive resources:
`open_bundle_resources`, then the selected resource tool. This does not
require `CFBundleExecutable` or a deep provider. Resource sessions have no
executable digest or binary snapshot; use `open_binary` for native code.
- Android APK code, classes, methods, or incoming references:
`inspect_android_package`, then focused Android tools when advertised.
Archive member inventory still uses the archive route above.
- Managed PE/CLI assembly: `inspect_managed_artifact`.
- Firmware image: `inspect_firmware_regions` when advertised. Use
`extract_firmware` when extraction is requested, with a caller-selected new
absolute output directory. These tools use caller-supplied Binwalk/Unblob on
Linux; see the [firmware guide](https://github.com/morluto/rea/blob/main/docs/guides/firmware/firmware-analysis.md).
- ESP32-family app or bootloader image (`0xE9` magic): `inspect_artifact`
without a provider; see the ESP section of `references/native-and-artifacts.md`.
- .NET NativeAOT PE/ELF: native Ghidra analysis. Native code can yield pseudocode;
consult the NativeAOT workflow in `references/native-and-artifacts.md` for
optional metadata recovery and its supported host/layout boundary.
- Native program behavior, live calls, instrumentation, or command-run effects:
choose the matching [native runtime workflow](references/runtime-observation.md#native-runtime-questions).
`open_binary` and `trace_native_values` establish static facts; they do not
observe execution. Runtime tools have distinct launch, attach, mutation and
cleanup effects, and do not require a preliminary static session unless their
own contract says so.
- User-owned browser page already open: `list_browser_targets`.
- Passive traffic capture of a SPA: the default scope is the page's own origin,
and data APIs usually live on another origin. Discover API origins from
absolute URLs in loaded bundles or script text via JavaScript analysis tools
(another passive capture without those origins still scopes to the page
origin, and `completeness.excluded` counts lack URLs). Pass both the page
origin and those API origins in `allowed_origins` — the list replaces the
default scope — and read `completeness.excluded` before reporting that an
API is absent. For long captures with body shapes, use
`export_evidence_bundle` instead of expecting an inline result.
- Browser action the agent must perform (click, type, navigate):
`capture_browser_scenario` with declared actions. See
[runtime observation](references/runtime-observation.md) for connect/launch
and the focused follow-up browser tools.
- Agent environment (AgentEnv bundle) of a web application: read
[environments](references/environments.md) before recording; it records with
`capture_browser_scenario` and writes the bundle with `export_environment_bundle`.
- Task for a desktop application's exported environment, from a
`capture-desktop-application` folder: read
[desktop application tasks](references/environments.md#tasks-for-a-desktop-application-environment).
- Environment of a Linux desktop app with a task, from a request such as
"build me an Inkscape environment where the agent changes a poster's
headline": read [desktop builds](references/desktop-build.md). You drive the
app on a build computer from `start_build_computer`, then export and witness.
- Fiddler Everywhere traffic or a native `.saz` capture: read the
[Fiddler workflow](references/runtime-observation.md#fiddler-everywhere-traffic-debugging).
Inspect saved SAZ with `inspect_web_network_capture` and `format: "saz"`;
connect Everywhere's separate MCP server for live traffic.
- Retained HAR/SAZ/native mitmproxy/aioquic qlog capture: `inspect_web_network_capture`. Select
original record ordinals when useful; inspect producer fields and byte/number
sidecars without fetching recorded URLs or claiming live attribution. HAR/SAZ/qlog
old-generation heap is `REA_CAPTURE_DECODER_OLD_GENERATION_MIB` (default 192 MiB,
floor 8, maximum 16384), set where REA starts. Host memory and `NODE_OPTIONS` do not
raise it. See
[historical captures](https://github.com/morluto/rea/blob/main/docs/guides/web/web-network-captures.md) for the exact upstream
profile and credential exclusions. Qlog support starts with aioquic 1.5.0
JSON 0.3; stream coverage and endpoint loss declarations do not recover bodies
or establish network loss. Prefer detail summary, then kind qlog trace/stream
pages; select one trace, stream ID and direction for contribution/range pages.
Facts retain all-recorded-events-before-selection scope. Other qlog layouts
and PCAP correlation are unsupported.
- User-owned Electron runtime already open: `list_electron_targets`. Before an electron-main Inspector attempt, check packaged fuses with `inspect_electron_fuses`; disabled `EnableNodeCliInspectArguments` means the app ignores `--inspect`.
- Shipped application exposure for a local `.app`, install directory, ASAR, or executable: `audit_release_exposure` / `rea audit-release-exposure`. Each check has a stable id, a status, and fixed remediation. Unknown and unassessed checks stay in the report. This does not establish that an application is secure. Credential-marker hits report the rule id and location only. See [release exposure](https://github.com/morluto/rea/blob/main/docs/release-exposure.md).
- Packet-field discovery: `inspect_packet_fields` searches installed protocols
and field names/types before choosing fields for `inspect_packet_capture`.
Query/page the relevant records; registrations do not establish observed traffic.
- Recorded packet captures: `inspect_packet_capture` with selected TShark fields,
display filters and decode-as rules. Requires Linux, util-linux prlimit and
TShark 4.x on an absolute `PATH` entry or via the optional absolute
`REA_TSHARK_COMMAND` override; see
[capture guide](https://github.com/morluto/rea/blob/main/docs/guides/data/packet-captures.md).
Packet limits count input packets before filtering. Output defaults to 50 matching
frames; use `match_offset` / `match_limit` for later pages, or explicit null
limit for complete matching output. Preserve the source digest across pages.
This does not perform live capture.
For address/DNS/SNI/protocol summaries use `inspect_pcap_capture` with MCP
detail summary, then retained kind pcap pages. Requires Wireshark 4.x tshark
and capinfos, with Linux prlimit or Windows Job Objects; see
[packet summaries](https://github.com/morluto/rea/blob/main/docs/guides/data/pcap-analysis.md).
Select an exact DNS question for address pages; page exhaustion does not prove
network completeness. This reads recordings and does not acquire traffic.
- Decoder discovery: `inspect_signal_decoders` searches installed decoder IDs
or selected decoder help, including channels and options. Query and page the
relevant details before choosing settings; this does not scan hardware.
- Recorded logic/signal traces: `decode_signal_recording` with explicit decoder,
channel and protocol settings. On Linux with util-linux prlimit, supply sigrok-cli 0.7.2 through
`REA_SIGROK_COMMAND`; see [recording guide](https://github.com/morluto/rea/blob/main/docs/guides/signals/signal-recordings.md).
Output defaults to 50 literal lines; select annotation classes and page with
`line_offset` / `line_limit`, or explicit null for all remaining output.
Window counts are display lines, not annotations or samples. Preserve source digests across pages.
This reads existing files; it does not acquire hardware signals.
- Explicit local WebAssembly artifact: `inspect_wasm_artifact`. Supply WABT 1.0.42
through `REA_WABT_BIN_DIRECTORY`; retain exact byte/tool identity and decoded WAT.
Select JavaScript glue and candidate paths explicitly and preserve URL/path
ambiguity. No implicit fetch or target execution; WAT is not original source.
See the [WASM guide](https://github.com/morluto/rea/blob/main/docs/guides/wasm/wasm-artifacts.md).
- Explicit local Android APK (or ZIP/AAB) signing: `inspect_apk_signature`.
Prefer its summary or selected signer/digest/lineage page; retain complete
Evidence and use `inspect_analysis_view` kind `apk-signature` for follow-ups.
Reports which schemes are present (v1 JAR, v2, v3, v3.1), each signer's
certificate SHA-256 fingerprint (v1 included, from its PKCS#7 block), v3 key-rotation lineage, source stamp, and
whether each signed digest still matches the bytes (self-consistency). This
is identity and integrity, not a trust or authenticity verdict.
See the [APK signature guide](https://github.com/morluto/rea/blob/main/docs/apk-signature.md).
- Explicit local EVM bytecode carrier: `inspect_evm_interface` with caller-selected
`raw` or `hex` encoding. The owned worker deadline is `timeout_ms` (default
30000 ms, range 1..2147483647, next call). Select byte, address-space and CPU
allowances separately through `producer_limits`; tighter inherited OS limits
remain enforced, and the 256 MiB worker heap is independent. Preserve
carrier/decoded digests and treat selectors,
argument strings and mutability as inferred candidates; this performs no chain
lookup or target execution. For the complete inferred interface without hex or
duplicate stdout, discover support and select `detail: "summary"`; complete
Evidence stays retained under its original identity. Use the returned
`parent_evidence_id` for exact function or `bytecode_hex`/stdout/stderr pages.
Candidate coverage does not establish interface completeness. See the
[EVM bytecode guide](https://github.com/morluto/rea/blob/main/docs/guides/evm/evm-bytecode.md).
- Embedded Go compiler, module, build-setting metadata, or static `(ptr, len)`
string literals: `inspect_go_binary` with the explicit local file (pass
`include_string_literals: true` and optional `max_string_literals` for bounded
inferred static string-header recovery, and page retained rows with
`inspect_analysis_view` kind `go` collection `string_literals`). This built-in
target-free inspection requires no Go installation or disassembler. Preserve
raw module bytes and source ranges; absent metadata does not prove a binary is
not Go, static string recovery excludes compiler-inlined stack or immediate
strings, and unparsed text records remain explicit. See the
[Go binary guide](https://github.com/morluto/rea/blob/main/docs/guides/native/go-binaries.md).
- Embedded CUDA/HIP code objects: `inspect_gpu_code_objects`; inventory targets
first, then decode selected payloads and query retained GPU Evidence.
- Bundled GGUF or safetensors model declarations: `inspect_model_artifact`
with the explicit local file. Default header hashing leaves full-file identity
unknown; `hash: "full"` additionally reads all weights for SHA-256. MCP returns
retained summaries; optional `view` selects one page in the initial call. Use
`inspect_analysis_view` kind `model-artifact` for
batched metadata keys, tensor names or type totals, selected fields and pages.
Preserve header-scoped indices and array preview omissions; do not infer model
behavior from declared architecture or tensor types. See the
[model file guide](https://github.com/morluto/rea/blob/main/docs/model-artifacts.md).
- Unity IL2CPP names, member owners and string literals from `global-metadata.dat`:
`inspect_il2cpp_metadata` with the explicit local file; MCP defaults to compact
counts. Query sections, images, types, methods, fields, properties, events,
parameters or string_literals through `inspect_analysis_view`
with the returned `parent_evidence_id`, using exact filters, batch indices and
selected fields. Parameters accept `method_indices`, avoiding a scan of all
parameter pages. Missing collections in historical Evidence remain unknown.
Versions 24, 27, 29, 31 and 39; no Unity, IL2CPP
binary or disassembler is run. Unrecognized magic (possibly encrypted) and
other versions are unsupported. Optional matching `binary_path` enables static native addresses and type references; query `method_addresses` with indices or addresses/states, preserving every shared-body method and both artifact identities. Without a linked binary native counts remain unknown. Byte-pattern fallback is inferred; no runtime behavior or same-build pairing is proved. See the
[IL2CPP metadata guide](https://github.com/morluto/rea/blob/main/docs/il2cpp-metadata.md).
- Embedded cargo-auditable Rust dependency metadata: `inspect_rust_binary`
with the explicit local ELF, PE, or thin Mach-O file. This built-in
target-free inspection requires no Rust installation or disassembler. Preserve
package order, dependency indices, and the section file range. A missing
`.dep-v0` section is null metadata, not proof the binary has no dependencies.
A rustc commit hash is an inference from a read-only `/rustc/<40-hex>/` path.
For selected crates, use `view: {collection: "packages", names: [...],
fields: ["name", "version", "source"]}` on the first call; explicitly include
`dependencies` for direct index references. Use `detail: "summary"` for counts
and ranges, or retained `inspect_analysis_view` Rust package/edge pages for
subsequent filters. Keep the complete parent Evidence accessible; CLI compact
delivery requires `--evidence_output`.
See the
[Rust binary guide](https://github.com/morluto/rea/blob/main/docs/rust-binaries.md).
- PE Authenticode certificate table: `inspect_pe_certificates` with the explicit
local PE32/PE32+ file. This built-in target-free inspection requires no
Windows SDK. Preserve WIN_CERTIFICATE ranges, types, payload SHA-256, and
PKCS#7 signer/X.509 identity fields, including `subject_key_identifier_hex`
when the signer uses that form. A missing security directory is observed
absence of that table, not proof the image is unsigned in every catalog sense.
Signatures are not verified. See the
[PE certificate guide](https://github.com/morluto/rea/blob/main/docs/pe-certificates.md).
- PE import and delay-load tables: `inspect_pe_imports` with the explicit
local PE32/PE32+ file. This built-in target-free inspection requires no
Windows SDK. Preserve DLL names, named and ordinal functions, ILT/IAT RVAs,
delay-load descriptors, and the pefile import hash of the standard import
table. A missing import directory is observed absence of that table, not
proof the image never imported APIs through another table. Bound snapshots
and loaded IAT contents remain unobserved. See the
[PE import guide](https://github.com/morluto/rea/blob/main/docs/pe-imports.md).
- Windows Shell Link shortcuts: `inspect_windows_shortcut` with the explicit
local `.lnk` file. This built-in target-free inspection requires no Windows
SDK or COM. Preserve header flags and FILETIMEs, IDList items, LinkInfo
volume and network names, StringData arguments and icon location, ExtraData
signatures, and Tracker droid facts labelled derived. Unknown IDList items
and ExtraData signatures stay raw with SHA-256. The target path is not
resolved or executed. See the
[Windows shortcut guide](https://github.com/morluto/rea/blob/main/docs/guides/native/windows-shortcut.md).
- PE TLS callbacks and load configuration: `inspect_pe_load_config` with the
explicit local PE32/PE32+ file. This built-in target-free inspection requires
no Windows SDK. Preserve callback VAs as strings with their RVA, file offset
and section, and the size-covered security cookie, SafeSEH, CFG,
EH-continuation and XFG fields. Guard-flag mitigation claims are inferred
linker declarations, not proof of enforcement. A missing directory is observed
absence of that declaration. See the
[PE load configuration guide](https://github.com/morluto/rea/blob/main/docs/pe-load-config.md).
- ELF, PE, and Mach-O hardening: `inspect_binary_hardening` with the explicit
local file. This built-in target-free inspection requires no pwntools or other
host tool. One image per Mach-O slice. Each property is `enabled`, `disabled`,
`partial`, `unknown`, or `not_applicable`, with the header field or import
behind it. A missing stack-protector or fortify import is `unknown`, not
proof the protection was omitted. Authenticode and Mach-O code signatures
report presence, not validity. Hardened runtime stays unknown and names
`inspect_signature`. The image is not executed. See the
[binary hardening guide](https://github.com/morluto/rea/blob/main/docs/binary-hardening.md).
- ELF note tables: `inspect_elf_notes` with the explicit local ELF32/ELF64
file. This built-in target-free inspection requires no disassembler. Preserve
PT_NOTE and SHT_NOTE ranges, owners, types, descriptor SHA-256, and GNU
build-id or ABI-tag fields. A missing table is observed absence of those
declarations, not proof the image never carried notes. See the
[ELF notes guide](https://github.com/morluto/rea/blob/main/docs/elf-notes.md).
- Code that runs before `main` or at exit in ELF or Mach-O:
`inspect_native_initializers` with the explicit local file (and
`architecture` for a universal Mach-O). This built-in inspection requires no
disassembler. Preserve slot and target addresses, the `resolution` of each
entry, and its limitations. An `unresolved` slot is not a zero pointer, and a
missing table is not proof that no constructor exists. See the
[initializer guide](https://github.com/morluto/rea/blob/main/docs/guides/native/native-initializers.md).
- ELF unwind function ranges: `inspect_elf_unwind_ranges` with the explicit
local ELF32/ELF64 file. This built-in target-free inspection requires no
disassembler. Preserve FDE start, end, size, containing section, exact
symbol name, CIE index, LSDA, and personality. Page with `offset` and
`limit`. A missing `.eh_frame` is observed absence of that declaration,
not proof the image has no functions. `.debug_frame` is reported present
or absent and is not decoded. CFA instructions are counted and not
evaluated. See the
[ELF unwind ranges guide](https://github.com/morluto/rea/blob/main/docs/guides/native/elf-unwind-ranges.md).
- Apple `.ips` crash reports: `inspect_apple_ips` with the explicit local
file. This built-in target-free inspection requires no Xcode, atos, or
pwntools. Preserve first-line metadata, `bug_type` as a string, process and
exception facts, lossless integer registers, usedImages UUIDs, and
documented classification matches. Only `bug_type` `"309"` yields a crash
body; other types, including `"288"` stackshots, return no crash result.
Select an optional `join_path` for UUID-and-architecture matching to one
local Mach-O or dSYM; preserve slice identity and unjoined reasons. Joined
static addresses do not resolve function names or dSYM symbols. See the
[Apple IPS guide](https://github.com/morluto/rea/blob/main/docs/apple-ips.md).
- Linux kernel module metadata: `inspect_kernel_module` with the explicit local
ELF32/ELF64 relocatable file. This built-in target-free inspection requires
no Linux host or `modinfo`. Preserve `.modinfo` key order and duplicate keys,
`__versions` CRC hex, section file ranges, and the appended signature
trailer's PKCS#7 SHA-256. A missing section is observed absence when section
names are known, not proof the module never carried that metadata.
`struct module` is not decoded. The signature is not validated. gzip, xz, and
zstd containers are unsupported; decompress into a separate file with an
external codec utility, then inspect the decompressed `ET_REL` image. See the
[kernel module guide](https://github.com/morluto/rea/blob/main/docs/kernel-modules.md).
- Linux kernel version and build config (`CONFIG_*` options such as
`CONFIG_SECURITY_SELINUX`): `inspect_kernel_build_config` with the explicit
local `vmlinux`, raw `Image`, x86 `bzImage`, or `configs.ko`. This built-in
target-free inspection requires no Linux host or `extract-ikconfig`. Preserve
the banner version, config SHA-256, option count, and value kinds; use `name`
for one option. An `IKCFG_ST` string is a config only with a valid gzip member
and `IKCFG_ED`. An absent config may mean the kernel was built without
`CONFIG_IKCONFIG`. Non-gzip payloads are unsupported; decompress externally,
then inspect the result. See the
[kernel build config guide](https://github.com/morluto/rea/blob/main/docs/guides/native/kernel-build-config.md).
- CPython 3.7–3.13 `.pyc` header, nested marshal code objects and bytecode
identity: `inspect_python_pyc` with the explicit local file. This built-in
target-free inspection requires no Python installation. Preserve magic,
PEP 552 hash or mtime/size, nested names, and bytecode SHA-256. Bytecode is
not decompiled, and a decoded header does not prove the matching `.py` is
present. See the
[CPython pyc guide](https://github.com/morluto/rea/blob/main/docs/python-pyc.md).
- Erlang BEAM IFF container: `inspect_erlang_beam` with the explicit local
`.beam` file. This built-in target-free inspection requires no Erlang
installation. Preserve chunk names, atom order, export/import/lambda tables,
and the Code header. For selected facts, prefer `detail: "summary"` plus a
`beam` view (collection, names/indices, fields and paging), retaining the full
Evidence. CLI summary requires `--evidence_output`. Missing table counts stay
null; decoded empty tables count zero. Bytecode is not executed. See the
[Erlang BEAM guide](https://github.com/morluto/rea/blob/main/docs/erlang-beam.md).
- Explicit local SQLite database snapshot: `inspect_sqlite_database`. Inspect
with Node.js 24.x >=24.15 or 26+ providing native SQLite limits. Read schema
first (compact inventory by default), or select an exact ordinary table
and `columns` when rows are needed. Defaults are selected-table schema and
20 rows; request `schema_scope: "database"` for the complete stored schema. Preserve both database and existing sibling WAL identity; row samples
report truncation and have unspecified order. Use a quiescent snapshot rather
than assuming a live database/WAL pair was copied atomically. See the
[SQLite guide](https://github.com/morluto/rea/blob/main/docs/guides/data/sqlite-databases.md).
- Explicit Linux ELF file for offline layout, symbols, relocations or static
mitigation evidence: `inspect_binary_layout`. This target-free operation uses
caller-supplied pwntools without opening a disassembler database. Preserve its
raw locations and inference/coverage limits; see the
[offline binary guide](https://github.com/morluto/rea/blob/main/docs/guides/native/binary-diagnostics.md).
- Supplied Linux x86-64 ELF core: `inspect_recorded_crash`. Read every recorded
thread, raw note and source-bound register without opening a live target.
Historical PIDs do not select live processes. Request `include_debugger_context`
only when core-only mapping candidates help; see the
[recorded crash guide](https://github.com/morluto/rea/blob/main/docs/guides/native/recorded-crashes.md).
- PE resource type/name/language trees and static icon group references:
`inspect_pe_resources` with an explicit path. This portable operation requires
no active target or engine; opaque payloads retain file ranges and SHA-256.
- PE export directories and ELF dynamic-symbol exports: `inspect_native_exports`
with an explicit path; portable, no active target or engine.
- Native executable, library, or analysis database: `open_binary`, then
use focused analysis tools directly; call `binary_overview` when metadata or
inventory context is useful and available from the selected provider.
For an existing IDA MCP configuration, use `open_binary` with the original
input binary and `provider_id: "ida"`; do not pass an `.idb` or `.i64` database.
The legacy attached profile binds the already-open GUI input by SHA-256 and
leaves its database open. The modern headless profile creates and closes an
owned private database without saving. Installation and registration reuse
[mrexodia/ida-pro-mcp](https://github.com/mrexodia/ida-pro-mcp); see the
[IDA provider guide](https://github.com/morluto/rea/blob/main/docs/providers/ida-provider.md).
For migration to the separate native IDA MCP plugin, follow the guide's
[legacy attached migration](https://github.com/morluto/rea/blob/main/docs/providers/ida-provider.md#migrate-a-legacy-attached-registration).
Replace the upstream registration explicitly; modern mrexodia tools are not the
native profile. Native attachment preserves the database but does not provide
direct callers; check session availability and the guide's verification limits.
Proceed directly to `analyze_function`, pseudocode, or function/string searches
after opening; IDA does not supply `binary_overview`. Consult session availability
when composing broader workflows. Modern direct callers and unsupported dossier
facets remain unknown; live IDA results are not replayed from snapshots.
With `REA_KUNA_COMMAND` configured, `provider_id: "kuna"` answers procedure
lists, lookups, pseudocode, assembly and `analyze_function` from Kuna's cached
whole-program export. Callers/callees use a separate body-free graph shared by
the session; `xrefs` returns exact-address incoming sites and filters forwarding
aliases. These queries and inventory lookups do not trigger a C export. Indirect
targets remain unknown and can make relationship coverage partial. Reference
caches expire on close; a fresh CLI process repeats the native reference walk.
Typed references, CFG, raw-memory tools and annotations remain unsupported. A cold first
answer can come from a single-function run (its limitations say so); assembly
waits for the export. For pseudocode and assembly, `raw_result.source` names the
cached `.c`/`.asm` file and byte span, which you may search directly instead of
calling per function. See the
[Kuna provider guide](https://github.com/morluto/rea/blob/main/docs/kuna-provider.md).
If the user says only "my app" without a name, path, or target already selected
for this task, ask which app before searching for artifacts. Paths in unrelated
saved reports and nearby test fixtures do not select the user's target. Resolve
an actual human-readable app name to one clear installed artifact when possible;
ask when matches are ambiguous. Never choose an example app on the user's behalf.
In a target-free session, use `open_binary` to bind any archive/package or
native target whose analysis tool operates on the active target. Do not call an
unadvertised tool; inspect `binary_session` with `{}` and its
`result.tool_availability` for availability reasons and remediation. Expand each
group's `names` into entries, inheriting the inventory-level client requirements
unless the group overrides them. The grouped name union is the complete tool
inventory; use `tools/list` for canonical tool schemas. The default summary keeps
each provider candidate's availability and target-support status, code and
reason; `omitted_fields` lists what it leaves out. Request `{"detail": "complete"}`
only when you need an unavailable candidate's remediation, provider
diagnostics, candidate capability sets, or tool surface, effect and annotation
flags; in those closed flag vocabularies, listed
flags are true and omitted known flags are false. Provider candidate facts
describe possible providers, while top-level session capabilities describe the
current route and `analysis_provider_binding` names the selected provider. Do
not treat those scopes as interchangeable.
For editable Ghidra analysis that must survive a later agent process, select
the [owned Program checkpoint workflow](references/native-and-artifacts.md#continue-editable-ghidra-analysis-across-processes)
before making metadata edits. It is separate from Evidence/Unknown persistence.
For a Ghidra startup timeout on a large native target, follow
[deadline recovery](references/native-and-artifacts.md#ghidra-startup-deadline-recovery)
before abandoning deep analysis or repeating the same cold import. The default
330 seconds can be changed for new startup attempts through
`update_analysis_settings` using `ghidra.startup_timeout_ms`. Changing the
server's launch environment instead requires a new REA process.
## Work summary-first
Start with inline Evidence and graph context. For unknown/large JavaScript or ELF
layout, select advertised `detail: "summary"`; MCP `analyze_function` already
defaults to a summary. Small complete results can cost less than extra pages.
Make a focused follow-up only for a specific unanswered question. Reuse matching
session state and Evidence instead of repeating analysis; recheck state when a
lifecycle change makes it useful.
Start with the default result and use its inline Evidence and graph context.
For a JavaScript application or ELF layout whose size is unknown or large,
call `analyze_javascript_application` or `inspect_binary_layout` with
`"detail": "summary"`, then inspect the retained `parent_evidence_id` with
`inspect_analysis_view`. Over MCP, `analyze_function` is summary-first without
a flag: its default reply is the summary, and the complete dossier stays
retained under `parent_evidence_id` for facet paging. Pass that ID as a
`retained-evidence` reference to `compare_functions`, or inside
`match_native_functions` `left_observations`/`right_observations` or
`build_call_path.functions`; request `"detail": "complete"` only when the
dossier must travel inline.
Use the same exact-reference form in other advertised logical Evidence inputs,
including optional sources, arrays and nested sources across domains. Each
source can independently be inline or retained; preserve its source identity,
coverage, partial facts and unknowns. A summary or inspection page has its own
Evidence ID: use `normalized_result.parent_evidence_id` when the consumer needs
the complete source. For portable CLI follow-ups, commands advertising
`--evidence-bundle` resolve references from that selected exported bundle.
See [Evidence composition](references/evidence-workflows.md) for scope and
recovery; file selectors are supported only where explicitly advertised.
Page module identities and select the returned `node_id` before requesting an
item; a single file path can represent several graph roles. Do not repeat an
identical analysis or rejected call without a changed input or resolved blocker.
Recheck session state when a lifecycle change makes that status useful.
Make a focused follow-up only when the
returned result leaves a specific question unanswered.
Complete source Evidence stays accessible through advertised retained references.
A summary/page has its own Evidence ID; use `normalized_result.parent_evidence_id`
when a consumer needs the complete source. Pass `{kind: "retained-evidence",
evidence_id}` in inputs advertising that form; do not resend complete records.
Sources may independently be inline or retained. Read the Evidence guide for
exact filters, bundle-based CLI reuse, comparison roles and missing-source recovery.
If MCP reports `resource_constraint` with `details.resource: "transport"`, call
`inspect_analysis_view` with its reported same-session Evidence reference for a
summary, one section/module, or a stable page. Use `trace_application_feature`
once a seed is known, or call `export_evidence_bundle` to write the complete
session to a selected path. The analysis remains complete in the ledger; avoid
repeating it merely to request the same oversized response. Complete CLI JSON
output also streams without a single MCP frame.
For REA transport refusal or client-truncated output, inspect a smaller retained
view or export complete Evidence. Do not parse a preview as canonical JSON or
repeat the producer. Never invent an Evidence ID; if unknown, find it with
`get_evidence_bundle` summary filters. Retention is bounded and session-local;
it does not establish automatic persistence or complete source coverage.
When the retained parent ID is unknown, use `get_evidence_bundle` with
`detail: "summary"` and exact filters for operation, target SHA-256,
analysis-profile digest or canonical procedure address. Select its returned
Evidence ID for the next view instead of requesting the complete bundle. When
that record has no supported view (for example an annotation), read it with
`detail: "records"` and an `evidence_id` filter; `missing_evidence_links`
names linked sources this session does not retain.
`retention: "complete-record"` means the whole record is stored; native
value-flow truncation and unknown metadata remain separate. For an exported
portable bundle, use `rea inspect-evidence-bundle` with JSON containing `path`,
`detail: "summary"` and the same `filters`. Reading that file still requires
validating its full contents within the local memory budget.
For broad investigations, read the Evidence guide's staged workflow. Track the
questions, source boundaries and unresolved gaps; do not claim completion while
required questions remain open. Inspect packaged resources alongside code when
relevant, and use runtime evidence only within the requested scope.
A client can also truncate a successful tool result without a REA transport
error. Treat that preview as incomplete; use the retained parent ID for smaller
views or export when the full record is needed. Do not parse the preview as
canonical JSON or repeat the producer. For JavaScript, read the
[module-view and trace recipes](references/javascript-applications.md) before
drilling into a large graph; a broad bidirectional trace can expand unrelated
containment relationships even after summary analysis.
Every conclusion must distinguish observations, inferences, and unknowns. Cite
Evidence IDs, preserve limitations and incomplete coverage, and never imply
that static analysis observed execution. Runtime requests execute the declared
target and lifecycle; do not broaden the target or action beyond those fields.
Within the user's requested investigation, call available analysis tools
directly. REA does not require permission grants or per-call approval flags.
The agent client can independently require approval. Follow the declared request
scope and the host's actual access requirements; use the recovery guide for an
observed client-policy rejection.
## Plan broader investigations
For requests that span multiple features or subsystems, use a staged workflow:
1. Turn the request into a checklist of questions and the evidence each answer
needs. Resolve target identity and constraints from the conversation and
workspace before asking for information again.
2. Inspect the current REA session, artifact identity, saved analysis database,
bookmarks, and prior evidence. Reuse matching state; do not open duplicate
sessions or repeat identical analysis.
3. Start with the smallest useful overview or inventory. Follow each question
from its entry point through relevant data and state changes to its result.
Batch related operations around a specific hypothesis, then expand only when
the returned evidence leaves a concrete gap.
4. Inspect relevant packaged resources and configuration alongside code when
they affect the question. Use format-aware inventory and parsers; do not
infer behavior from filenames, strings, or layout alone.
5. Corroborate a conclusion with the evidence type it requires. Use runtime
observation only when static evidence cannot answer the question and the
required host runtime and OS access are available. For JavaScript behavior,
run probes against the actual app through the available browser, Electron,
or process capture workflow.
6. Decompose work into independent questions. When parallel workers are
available and the questions do not depend on one another, assign distinct
scopes, point workers to existing evidence, and ask them to return sources,
conclusions, and unresolved gaps. Otherwise, work sequentially.
7. Keep a concise finding ledger linking each conclusion to Evidence IDs,
confidence/evidence type, search boundary, and remaining unknowns. Update
the shared index or investigation report so later passes can reuse results.
Before finishing, revisit the original checklist. Mark each question as
answered, partially answered, or unresolved based on its evidence; keep
bounded negative searches bounded, and do not describe a broad investigation
as complete while required questions remain open.
## Read only the relevant guide
- Native binaries, managed assemblies, archives, and extraction:
[references/native-and-artifacts.md](references/native-and-artifacts.md)
- ASARs, extracted JavaScript, feature tracing, and version comparison:
[references/javascript-applications.md](references/javascript-applications.md)
- Android APK declarations, classes, methods, and static references:
[references/android-applications.md](references/android-applications.md)
- Native instrumentation, debugger and process capture; browser/Electron
observation; static/runtime comparison:
[references/runtime-observation.md](references/runtime-observation.md)
- Evidence paging, comparisons, residual unknowns, and verification:
[references/evidence-workflows.md](references/evidence-workflows.md)
- Agent environments (AgentEnv bundles) of a web application, recorded from
a live instance or a HAR:
[references/environments.md](references/environments.md)
- Tasks for a desktop application's exported environment:
[references/environments.md](references/environments.md#tasks-for-a-desktop-application-environment)
- A desktop app environment from a request, built and witnessed on build
computers: [references/desktop-build.md](references/desktop-build.md)
## Finish the task
Explain findings in plain language and tie them to returned Evidence. For builds,
use normal coding tools and separate observed behavior from design choices.
Close an opened native/resource session with `close_binary` when no longer needed.
Evidence references and Unknown history remain on the same MCP connection.
End with `reset_investigation`; use `save_path` to checkpoint the complete bundle
before clearing. Import that bundle, including its source closure, to restore
original references and Unknown history in a new process. Editable Ghidra Program
checkpoints are separate; read that workflow before edits needing persistence.
Explain findings in plain language and tie them to returned evidence. When the
user asks to build something, use normal coding tools and separate observed
behavior from design choices. Close an opened native session with
`close_binary` when its engine is no longer needed. Evidence references and Unknown
history remain in memory on the same MCP connection. End the investigation with
`reset_investigation`; supply `save_path` to checkpoint the complete bundle before
clearing. Import the saved bundle to restore original references and Unknown
history in a new process. Retention is bounded and is not automatic persistence.
@@ -1,23 +1,11 @@
# Connection and task recovery
Read only the sections needed for the selected task.
- [Choose the remedy from the observed failure](#choose-the-remedy-from-the-observed-failure)
- [Distinguish client and server deadlines](#distinguish-client-and-server-deadlines)
- [Recovery ladder](#recovery-ladder)
- [REA installed as a client plugin](#rea-installed-as-a-client-plugin)
- [Repair registration when needed](#repair-registration-when-needed)
- [Codex tool approval is separate from REA readiness](#codex-tool-approval-is-separate-from-rea-readiness)
- [Repair an editable REA limit](#repair-an-editable-rea-limit)
- [Continue through an installed CLI](#continue-through-an-installed-cli)
Read this guide only for an observed connection, command, approval or provider
blocker. Keep the user's target and question in view. Explain what prevented the
operation and the next useful step; after repair, continue the investigation.
Commands below use an installed `rea`. If none is available, use the package
invocation under [Continue through an installed CLI](#continue-through-an-installed-cli)
when acquiring REA is authorized. Follow the connected
invocation in `SKILL.md` when acquiring REA is authorized. Follow the connected
server's schemas and the installed CLI's help rather than assuming repository
main and an older release expose the same arguments. Tool-name prefixes belong
to the client: find the canonical REA operation in its actual tool listing and
@@ -234,25 +222,6 @@ not edit host deadlines, response buffers, registrations or fixed limits.
## Continue through an installed CLI
A concrete CLI fallback for an operator-supplied JavaScript tree or ASAR is:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
No MCP registration or native engine is required. Read the returned Evidence,
graph, limitations, and unknowns with the same care as an MCP result; the CLI
returns the Evidence record directly. This fallback does not establish that
MCP is configured. Native CLI tasks still require their selected engine.
Prefer an existing `rea` CLI. When none is installed and REA package acquisition
is within the authorized scope, use `npx -y rea-agents@latest` in its place from a
trusted directory outside the target project, with absolute target paths. Managed
setup binds the current installation directly; retain its runtime and package
cache entry, or restore that installation before repairing its registration.
For setup-owned registration, configuration repair starts with a read-only setup plan; show paths, backups and
skill replacement before applying authorized changes. Preserve existing approval
and target-execution boundaries.
Check the command environment only if the launcher fails:
```bash
@@ -1,13 +1,5 @@
# Agent environments from a live application
Read only the sections needed for the selected task.
- [Before recording](#before-recording)
- [Record a session an environment can learn writes from](#record-a-session-an-environment-can-learn-writes-from)
- [Check coverage](#check-coverage)
- [Export and hand off](#export-and-hand-off)
- [Tasks for a desktop application environment](#tasks-for-a-desktop-application-environment)
Use this when the user asks for an agent environment (an AgentEnv bundle) of a
web application: a folder `agent-env` deploys, whose MCP tools answer from what
one recording of the application observed and serve the writes that recording
@@ -1,25 +1,7 @@
# Evidence, comparison, and verification workflows
Read only the sections needed for the selected task.
- [Read and compose source Evidence](#read-and-compose-source-evidence)
- [Inventory and decompilation views](#inventory-and-decompilation-views)
- [Unknowns and comparisons](#unknowns-and-comparisons)
- [Process captures and environment views](#process-captures-and-environment-views)
- [Native export table pages](#native-export-table-pages)
- [Recover oversized delivery and find retained Evidence](#recover-oversized-delivery-and-find-retained-evidence)
- [Plan broader investigations](#plan-broader-investigations)
## Read and compose source Evidence
Evidence-producing MCP tools return Evidence directly in `structuredContent`.
CLI complete JSON returns Evidence directly. CLI summary delivery through
`--evidence_output` returns `{summary, evidence_bundle}`: `summary` is the view's
Evidence, and `evidence_bundle` is the export receipt with its path and byte count.
Read `summary.normalized_result.parent_evidence_id` for the complete source ID;
pass the receipt's path to `--evidence-bundle` on a later supported CLI command.
Advertised summary delivery retains the complete source separately;
complete inline delivery is available where the connected contract says so. Read `normalized_result` for operation data,
Evidence-producing tools return the complete Evidence record directly in MCP
`structuredContent` and CLI JSON. Read `normalized_result` for operation data,
and cite `evidence_id` when another tool or your explanation needs a stable
reference. Preserve authority, limitations, coverage, and residual unknowns. If a result
is incomplete or paginated, continue only when the remaining data matters to
@@ -67,8 +49,6 @@ An input bundle grants no implicit file lookup: `evidence-file` and historical
graph file selectors are accepted only by contracts advertising them. See the
[CLI reuse guide](https://github.com/morluto/rea/blob/main/docs/getting-started/cli.md#import-export-and-compare-evidence).
## Inventory and decompilation views
Completed `inspect_artifact` inventory also supports a summary and
`view: {kind: "inventory", collection: "occurrences", logical_paths: ["package.json", "main.js"]}`.
Omit `logical_paths` to page all available occurrences; `[]` selects none.
@@ -101,8 +81,6 @@ exact `where.indices`, `addresses`, `procedure_addresses` or `statuses`. Add
`collection: "pseudocode"` with one row's `index` to page a large body. Batch
limitations aggregate every item, so do not attribute them to one successful row.
## Unknowns and comparisons
Use `record_unknown` to track unresolved questions in the user's investigation
and name the authority or environment still required. Supply supporting and
contradicting evidence IDs. It updates the session registry directly.
@@ -133,8 +111,6 @@ or absence:
- `verify_reconstruction` evaluates a finite typed specification. A pass covers
only declared comparable claims, not global equivalence.
## Process captures and environment views
Process captures are opt-in behavioral evidence, not a security sandbox. V3
captures cannot be upgraded to V4; rerun the original scenario. Distinguish root
exit from descendant settlement and require freshness when the task needs it.
@@ -188,8 +164,6 @@ catalog and state size checks. `rea environment-build <capture.har> <dir>`
inspects, projects and exports in one step and prints the same coverage, or
`null` when the bundle serves replay.
## Native export table pages
For a PE/ELF export question, `inspect_native_exports` accepts `detail: "summary"`
and retains the complete table. Query `inspect_analysis_view` with
`view: {kind: "native-exports", collection: "exports", names: ["selected_name"],
@@ -200,69 +174,3 @@ coverage describes selected retained rows; `summary.coverage` or `exports.covera
still describes the source table, so an empty partial page does not prove absence.
CLI `inspect-native-exports --detail summary --evidence_output PATH` saves the
complete bundle for later `inspect-analysis-view --evidence-bundle PATH` requests.
## Recover oversized delivery and find retained Evidence
If MCP reports `resource_constraint` with `details.resource: "transport"`, call
`inspect_analysis_view` with its reported same-session Evidence reference for a
summary, one section/module, or a stable page. Use `trace_application_feature`
once a seed is known, or call `export_evidence_bundle` to write the complete
session to a selected path. The analysis remains complete in the ledger; avoid
repeating it merely to request the same oversized response. Complete CLI JSON
output also streams without a single MCP frame.
When the retained parent ID is unknown, use `get_evidence_bundle` with
`detail: "summary"` and exact filters for operation, target SHA-256,
analysis-profile digest or canonical procedure address. Select its returned
Evidence ID for the next view instead of requesting the complete bundle. When
that record has no supported view (for example an annotation), read it with
`detail: "records"` and an `evidence_id` filter; `missing_evidence_links`
names linked sources this session does not retain.
`retention: "complete-record"` means the whole record is stored; native
value-flow truncation and unknown metadata remain separate. For an exported
portable bundle, use `rea inspect-evidence-bundle` with JSON containing `path`,
`detail: "summary"` and the same `filters`. Reading that file still requires
validating its full contents within the local memory budget.
A client can also truncate a successful tool result without a REA transport
error. Treat that preview as incomplete; use the retained parent ID for smaller
views or export when the full record is needed. Do not parse the preview as
canonical JSON or repeat the producer. For JavaScript, read the
[module-view and trace recipes](javascript-applications.md) before
drilling into a large graph; a broad bidirectional trace can expand unrelated
containment relationships even after summary analysis.
## Plan broader investigations
For requests that span multiple features or subsystems, use a staged workflow:
1. Turn the request into a checklist of questions and the evidence each answer
needs. Resolve target identity and constraints from the conversation and
workspace before asking for information again.
2. Inspect the current REA session, artifact identity, saved analysis database,
bookmarks, and prior evidence. Reuse matching state; do not open duplicate
sessions or repeat identical analysis.
3. Start with the smallest useful overview or inventory. Follow each question
from its entry point through relevant data and state changes to its result.
Batch related operations around a specific hypothesis, then expand only when
the returned evidence leaves a concrete gap.
4. Inspect relevant packaged resources and configuration alongside code when
they affect the question. Use format-aware inventory and parsers; do not
infer behavior from filenames, strings, or layout alone.
5. Corroborate a conclusion with the evidence type it requires. Use runtime
observation only when static evidence cannot answer the question and the
required host runtime and OS access are available. For JavaScript behavior,
run probes against the actual app through the available browser, Electron,
or process capture workflow.
6. Decompose work into independent questions. When parallel workers are
available and the questions do not depend on one another, assign distinct
scopes, point workers to existing evidence, and ask them to return sources,
conclusions, and unresolved gaps. Otherwise, work sequentially.
7. Keep a concise finding ledger linking each conclusion to Evidence IDs,
confidence/evidence type, search boundary, and remaining unknowns. Update
the shared index or investigation report so later passes can reuse results.
Before finishing, revisit the original checklist. Mark each question as
answered, partially answered, or unresolved based on its evidence; keep
bounded negative searches bounded, and do not describe a broad investigation
as complete while required questions remain open.
@@ -1,76 +0,0 @@
# Firmware and raw images
## Firmware regions and extraction
Use `inspect_firmware_regions` when advertised. Use `extract_firmware` when
extraction is requested, with a caller-selected new absolute output directory.
These tools use caller-supplied Binwalk/Unblob on Linux; see the
[firmware guide](https://github.com/morluto/rea/blob/main/docs/guides/firmware/firmware-analysis.md).
## Headerless raw images and firmware containers
For other headerless images (embedded firmware without ELF/Mach-O/PE/MZ
headers), open with `format: "raw-binary"`, `architecture` (`x86`, `x86_64`,
`arm`, `arm64`, `6502`, or `m68k`), and `entry_point` (`0x` hex). File offset 0 is mapped at
that address. Hopper and IDA refuse this interpretation; Ghidra uses
`BinaryLoader` with a generic language: big-endian for `m68k`, little-endian
otherwise. Big-endian ARM, Thumb-only, and Cortex-M variants are unsupported.
`6502` selects NMOS 6502; the entry and complete file mapping must fit within
`0x0000..0xffff`. 65C02, 65816, cartridge containers, bank switching, and mapper
semantics are unsupported. `m68k` requires an even entry and decodes the 68040
superset of the 68000 family; Amiga/Atari containers and vector-table entry
discovery are unsupported. Windows Ghidra P0 admits raw images only for `6502`. See [raw binary guide](https://github.com/morluto/rea/blob/main/docs/providers/raw-binary.md).
Intel HEX, S-record, UF2 and DfuSe files are containers, not raw images:
`inspect_artifact` lists address regions (UF2 per familyID, DfuSe per element
with its alternate setting), and `extract_artifact` writes each region. Open an
extracted region as `raw-binary` with its start address as `entry_point`. UF2
numbering gaps leave coverage unknown; a DfuSe suffix CRC mismatch follows
`integrity_policy`. See [UF2 and DfuSe](https://github.com/morluto/rea/blob/main/docs/uf2-dfuse.md).
## ESP images
For an ESP32-family app or bootloader image, `open_binary` then
`inspect_artifact` with `detail: "summary"`. `root_format_metadata` holds the
observed `header` and `descriptor` (its `kind` is the app or bootloader role)
and the derived `chip`, `checksum`, `appended_sha256` and
`undescribed_trailing` ranges. Segment rows (`segment-NN.bin`) carry
`load_addr`, `classification` (`mapped`, `loaded`, `not-loaded` or `unknown`,
per ESP-IDF `should_map`/`should_load`; RAM ranges are not checked) and
`source_location`; select them with `inspect_analysis_view` and
`logical_paths`. Signature blocks are `unverified`; `public_key_sha256`
matches eFuse key digests.
Unknown `source_coverage` means truncation or undescribed trailing bytes, such
as the flash segments of a `--ram-only-header` image; do not infer absence
from it. Erased flash is described. Integrity mismatches are facts, not
refusals. ESP8266 images are refused, and flash-encrypted images are not
recognized. Segments are not disassembled, and RISC-V or Xtensa segments
cannot be opened as raw-binary. See
[ESP images](https://github.com/morluto/rea/blob/main/docs/esp-image.md).
## ESP flash images
A whole-flash dump opens as `esp-flash-image`. `root_format_metadata` holds
`partition_table` (its `method`: `default`, `caller` or a derived
`scan`), `bootloader` and `apps` ESP image facts, the `otadata` records,
`partitions_outside_dump` and `boot_selection`. Every table has a
`boot_selection`: its `basis` names the bootloader's branch (`no-otadata`
without otadata, `none` when the bootloader rejects the table, `unknown`
when a record is missing), and it lists its `assumptions`;
`running_partition` is always `unknown`. Each image names its entry
`path`. With `detail: "summary"` the root kind is `esp-flash-image-summary`
and each image is a compact `summary` (chip, entry point, segment count,
classified load ranges, signature block count, descriptor identity,
integrity); complete detail keeps every segment. Partition rows
(`partitions/<label>.bin`) carry the declared `type`, `subtype`, names,
`flash_address`, `size` and flags; `encrypted` is a storage flag, not
proof of ciphertext. `written_offset`/`written_end` bound the bytes that
are not erased: an app slot whose facts say `erased` can still hold an older
image, and a limitation says so. For a dump read from another flash address, call
`open_binary` with `format: "esp-flash-image"` and `flash_offset`; an
`esp-image` whose `flash_dump` is set names that offset in a next probe; a
relocated table can be named with `partition_table_offset`. No table found
means unknown, often flash encryption. Extraction writes `nvs_keys` as
ordinary data. See
[ESP flash images](https://github.com/morluto/rea/blob/main/docs/esp-flash-image.md).
@@ -1,449 +0,0 @@
# Offline format and metadata inspection
Read only the sections needed for the selected task.
- [Release exposure](#release-exposure)
- [Go build metadata](#go-build-metadata)
- [Model files](#model-files)
- [IL2CPP metadata](#il2cpp-metadata)
- [Rust dependency metadata](#rust-dependency-metadata)
- [PE Authenticode certificate tables](#pe-authenticode-certificate-tables)
- [PE import and delay-load tables](#pe-import-and-delay-load-tables)
- [Windows Shell Link shortcuts](#windows-shell-link-shortcuts)
- [PE TLS callbacks and load configuration](#pe-tls-callbacks-and-load-configuration)
- [Binary hardening](#binary-hardening)
- [ELF note tables](#elf-note-tables)
- [ELF and Mach-O initializer functions](#elf-and-mach-o-initializer-functions)
- [ELF unwind function ranges](#elf-unwind-function-ranges)
- [Apple IPS crash reports](#apple-ips-crash-reports)
- [Linux kernel modules](#linux-kernel-modules)
- [Linux kernel build config](#linux-kernel-build-config)
- [CPython pyc files](#cpython-pyc-files)
- [Erlang BEAM files](#erlang-beam-files)
- [SQLite snapshots](#sqlite-snapshots)
- [Recorded Linux crashes](#recorded-linux-crashes)
- [Offline ELF layout](#offline-elf-layout)
- [Offline WASM](#offline-wasm)
- [Offline APK signatures](#offline-apk-signatures)
- [Offline PE resource inventory](#offline-pe-resource-inventory)
- [Offline PE and ELF export tables](#offline-pe-and-elf-export-tables)
- [EVM bytecode](#evm-bytecode)
- [Embedded GPU code objects](#embedded-gpu-code-objects)
## Release exposure
Use `audit_release_exposure` (`rea audit-release-exposure PATH`) when the question is what static analysis of a shipped `.app`, install directory, ASAR, or executable recovers. Each check has a stable id, a status, the observation behind it, and fixed remediation. Unknown and unassessed checks stay in the report. The report does not establish that the application is secure.
`text.credential_markers` reports a rule id and location only. Matched bytes, previews, lengths, and hashes of matched bytes are omitted.
`native.hardening`, `macho.hardened_runtime`, and `android.manifest` are not assessed here. Their next probes are `inspect_binary_hardening`, `inspect_signature`, and `inspect_android_package`.
See [release exposure](https://github.com/morluto/rea/blob/main/docs/release-exposure.md).
## Go build metadata
Use `inspect_go_binary` for an explicit local ELF, PE or Mach-O (universal
files need `architecture`) when the
question concerns the embedded compiler version, module/dependency versions,
local replacements, build settings, or static Go string literals packed without
NUL terminators. Pass `include_string_literals: true`
(`--include-string-literals`, optional `max_string_literals` /
`--max-string-literals`) to recover bounded inferred word-aligned `(ptr, len)`
UTF-8 string slices from read-only data and page them through `go`
`string_literals` views. No `open_binary`, Go installation or
target execution is required. The CLI equivalent is `inspect-go-binary PATH`.
Preserve artifact SHA-256, original file ranges, framed module bytes and every
unparsed module-text line. A missing record is an unknown, not proof of a
non-Go binary; observed metadata does not authenticate its claims, and static
`(ptr, len)` string recovery excludes compiler-inlined stack/immediate strings
without proving runtime reachability. Function,
source and type recovery remain a separate native-analysis question.
See the [Go binary guide](https://github.com/morluto/rea/blob/main/docs/guides/native/go-binaries.md).
## Model files
Optional `view` selects one page in the initial call. Batch metadata keys or
tensor names and select only needed fields; do not infer model behavior from
declared architecture or tensor types.
Use `inspect_model_artifact` (`inspect-model-artifact PATH`) for local GGUF v2/v3
and safetensors declarations without a model runtime. Header-only mode does not
read weights or establish full-file identity. `hash: "full"` explicitly streams
all bytes. Retained `model-artifact` views page `metadata`, `tensors` and derived
`types`, with exact batch filters and selected fields. Arrays retain typed counts
and eight-element previews; omitted values, unknown types and runtime behavior
stay explicit. See [model files](https://github.com/morluto/rea/blob/main/docs/model-artifacts.md).
## IL2CPP metadata
Use `inspect_il2cpp_metadata` for an explicit local Unity `global-metadata.dat`
(versions 24, 27, 29, 31 and 39) when the question concerns assembly images,
names, member owners, string literals, namespaces or index ranges. No `open_binary`, Unity installation or target
execution is required. The CLI equivalent is `inspect-il2cpp-metadata PATH`.
MCP defaults to counts with complete retained Evidence. Query `sections`,
`images`, `types`, `methods`, `fields`, `properties`, `events`, `parameters` or
`string_literals` through `inspect_analysis_view`; use exact names,
batch indices and selected fields. Row indices are scoped by artifact SHA-256
and collection; page offsets count filtered matches. Parameters support batch
`method_indices` to read one method's parameters directly. Historical reports
without member collections retain unknown counts rather than empty inventories. Preserve
artifact SHA-256 and index widths. Unrecognized magic may mean encryption and
is unsupported, not decrypted.
Optional matching `binary_path` enables static native addresses and type
references. Query `method_addresses` with indices or addresses/states, preserving
every shared-body method and both artifact identities. Without a linked binary,
native counts remain unknown. Byte-pattern fallback is inferred; no runtime
behavior or same-build pairing is proved.
See [IL2CPP metadata](https://github.com/morluto/rea/blob/main/docs/il2cpp-metadata.md).
## Rust dependency metadata
For selected crates, use `view: {collection: "packages", names: [...],
fields: ["name", "version", "source"]}` on the first call. Include `dependencies`
explicitly for direct index references. Select `detail: "summary"` for counts and
ranges; retained Rust package/edge views support later filters. Keep complete
parent Evidence accessible; CLI compact delivery requires `--evidence_output`.
Use `inspect_rust_binary` for an explicit local ELF, PE, or Mach-O (universal
files need `architecture`) when
the question concerns the cargo-auditable crate list embedded in `.dep-v0`
(Mach-O: `__DATA,.dep-v0`). No `open_binary`, Rust installation, or target
execution is required. The CLI equivalent is `inspect-rust-binary PATH`.
Preserve artifact SHA-256, package order, dependency indices, and the section
file range. A missing section is null metadata, not proof the binary has no
dependencies. An omitted package kind stays null. A rustc commit hash is
inferred from a read-only `/rustc/<40-hex>/` path and does not by itself
identify the compiler. See [Rust binaries](https://github.com/morluto/rea/blob/main/docs/rust-binaries.md).
## PE Authenticode certificate tables
Use `inspect_pe_certificates` for an explicit local PE32/PE32+ image when the
question concerns the Authenticode certificate table, WIN_CERTIFICATE types, or
embedded PKCS#7 signer and X.509 identity fields. No `open_binary`, Windows SDK,
or target execution is required. The CLI equivalent is
`inspect-pe-certificates PATH`. Preserve artifact SHA-256, table file ranges,
payload hashes, original validity-time spellings, and
`subject_key_identifier_hex` when the signer uses that form. A missing
`IMAGE_DIRECTORY_ENTRY_SECURITY` directory is observed absence of that table,
not proof the image is unsigned in every catalog or overlay sense. Signatures,
chains, timestamps, and Authenticode digest coverage remain unobserved. See
[PE Authenticode certificate tables](https://github.com/morluto/rea/blob/main/docs/pe-certificates.md).
## PE import and delay-load tables
Use `inspect_pe_imports` for an explicit local PE32/PE32+ image when the
question concerns imported DLL and function names, ordinals, delay-load
descriptors, or the pefile import hash. No `open_binary`, Windows SDK, or
target execution is required. The CLI equivalent is `inspect-pe-imports PATH`.
Preserve artifact SHA-256, directory file ranges, original DLL spellings, and
ILT/IAT RVAs. A missing `IMAGE_DIRECTORY_ENTRY_IMPORT` directory is observed
absence of that table, not proof the image never imported APIs through another
table or overlay. Bound snapshots, loaded IAT contents, and export-forward
resolution remain unobserved. See
[PE import and delay-load tables](https://github.com/morluto/rea/blob/main/docs/pe-imports.md).
## Windows Shell Link shortcuts
Use `inspect_windows_shortcut` for an explicit local `.lnk` file when the
question concerns the recorded target path, arguments, working directory, icon
location, volume serial, network share names, ExtraData blocks, or Tracker
droid identifiers. No `open_binary`, Windows SDK, COM, or target execution is
required. The CLI equivalent is `inspect-windows-shortcut PATH`. Preserve
artifact SHA-256, FILETIME raw decimals, IDList type bytes, and ExtraData
signatures. Unknown IDList items and ExtraData signatures remain raw with
SHA-256; they are not guessed. Version-1 UUID MAC and timestamp values are
labelled derived. A missing optional section is observed absence of that
structure. See
[Windows Shell Link shortcuts](https://github.com/morluto/rea/blob/main/docs/guides/native/windows-shortcut.md).
## PE TLS callbacks and load configuration
Preserve each callback's RVA, file offset and section alongside its VA and the
size-covered declaration fields. A missing directory is observed absence of
that declaration.
Use `inspect_pe_load_config` for an explicit local PE32/PE32+ image when the
question concerns code that runs before the entry point through TLS callbacks,
or the security cookie, SafeSEH, CFG, EH-continuation and XFG declarations in
the load configuration. No `open_binary`, Windows SDK, or target execution is
required. The CLI equivalent is `inspect-pe-load-config PATH`. Preserve artifact
SHA-256, directory and callback-array file ranges, and callback VAs as
hexadecimal strings. Fields outside the declared `Size` are `null`. Guard-flag
mitigations carry `evidence_kind: "inferred"`; enforcement remains unobserved.
See
[PE TLS callbacks and load configuration](https://github.com/morluto/rea/blob/main/docs/pe-load-config.md).
## Binary hardening
Each property reports `enabled`, `disabled`, `partial`, `unknown`, or
`not_applicable`, with the header field or import behind it.
Use `inspect_binary_hardening` for an explicit local ELF, PE, or Mach-O file
when the question concerns PIE or ASLR, NX, RELRO, stack-protector or FORTIFY
signs, control-flow flags, or stripped state. No `open_binary`, pwntools, or
target execution is required. The CLI equivalent is
`inspect-binary-hardening PATH`. Universal Mach-O files return one image per
slice. A missing import is `unknown`, not proof the protection was omitted.
Authenticode and Mach-O code-signature rows report presence, not validity.
`hardened_runtime` stays unknown and names `inspect_signature`. See
[binary hardening](https://github.com/morluto/rea/blob/main/docs/binary-hardening.md).
## ELF note tables
Retain each note's owner, type, descriptor SHA-256, and PT_NOTE/SHT_NOTE ranges.
Use `inspect_elf_notes` for an explicit local ELF32/ELF64 image when the
question concerns PT_NOTE/SHT_NOTE owners, GNU build-id, or GNU ABI-tag
fields. No `open_binary` or target execution is required. The CLI equivalent
is `inspect-elf-notes PATH`. Preserve artifact SHA-256, table file ranges,
descriptor hashes, and original owner spellings. A missing table is observed
absence of those declarations, not proof the image never carried notes in
another container. See
[ELF note tables](https://github.com/morluto/rea/blob/main/docs/elf-notes.md).
## ELF and Mach-O initializer functions
Use `inspect_native_initializers` for an explicit local ELF or Mach-O image
when the question is which functions run before `main` or at exit: ELF
`DT_PREINIT_ARRAY`, `DT_INIT`, `DT_INIT_ARRAY`, `DT_FINI`, `DT_FINI_ARRAY` and
legacy `.ctors`/`.dtors`, or Mach-O `__mod_init_func`, `__init_offsets` and
`__mod_term_func`. Pass `architecture` for a universal Mach-O. The CLI
equivalent is `inspect-native-initializers PATH`. PIE slots are resolved
through RELATIVE, RELR and section relocations, and Mach-O slots through
chained fixups. Keep each entry's `resolution`: `unresolved` means the value was
not established, and `unresolved_import` names an external symbol. Page with
`offset` and `limit`. See
[ELF and Mach-O initializer functions](https://github.com/morluto/rea/blob/main/docs/guides/native/native-initializers.md).
## ELF unwind function ranges
Use `inspect_elf_unwind_ranges` for an explicit local ELF32/ELF64 image when
the question concerns function starts and lengths that survive `strip`, which
functions have an LSDA, or which ranges have no exact symbol. No
`open_binary` or target execution is required. The CLI equivalent is
`inspect-elf-unwind-ranges PATH`. Preserve artifact SHA-256, FDE start and
end, containing section, exact symbol name, CIE index, LSDA, and personality.
Page later ranges with `offset` and `limit`. A missing `.eh_frame` is
observed absence of that declaration and of `PT_GNU_EH_FRAME`, not proof the
image has no functions. Header disagreement is a limitation and does not drop
decoded ranges. `.debug_frame` is reported present or absent and is not
decoded. CFA instructions are counted and not evaluated. Indirect EH pointers
are not dereferenced. See
[ELF unwind function ranges](https://github.com/morluto/rea/blob/main/docs/guides/native/elf-unwind-ranges.md).
## Apple IPS crash reports
Use `inspect_apple_ips` for an explicit local Apple `.ips` file when the
question concerns crash metadata, exception and termination facts, threads,
usedImages, lossless registers, or documented crash classes. No `open_binary`,
Xcode, atos, or target execution is required. The CLI equivalent is
`inspect-apple-ips PATH`. Preserve artifact SHA-256, `bug_type` as a string,
uppercase image UUIDs, and integer literals that exceed IEEE-754 range. Only
`bug_type` `"309"` yields a crash body; other DiagnosticReports types return
no crash result. Select `join_path` for one local Mach-O or dSYM to match
report images to slices by UUID and architecture. Preserve the selected
artifact identity, slice facts and unjoined reasons; a joined static address
does not resolve function names or dSYM symbols. See
[Apple IPS crash reports](https://github.com/morluto/rea/blob/main/docs/apple-ips.md).
## Linux kernel modules
Use `inspect_kernel_module` for an explicit local ELF32/ELF64 relocatable
object when the question concerns `.modinfo` license, vermagic, alias,
depends, or parm strings, the `__versions` symbol CRCs, or an appended module
signature trailer. No `open_binary`, Linux host, or `modinfo` program is
required. The CLI equivalent is `inspect-kernel-module PATH`. Preserve
artifact SHA-256, key order, duplicate keys, CRC hex, and section file ranges.
A missing section is observed absence when section names are known, not proof
the module never carried that metadata. `.gnu.linkonce.this_module` is reported
as present or absent; `struct module` stays unknown because its layout changes
with the kernel version. `__ksymtab` records are not decoded. The PKCS#7 range
and its SHA-256 are observed; certificate, signer, and digest coverage remain
unvalidated. An absent trailer is not proof the module is unsigned in another
container. gzip, xz, and zstd whole-file containers are unsupported and name
`external_decompression` as the next probe: use an external codec utility to
decompress into a separate file, then call `inspect_kernel_module` on that file. See
[Linux kernel modules](https://github.com/morluto/rea/blob/main/docs/kernel-modules.md).
## Linux kernel build config
Use `inspect_kernel_build_config` for an explicit local kernel image (`vmlinux`
ELF, raw arm64 `Image`, x86 `bzImage`/`zImage`, or `configs.ko`) when the
question is which kernel version it is or how it was configured. No
`open_binary`, Linux host, or `extract-ikconfig` is required. The CLI
equivalent is `inspect-kernel-build-config PATH`; pass `name` for one exact
`CONFIG_*` option and `offset`/`limit` to page the rest. Preserve the banner
version, builder, and compiler, the config SHA-256 and option count, and each
entry's value kind (`y`, `m`, `string`, `number`, `not_set`). A config is
accepted only when `IKCFG_ST` is immediately followed by a valid gzip member
and `IKCFG_ED`, so an ordinary `IKCFG_ST` string is not a config. Offsets with
`space: "inflated_payload"` are positions in an inflated bzImage payload, not
file offsets. An absent config describes only the searched bytes; the kernel
may have been built without `CONFIG_IKCONFIG` or with it as a module. Truncated
payloads are incomplete, not absent. A framed blob that does not decode, has a
bad CRC, or holds non-config text is `malformed`, not absent. xz, lz4, lzma, zstd, bzip2, and lzo
payloads are unsupported and name `external_decompression`: decompress with an
external utility, then call `inspect_kernel_build_config` on the result. See
[Linux kernel build config](https://github.com/morluto/rea/blob/main/docs/guides/native/kernel-build-config.md).
## CPython pyc files
Use `inspect_python_pyc` for an explicit local CPython 3.7–3.13 `.pyc` when the
question concerns the header magic, PEP 552 source hash or timestamp, nested
code-object names, or bytecode identity. No `open_binary`, Python installation,
or target execution is required. The CLI equivalent is `inspect-python-pyc PATH`.
Preserve artifact SHA-256, the 16-byte header fields, nested code names, and
bytecode SHA-256. Bytecode is not decompiled. A decoded header does not prove
the matching `.py` is present. See [CPython pyc inspection](https://github.com/morluto/rea/blob/main/docs/python-pyc.md).
## Erlang BEAM files
Use `inspect_erlang_beam` for an explicit local `FOR1`/`BEAM` file when the
question concerns IFF chunks, atom names, exports, imports, lambdas, or the
Code header. No `open_binary`, Erlang installation, or bytecode execution is
required. The CLI equivalent is `inspect-erlang-beam PATH`. Preserve artifact
SHA-256, chunk file ranges, 1-based atom indices, and original atom spellings.
Missing optional tables stay null. For a few facts, use `detail: "summary"` with
a `beam` view selecting a collection, exact names/indices and fields; read later
pages from `parent_evidence_id` or the complete Evidence bundle. Same-name
exports retain every arity and original atom index. CLI summary delivery requires
`--evidence_output`. Null table counts remain distinct from decoded empty tables. See
[Erlang BEAM files](https://github.com/morluto/rea/blob/main/docs/erlang-beam.md).
## SQLite snapshots
Use `inspect_sqlite_database` with an explicit local database snapshot for its
schema and optional rows from one exact ordinary table. It requires Node.js
24.x >=24.15 or 26+ with the native `DatabaseSync.limits` API. It also consumes the
existing sibling `-wal`; retain both original digests when citing its result.
Default output is a compact inventory; a selected table expands only its own
schema and samples 20 rows. Select `columns` to avoid irrelevant or large cells,
and request `schema_scope: "database"` only for complete schema details.
Choose `row_limit` when a finite sample is useful and keep truncation and
unspecified row order explicit. Virtual tables, shadow tables and views are
schema facts, not row-read targets. REA inspects private copies and leaves
the source files untouched; obtain a coherent quiescent database/WAL snapshot
before inspection. See [SQLite snapshots](https://github.com/morluto/rea/blob/main/docs/guides/data/sqlite-databases.md).
## Recorded Linux crashes
Use `inspect_recorded_crash` with an explicit Linux x86-64 ELF core path to
inspect historical thread registers, signals, raw notes and original file
ranges. It is independent of the active disassembler target. Source-bound
register values and note bytes are observations; missing notes, signal-thread
association and current executable/library identity remain unknown. PIDs are
historical metadata and never authorize live attach or control.
The optional `include_debugger_context` facet adds mapping candidates through
caller-supplied GDB/pwndbg in an owned core-only session. Display names do not
establish current file identity; zero reported flags leave permissions unknown.
Requested unavailable context fails with setup guidance. See
[recorded crashes](https://github.com/morluto/rea/blob/main/docs/guides/native/recorded-crashes.md)
for exact upstream profiles, bounds and verification coverage.
## Offline ELF layout
Use `inspect_binary_layout` with an explicit local ELF path for file-backed
sections, symbols, relocations, linkage names, and static mitigation inferences
without opening a disassembler target. When that complete record is already
retained, call `inspect_analysis_view` for a summary, mitigations or linkage
facet, one section or symbol, or a stable page. Do not re-run the decoder to
read a single object.
## Offline WASM
Supply WABT 1.0.42 through `REA_WABT_BIN_DIRECTORY`. Select JavaScript glue and
candidate paths explicitly, preserving URL/path ambiguity and exact byte/tool
identity; no implicit fetch is performed.
Use `inspect_wasm_artifact` with caller-supplied WABT for selected local bytes.
For counts, validation and WAT identity, select `detail: "summary"` on the first
MCP call. The complete original Evidence stays retained; use
`normalized_result.parent_evidence_id` for exact exports or text pages. Omitted
or complete detail keeps the existing complete result.
For a completed result, use `inspect_analysis_view` with its reported retained
reference for a summary or `view: {kind: "wasm", facet: "exports", offset: 0, limit: 32}`.
Facets also include sections, imports, candidates, glue, WAT, headers, details
and tool profile. Text offsets count UTF-16 code units; follow `next_offset`.
The complete source remains retained after an oversized MCP reply. No target
execution or network acquisition is implied, and WAT is decoded representation.
For exact core-module import/export differences, use `compare_wasm_interfaces`
with two complete inspection Evidence records (or current-session retained
references) and explicit scope. Preserve unknown types/duplicate names; static
interface equality is not runtime compatibility. CLI accepts complete inline
Evidence in `rea compare-wasm-interfaces input.json --json`, or exact references
with `--evidence-bundle /absolute/path/evidence.json`. Use the complete parent
ID when the inspection returned a summary.
See the [WASM guide](https://github.com/morluto/rea/blob/main/docs/guides/wasm/wasm-artifacts.md).
## Offline APK signatures
Use `inspect_apk_signature` for a selected local APK/ZIP/AAB. Default MCP summaries
retain complete Evidence. Select `view.collection: signers/digests/lineage`,
with scheme or ID batches, fields and paging; use `inspect_analysis_view` kind
`apk-signature` for retained reads after deletion. IDs retain artifact/scheme
scope and signer links; `fields: []` keeps identity links. Empty pages preserve
partial decoding reasons and unknown authenticity. CLI compact delivery needs
`--evidence_output` to preserve the complete parent.
It reads the APK
Signing Block (v2/v3/v3.1) and v1 `META-INF` signing without executing anything,
and reports each signer's certificate facts (for v1, the certificate the PKCS#7
block's SignerInfo names), the declared v3 rotation lineage,
the declared source-stamp certificate and, for SHA-256/SHA-512 digests, whether
the declared content digest matches the current bytes. That match is not a
signature verification: anyone who changes the bytes can recompute the digest.
`posture` summarises v1-only signing (which cannot tell a v1-only APK from one
whose v2/v3 block was removed), observed signer agreement across schemes,
whether the v1 signer equals the block-scheme signer (`false` is notable),
whether every differing signer appears in a declared (unverified) rotation
lineage, and the strongest scheme.
It never claims authenticity: no signature or lineage is cryptographically
verified.
See the [APK signature guide](https://github.com/morluto/rea/blob/main/docs/apk-signature.md).
## Offline PE resource inventory
Use `inspect_pe_resources` with an absolute `path`, or
`rea inspect-pe-resources ./selected.exe --json`, for portable PE32/PE32+
resource inspection without opening a native target. Preserve returned Evidence:
type/name/language IDs and UTF-16 names, original directory/data/payload ranges,
code pages, payload hashes and static icon-group references. Same-language icon
resolution is explicit; missing references and size mismatch remain visible.
Other payloads are opaque, and this operation does not decode images or simulate
Windows resource loading. Consult the budgets and supported tree profile in
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/guides/native/binary-diagnostics.md).
## Offline PE and ELF export tables
Use `inspect_native_exports` with an absolute `path`, or
`rea inspect-native-exports ./libexample.so --json`, to read a PE export
directory or ELF dynamic symbol table on any host without opening a native
target. Ordinal-only, forwarded and hidden-version exports stay distinct;
partial or unsupported coverage means the inventory is incomplete, not that
exports are absent. Use `inspect_macho` for Mach-O. See
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/guides/native/binary-diagnostics.md).
## EVM bytecode
Explicit local EVM bytecode carrier: `inspect_evm_interface` with caller-selected
`raw` or `hex` encoding. The owned worker deadline is `timeout_ms` (default
30000 ms, range 1..2147483647, next call). Select byte, address-space and CPU
allowances separately through `producer_limits`; tighter inherited OS limits
remain enforced, and the 256 MiB worker heap is independent. Preserve carrier/decoded digests and treat selectors,
argument strings and mutability as inferred candidates; this performs no chain
lookup or target execution. For the complete inferred interface without hex or
duplicate stdout, discover support and select `detail: "summary"`; complete
Evidence stays retained under its original identity. Use the returned
`parent_evidence_id` for exact function or `bytecode_hex`/stdout/stderr pages.
Candidate coverage does not establish interface completeness. See the
[EVM bytecode guide](https://github.com/morluto/rea/blob/main/docs/guides/evm/evm-bytecode.md).
## Embedded GPU code objects
Embedded CUDA/HIP code objects: `inspect_gpu_code_objects`; inventory targets
first, then decode selected payloads and query retained GPU Evidence.
@@ -1,13 +1,5 @@
# JavaScript and Electron application artifacts
Read only the sections needed for the selected task.
- [Inventory route candidates without returning the application graph](#inventory-route-candidates-without-returning-the-application-graph)
- [Read a module without repeating analysis](#read-a-module-without-repeating-analysis)
- [Trace only the remaining question](#trace-only-the-remaining-question)
- [Recover an oversized display](#recover-an-oversized-display)
- [Reusing application Evidence](#reusing-application-evidence)
For captured or bundled JavaScript that needs readable source recovery, use
`recover_javascript_sources` with an absolute `path` and absent
`output_directory`. Supply related runtime/shared/lazy files in
@@ -98,9 +90,6 @@ Endpoint candidates remain inferences rather than confirmed network behavior.
## Read a module without repeating analysis
Use the actual returned parent ID, on the same connection, as `source.evidence_id`.
For CLI summary exports, take that ID from
`summary.normalized_result.parent_evidence_id` and pass `--evidence-bundle` with
the exported bundle's path to each later `inspect-analysis-view` invocation.
The following IDs are placeholders to replace with returned values. When the
module's node ID is not already known, page identities before selecting an item.
Pages return only node IDs, paths and roles; choose a useful page size and follow
@@ -1,23 +1,4 @@
# Native and managed analysis
<a id="native-managed-and-packaged-artifacts"></a>
Read only the sections needed for the selected task.
- [Other artifact guides](#other-artifact-guides)
- [Apple app resources](#apple-app-resources)
- [Desktop installation directories](#desktop-installation-directories)
- [Native targets](#native-targets)
- [Continue editable Ghidra analysis across processes](#continue-editable-ghidra-analysis-across-processes)
- [Reuse caller-selected Ghidra analysis](#reuse-caller-selected-ghidra-analysis)
- [Ghidra startup deadline recovery](#ghidra-startup-deadline-recovery)
- [Bridge request ceiling](#bridge-request-ceiling)
- [Selected native facts and call graphs](#selected-native-facts-and-call-graphs)
- [Static memory and original file bytes](#static-memory-and-original-file-bytes)
- [DOS MZ with Ghidra](#dos-mz-with-ghidra)
- [Managed PE/CLI](#managed-pe-cli-guide)
- [DOS COM loader interpretation](#dos-com-loader-interpretation)
- [Function annotations in Ghidra](#function-annotations-in-ghidra)
# Native, managed, and packaged artifacts
For release comparisons on one MCP connection, retain `list_procedures` Evidence
from each target and reuse its exact IDs after target switches or `close_binary`.
@@ -81,39 +62,342 @@ runtime presence. For compact CLI output, use
`inspect-desktop-application PATH --detail summary --evidence_output BUNDLE`.
See [desktop installations](https://github.com/morluto/rea/blob/main/docs/desktop-applications.md).
## Release exposure
Use `audit_release_exposure` (`rea audit-release-exposure PATH`) when the question is what static analysis of a shipped `.app`, install directory, ASAR, or executable recovers. Each check has a stable id, a status, the observation behind it, and fixed remediation. Unknown and unassessed checks stay in the report. The report does not establish that the application is secure.
`text.credential_markers` reports a rule id and location only. Matched bytes, previews, lengths, and hashes of matched bytes are omitted.
`native.hardening`, `macho.hardened_runtime`, and `android.manifest` are not assessed here. Their next probes are `inspect_binary_hardening`, `inspect_signature`, and `inspect_android_package`.
See [release exposure](https://github.com/morluto/rea/blob/main/docs/release-exposure.md).
## Go build metadata
Use `inspect_go_binary` for an explicit local ELF, PE or Mach-O (universal
files need `architecture`) when the
question concerns the embedded compiler version, module/dependency versions,
local replacements, build settings, or static Go string literals packed without
NUL terminators. Pass `include_string_literals: true`
(`--include-string-literals`, optional `max_string_literals` /
`--max-string-literals`) to recover bounded inferred word-aligned `(ptr, len)`
UTF-8 string slices from read-only data and page them through `go`
`string_literals` views. No `open_binary`, Go installation or
target execution is required. The CLI equivalent is `inspect-go-binary PATH`.
Preserve artifact SHA-256, original file ranges, framed module bytes and every
unparsed module-text line. A missing record is an unknown, not proof of a
non-Go binary; observed metadata does not authenticate its claims, and static
`(ptr, len)` string recovery excludes compiler-inlined stack/immediate strings
without proving runtime reachability. Function,
source and type recovery remain a separate native-analysis question.
## Model files
Use `inspect_model_artifact` (`inspect-model-artifact PATH`) for local GGUF v2/v3
and safetensors declarations without a model runtime. Header-only mode does not
read weights or establish full-file identity. `hash: "full"` explicitly streams
all bytes. Retained `model-artifact` views page `metadata`, `tensors` and derived
`types`, with exact batch filters and selected fields. Arrays retain typed counts
and eight-element previews; omitted values, unknown types and runtime behavior
stay explicit. See [model files](https://github.com/morluto/rea/blob/main/docs/model-artifacts.md).
## IL2CPP metadata
Use `inspect_il2cpp_metadata` for an explicit local Unity `global-metadata.dat`
(versions 24, 27, 29, 31 and 39) when the question concerns assembly images,
names, member owners, string literals, namespaces or index ranges. No `open_binary`, Unity installation or target
execution is required. The CLI equivalent is `inspect-il2cpp-metadata PATH`.
MCP defaults to counts with complete retained Evidence. Query `sections`,
`images`, `types`, `methods`, `fields`, `properties`, `events`, `parameters` or
`string_literals` through `inspect_analysis_view`; use exact names,
batch indices and selected fields. Row indices are scoped by artifact SHA-256
and collection; page offsets count filtered matches. Parameters support batch
`method_indices` to read one method's parameters directly. Historical reports
without member collections retain unknown counts rather than empty inventories. Preserve
artifact SHA-256 and index widths. Unrecognized magic may mean encryption and
is unsupported, not decrypted. Linking names to `GameAssembly` or
`UnityFramework` code addresses remains a separate native-analysis question.
See [IL2CPP metadata](https://github.com/morluto/rea/blob/main/docs/il2cpp-metadata.md).
## Rust dependency metadata
Use `inspect_rust_binary` for an explicit local ELF, PE, or Mach-O (universal
files need `architecture`) when
the question concerns the cargo-auditable crate list embedded in `.dep-v0`
(Mach-O: `__DATA,.dep-v0`). No `open_binary`, Rust installation, or target
execution is required. The CLI equivalent is `inspect-rust-binary PATH`.
Preserve artifact SHA-256, package order, dependency indices, and the section
file range. A missing section is null metadata, not proof the binary has no
dependencies. An omitted package kind stays null. A rustc commit hash is
inferred from a read-only `/rustc/<40-hex>/` path and does not by itself
identify the compiler. See [Rust binaries](https://github.com/morluto/rea/blob/main/docs/rust-binaries.md).
## PE Authenticode certificate tables
Use `inspect_pe_certificates` for an explicit local PE32/PE32+ image when the
question concerns the Authenticode certificate table, WIN_CERTIFICATE types, or
embedded PKCS#7 signer and X.509 identity fields. No `open_binary`, Windows SDK,
or target execution is required. The CLI equivalent is
`inspect-pe-certificates PATH`. Preserve artifact SHA-256, table file ranges,
payload hashes, original validity-time spellings, and
`subject_key_identifier_hex` when the signer uses that form. A missing
`IMAGE_DIRECTORY_ENTRY_SECURITY` directory is observed absence of that table,
not proof the image is unsigned in every catalog or overlay sense. Signatures,
chains, timestamps, and Authenticode digest coverage remain unobserved. See
[PE Authenticode certificate tables](https://github.com/morluto/rea/blob/main/docs/pe-certificates.md).
## PE import and delay-load tables
Use `inspect_pe_imports` for an explicit local PE32/PE32+ image when the
question concerns imported DLL and function names, ordinals, delay-load
descriptors, or the pefile import hash. No `open_binary`, Windows SDK, or
target execution is required. The CLI equivalent is `inspect-pe-imports PATH`.
Preserve artifact SHA-256, directory file ranges, original DLL spellings, and
ILT/IAT RVAs. A missing `IMAGE_DIRECTORY_ENTRY_IMPORT` directory is observed
absence of that table, not proof the image never imported APIs through another
table or overlay. Bound snapshots, loaded IAT contents, and export-forward
resolution remain unobserved. See
[PE import and delay-load tables](https://github.com/morluto/rea/blob/main/docs/pe-imports.md).
## Windows Shell Link shortcuts
Use `inspect_windows_shortcut` for an explicit local `.lnk` file when the
question concerns the recorded target path, arguments, working directory, icon
location, volume serial, network share names, ExtraData blocks, or Tracker
droid identifiers. No `open_binary`, Windows SDK, COM, or target execution is
required. The CLI equivalent is `inspect-windows-shortcut PATH`. Preserve
artifact SHA-256, FILETIME raw decimals, IDList type bytes, and ExtraData
signatures. Unknown IDList items and ExtraData signatures remain raw with
SHA-256; they are not guessed. Version-1 UUID MAC and timestamp values are
labelled derived. A missing optional section is observed absence of that
structure. See
[Windows Shell Link shortcuts](https://github.com/morluto/rea/blob/main/docs/guides/native/windows-shortcut.md).
## PE TLS callbacks and load configuration
Use `inspect_pe_load_config` for an explicit local PE32/PE32+ image when the
question concerns code that runs before the entry point through TLS callbacks,
or the security cookie, SafeSEH, CFG, EH-continuation and XFG declarations in
the load configuration. No `open_binary`, Windows SDK, or target execution is
required. The CLI equivalent is `inspect-pe-load-config PATH`. Preserve artifact
SHA-256, directory and callback-array file ranges, and callback VAs as
hexadecimal strings. Fields outside the declared `Size` are `null`. Guard-flag
mitigations carry `evidence_kind: "inferred"`; enforcement remains unobserved.
See
[PE TLS callbacks and load configuration](https://github.com/morluto/rea/blob/main/docs/pe-load-config.md).
## Binary hardening
Use `inspect_binary_hardening` for an explicit local ELF, PE, or Mach-O file
when the question concerns PIE or ASLR, NX, RELRO, stack-protector or FORTIFY
signs, control-flow flags, or stripped state. No `open_binary`, pwntools, or
target execution is required. The CLI equivalent is
`inspect-binary-hardening PATH`. Universal Mach-O files return one image per
slice. A missing import is `unknown`, not proof the protection was omitted.
Authenticode and Mach-O code-signature rows report presence, not validity.
`hardened_runtime` stays unknown and names `inspect_signature`. See
[binary hardening](https://github.com/morluto/rea/blob/main/docs/binary-hardening.md).
## ELF note tables
Use `inspect_elf_notes` for an explicit local ELF32/ELF64 image when the
question concerns PT_NOTE/SHT_NOTE owners, GNU build-id, or GNU ABI-tag
fields. No `open_binary` or target execution is required. The CLI equivalent
is `inspect-elf-notes PATH`. Preserve artifact SHA-256, table file ranges,
descriptor hashes, and original owner spellings. A missing table is observed
absence of those declarations, not proof the image never carried notes in
another container. See
[ELF note tables](https://github.com/morluto/rea/blob/main/docs/elf-notes.md).
## ELF and Mach-O initializer functions
Use `inspect_native_initializers` for an explicit local ELF or Mach-O image
when the question is which functions run before `main` or at exit: ELF
`DT_PREINIT_ARRAY`, `DT_INIT`, `DT_INIT_ARRAY`, `DT_FINI`, `DT_FINI_ARRAY` and
legacy `.ctors`/`.dtors`, or Mach-O `__mod_init_func`, `__init_offsets` and
`__mod_term_func`. Pass `architecture` for a universal Mach-O. The CLI
equivalent is `inspect-native-initializers PATH`. PIE slots are resolved
through RELATIVE, RELR and section relocations, and Mach-O slots through
chained fixups. Keep each entry's `resolution`: `unresolved` means the value was
not established, and `unresolved_import` names an external symbol. Page with
`offset` and `limit`. See
[ELF and Mach-O initializer functions](https://github.com/morluto/rea/blob/main/docs/guides/native/native-initializers.md).
## ELF unwind function ranges
Use `inspect_elf_unwind_ranges` for an explicit local ELF32/ELF64 image when
the question concerns function starts and lengths that survive `strip`, which
functions have an LSDA, or which ranges have no exact symbol. No
`open_binary` or target execution is required. The CLI equivalent is
`inspect-elf-unwind-ranges PATH`. Preserve artifact SHA-256, FDE start and
end, containing section, exact symbol name, CIE index, LSDA, and personality.
Page later ranges with `offset` and `limit`. A missing `.eh_frame` is
observed absence of that declaration and of `PT_GNU_EH_FRAME`, not proof the
image has no functions. Header disagreement is a limitation and does not drop
decoded ranges. `.debug_frame` is reported present or absent and is not
decoded. CFA instructions are counted and not evaluated. Indirect EH pointers
are not dereferenced. See
[ELF unwind function ranges](https://github.com/morluto/rea/blob/main/docs/guides/native/elf-unwind-ranges.md).
## Apple IPS crash reports
Use `inspect_apple_ips` for an explicit local Apple `.ips` file when the
question concerns crash metadata, exception and termination facts, threads,
usedImages, lossless registers, or documented crash classes. No `open_binary`,
Xcode, atos, or target execution is required. The CLI equivalent is
`inspect-apple-ips PATH`. Preserve artifact SHA-256, `bug_type` as a string,
uppercase image UUIDs, and integer literals that exceed IEEE-754 range. Only
`bug_type` `"309"` yields a crash body; other DiagnosticReports types return
no crash result. Select `join_path` for one local Mach-O or dSYM to match
report images to slices by UUID and architecture. Preserve the selected
artifact identity, slice facts and unjoined reasons; a joined static address
does not resolve function names or dSYM symbols. See
[Apple IPS crash reports](https://github.com/morluto/rea/blob/main/docs/apple-ips.md).
## Linux kernel modules
Use `inspect_kernel_module` for an explicit local ELF32/ELF64 relocatable
object when the question concerns `.modinfo` license, vermagic, alias,
depends, or parm strings, the `__versions` symbol CRCs, or an appended module
signature trailer. No `open_binary`, Linux host, or `modinfo` program is
required. The CLI equivalent is `inspect-kernel-module PATH`. Preserve
artifact SHA-256, key order, duplicate keys, CRC hex, and section file ranges.
A missing section is observed absence when section names are known, not proof
the module never carried that metadata. `.gnu.linkonce.this_module` is reported
as present or absent; `struct module` stays unknown because its layout changes
with the kernel version. `__ksymtab` records are not decoded. The PKCS#7 range
and its SHA-256 are observed; certificate, signer, and digest coverage remain
unvalidated. An absent trailer is not proof the module is unsigned in another
container. gzip, xz, and zstd whole-file containers are unsupported and name
`external_decompression` as the next probe: use an external codec utility to
decompress into a separate file, then call `inspect_kernel_module` on that file. See
[Linux kernel modules](https://github.com/morluto/rea/blob/main/docs/kernel-modules.md).
## Linux kernel build config
Use `inspect_kernel_build_config` for an explicit local kernel image (`vmlinux`
ELF, raw arm64 `Image`, x86 `bzImage`/`zImage`, or `configs.ko`) when the
question is which kernel version it is or how it was configured. No
`open_binary`, Linux host, or `extract-ikconfig` is required. The CLI
equivalent is `inspect-kernel-build-config PATH`; pass `name` for one exact
`CONFIG_*` option and `offset`/`limit` to page the rest. Preserve the banner
version, builder, and compiler, the config SHA-256 and option count, and each
entry's value kind (`y`, `m`, `string`, `number`, `not_set`). A config is
accepted only when `IKCFG_ST` is immediately followed by a valid gzip member
and `IKCFG_ED`, so an ordinary `IKCFG_ST` string is not a config. Offsets with
`space: "inflated_payload"` are positions in an inflated bzImage payload, not
file offsets. An absent config describes only the searched bytes; the kernel
may have been built without `CONFIG_IKCONFIG` or with it as a module. Truncated
payloads are incomplete, not absent. A framed blob that does not decode, has a
bad CRC, or holds non-config text is `malformed`, not absent. xz, lz4, lzma, zstd, bzip2, and lzo
payloads are unsupported and name `external_decompression`: decompress with an
external utility, then call `inspect_kernel_build_config` on the result. See
[Linux kernel build config](https://github.com/morluto/rea/blob/main/docs/guides/native/kernel-build-config.md).
## CPython pyc files
Use `inspect_python_pyc` for an explicit local CPython 3.7–3.13 `.pyc` when the
question concerns the header magic, PEP 552 source hash or timestamp, nested
code-object names, or bytecode identity. No `open_binary`, Python installation,
or target execution is required. The CLI equivalent is `inspect-python-pyc PATH`.
Preserve artifact SHA-256, the 16-byte header fields, nested code names, and
bytecode SHA-256. Bytecode is not decompiled. A decoded header does not prove
the matching `.py` is present. See [CPython pyc inspection](https://github.com/morluto/rea/blob/main/docs/python-pyc.md).
## Erlang BEAM files
Use `inspect_erlang_beam` for an explicit local `FOR1`/`BEAM` file when the
question concerns IFF chunks, atom names, exports, imports, lambdas, or the
Code header. No `open_binary`, Erlang installation, or bytecode execution is
required. The CLI equivalent is `inspect-erlang-beam PATH`. Preserve artifact
SHA-256, chunk file ranges, 1-based atom indices, and original atom spellings.
Missing optional tables stay null. For a few facts, use `detail: "summary"` with
a `beam` view selecting a collection, exact names/indices and fields; read later
pages from `parent_evidence_id` or the complete Evidence bundle. Same-name
exports retain every arity and original atom index. CLI summary delivery requires
`--evidence_output`. Null table counts remain distinct from decoded empty tables. See
[Erlang BEAM files](https://github.com/morluto/rea/blob/main/docs/erlang-beam.md).
## SQLite snapshots
Use `inspect_sqlite_database` with an explicit local database snapshot for its
schema and optional rows from one exact ordinary table. It requires Node.js
24.x >=24.15 or 26+ with the native `DatabaseSync.limits` API. It also consumes the
existing sibling `-wal`; retain both original digests when citing its result.
Default output is a compact inventory; a selected table expands only its own
schema and samples 20 rows. Select `columns` to avoid irrelevant or large cells,
and request `schema_scope: "database"` only for complete schema details.
Choose `row_limit` when a finite sample is useful and keep truncation and
unspecified row order explicit. Virtual tables, shadow tables and views are
schema facts, not row-read targets. REA inspects private copies and leaves
the source files untouched; obtain a coherent quiescent database/WAL snapshot
before inspection. See [SQLite snapshots](https://github.com/morluto/rea/blob/main/docs/guides/data/sqlite-databases.md).
## Recorded Linux crashes
Use `inspect_recorded_crash` with an explicit Linux x86-64 ELF core path to
inspect historical thread registers, signals, raw notes and original file
ranges. It is independent of the active disassembler target. Source-bound
register values and note bytes are observations; missing notes, signal-thread
association and current executable/library identity remain unknown. PIDs are
historical metadata and never authorize live attach or control.
The optional `include_debugger_context` facet adds mapping candidates through
caller-supplied GDB/pwndbg in an owned core-only session. Display names do not
establish current file identity; zero reported flags leave permissions unknown.
Requested unavailable context fails with setup guidance. See
[recorded crashes](https://github.com/morluto/rea/blob/main/docs/guides/native/recorded-crashes.md)
for exact upstream profiles, bounds and verification coverage.
## Offline ELF layout
Use `inspect_binary_layout` with an explicit local ELF path for file-backed
sections, symbols, relocations, linkage names, and static mitigation inferences
without opening a disassembler target. When that complete record is already
retained, call `inspect_analysis_view` for a summary, mitigations or linkage
facet, one section or symbol, or a stable page. Do not re-run the decoder to
read a single object.
## Offline WASM
Use `inspect_wasm_artifact` with caller-supplied WABT for selected local bytes.
For counts, validation and WAT identity, select `detail: "summary"` on the first
MCP call. The complete original Evidence stays retained; use
`normalized_result.parent_evidence_id` for exact exports or text pages. Omitted
or complete detail keeps the existing complete result.
For a completed result, use `inspect_analysis_view` with its reported retained
reference for a summary or `view: {kind: "wasm", facet: "exports", offset: 0, limit: 32}`.
Facets also include sections, imports, candidates, glue, WAT, headers, details
and tool profile. Text offsets count UTF-16 code units; follow `next_offset`.
The complete source remains retained after an oversized MCP reply. No target
execution or network acquisition is implied, and WAT is decoded representation.
## Offline APK signatures
Use `inspect_apk_signature` for a selected local APK/ZIP/AAB. Default MCP summaries
retain complete Evidence. Select `view.collection: signers/digests/lineage`,
with scheme or ID batches, fields and paging; use `inspect_analysis_view` kind
`apk-signature` for retained reads after deletion. IDs retain artifact/scheme
scope and signer links; `fields: []` keeps identity links. Empty pages preserve
partial decoding reasons and unknown authenticity. CLI compact delivery needs
`--evidence_output` to preserve the complete parent.
It reads the APK
Signing Block (v2/v3/v3.1) and v1 `META-INF` signing without executing anything,
and reports each signer's certificate facts (for v1, the certificate the PKCS#7
block's SignerInfo names), the declared v3 rotation lineage,
the declared source-stamp certificate and, for SHA-256/SHA-512 digests, whether
the declared content digest matches the current bytes. That match is not a
signature verification: anyone who changes the bytes can recompute the digest.
`posture` summarises v1-only signing (which cannot tell a v1-only APK from one
whose v2/v3 block was removed), observed signer agreement across schemes,
whether the v1 signer equals the block-scheme signer (`false` is notable),
whether every differing signer appears in a declared (unverified) rotation
lineage, and the strongest scheme.
It never claims authenticity: no signature or lineage is cryptographically
verified.
## Native targets
For an existing IDA MCP configuration, use `open_binary` with the original
input binary and `provider_id: "ida"`; do not pass an `.idb` or `.i64` database.
The legacy attached profile binds the already-open GUI input by SHA-256 and
leaves its database open. The modern headless profile creates and closes an
owned private database without saving. Installation and registration reuse
[mrexodia/ida-pro-mcp](https://github.com/mrexodia/ida-pro-mcp); see the
[IDA provider guide](https://github.com/morluto/rea/blob/main/docs/providers/ida-provider.md).
For migration to the separate native IDA MCP plugin, follow the guide's
[legacy attached migration](https://github.com/morluto/rea/blob/main/docs/providers/ida-provider.md#migrate-a-legacy-attached-registration).
Replace the upstream registration explicitly; modern mrexodia tools are not the
native profile. Native attachment preserves the database but does not provide
direct callers; check session availability and the guide's verification limits.
Proceed directly to `analyze_function`, pseudocode, or function/string searches
after opening; IDA does not supply `binary_overview`. Consult session availability
when composing broader workflows. Modern direct callers and unsupported dossier
facets remain unknown; live IDA results are not replayed from snapshots.
With `REA_KUNA_COMMAND` configured, `provider_id: "kuna"` answers procedure
lists, lookups, pseudocode, assembly and `analyze_function` from Kuna's cached
whole-program export. Callers/callees use a separate body-free graph shared by
the session; `xrefs` returns exact-address incoming sites and filters forwarding
aliases. These queries and inventory lookups do not trigger a C export. Indirect
targets remain unknown and can make relationship coverage partial. Reference
caches expire on close; a fresh CLI process repeats the native reference walk.
Typed references, CFG, raw-memory tools and annotations remain unsupported. A cold first
answer can come from a single-function run (its limitations say so); assembly
waits for the export. For pseudocode and assembly, `raw_result.source` names the
cached `.c`/`.asm` file and byte span, which you may search directly instead of
calling per function. See the
[Kuna provider guide](https://github.com/morluto/rea/blob/main/docs/kuna-provider.md).
After `open_binary`, use focused search, procedure, or function tools directly.
Use `binary_overview` when target metadata or inventory context helps answer the
question. Prefer literal search, names, decompilation, callers, callees, and
@@ -348,8 +632,6 @@ that a class is a root class or has no ivars.
## Managed PE/CLI
<a id="managed-pe-cli-guide"></a>
Start with `inspect_managed_artifact`. REA's canonical managed inspection is
execution-free: do not claim it loaded, reflected, executed, or resolved the
assembly. Keep managed/native boundaries and unavailable reconstruction facts
@@ -408,17 +690,81 @@ and distinguish derived metadata from Ghidra database observations. Recovered
types and native pseudocode are not original C# source. Windows Ghidra P0 has
no database mutation authority.
For headerless firmware and address-bearing containers, read
[raw images](firmware-and-raw-images.md#headerless-raw-images-and-firmware-containers).
## Packages and extraction
## DOS COM loader interpretation
Use `inspect_artifact` for application bundles, archives, ZIP/APK/IPA/MSIX/AppX,
ASAR, or DMG inputs when the artifact graph and findings help answer the
question. It returns the complete artifact graph inline. Cite graph manifest
IDs when using them.
For an IPA or a macOS `.app`, ZIP, or DMG, pass the inventory Evidence to
`project_apple_application_graph`. It reports application roots and nested
bundles by path convention: app extensions, XPC services, frameworks, login
items, system and driver extensions, and plug-ins. It also lists privileged
helpers, launchd plists, symlinks, and each bundle's `info_plist_path` and
executable candidates. Roles are path conventions, not parsed plists; read the
listed plists with `inspect_plist`.
After `open_binary` on a `.app` or Mach-O, use `trace_dylib_resolution` to see
which file each `@rpath`, `@loader_path` and `@executable_path` load reaches for
every executable in the bundle. It also lists missing or weak loads and earlier
`@rpath` candidates that are absent. Narrow large bundles with `roots` or
`architecture`. System paths stay undetermined because the dyld shared cache
provides them.
Use `extract_artifact` when materialized files from a supported format are needed.
It extracts the active target and accepts optional `integrity_policy`; see the [MCP contracts](https://github.com/morluto/rea/blob/main/docs/reference/mcp-contracts.md).
It materializes all regular files into a fresh temporary directory chosen by
REA. Symlinks and encrypted entries are inventory facts, not extractable files.
The result reports the materialized directory; callers do not choose the
destination. No separate permission grant is needed.
For classic iNES cartridges, `open_binary` recognizes the byte signature without
an executable architecture. `inspect_artifact` retains header facts and each
PRG/CHR file unit's offset, length and SHA-256; its nested inventory's
`ines.reconstruction` verifies the actual ordered region bytes against the
whole-file digest. Extraction includes the original header, optional trainer
and uninterpreted trailing bytes. These file units are not mapper/CPU bank
maps. NES 2.0, UNIF, dirty legacy headers and truncated payloads are explicitly
refused. No emulator or 6502 analysis is performed. See the
[iNES guide](https://github.com/morluto/rea/blob/main/docs/ines-containers.md).
Native DMG traversal automatically uses a read-only mount on supported macOS
hosts, with an owned temporary mount directory and cleanup. It needs no approval
flag. Unsupported hosts or mount failures remain explicit limitations; do not
claim child inventory when only root identity is available. DMG extraction is
unavailable through `extract_artifact`. To access DMG files on macOS, create a
read-only mount with your own tools, select files from that mount for analysis,
and detach the mount you created when finished. REA's inspection mount is
temporary, not a retained export of the image's contents.
For headerless DOS COM, explicitly open with `format: "dos-com"` (CLI
`--target-format dos-com`). Inspect the load image before trusting function
analysis: the entry is `0x10100`, file offset zero, with imposed real-mode segment
context. PSP/stack/device state remains unmodeled. See [DOS guide](https://github.com/morluto/rea/blob/main/docs/providers/ghidra-dos.md).
## Function annotations in Ghidra
Use `inspect_byte_entropy` to triage high- and low-entropy ranges before extraction.
For other headerless images (embedded firmware without ELF/Mach-O/PE/MZ
headers), open with `format: "raw-binary"`, `architecture` (`x86`, `x86_64`,
`arm`, `arm64`, `6502`, or `m68k`), and `entry_point` (`0x` hex). File offset 0 is mapped at
that address. Hopper and IDA refuse this interpretation; Ghidra uses
`BinaryLoader` with a generic language: big-endian for `m68k`, little-endian
otherwise. Big-endian ARM, Thumb-only, and Cortex-M variants are unsupported.
`6502` selects NMOS 6502; the entry and complete file mapping must fit within
`0x0000..0xffff`. 65C02, 65816, cartridge containers, bank switching, and mapper
semantics are unsupported. `m68k` requires an even entry and decodes the 68040
superset of the 68000 family; Amiga/Atari containers and vector-table entry
discovery are unsupported. Windows Ghidra P0 admits raw images only for `6502`. See [raw binary guide](https://github.com/morluto/rea/blob/main/docs/providers/raw-binary.md).
Intel HEX, S-record, UF2 and DfuSe files are containers, not raw images:
`inspect_artifact` lists address regions (UF2 per familyID, DfuSe per element
with its alternate setting), and `extract_artifact` writes each region. Open an
extracted region as `raw-binary` with its start address as `entry_point`. UF2
numbering gaps leave coverage unknown; a DfuSe suffix CRC mismatch follows
`integrity_policy`. See [UF2 and DfuSe](https://github.com/morluto/rea/blob/main/docs/uf2-dfuse.md).
### Function annotations in Ghidra
Fresh-import sessions admit annotations; existing-project reuse is read-only.
Use `annotate_native_function` for one function name and/or entry comments, with at least one explicit change. Review its annotation readback and refreshed dossier inline. Changes are atomic; empty comments clear them, omitted fields preserve them. Later MCP calls observe edits. Normal close saves them when owned Program checkpoint mode is enabled; ephemeral mode discards them. CLI `annotate-native-function` follows the same setting when closing after its updated result. Select the [checkpoint workflow](#continue-editable-ghidra-analysis-across-processes) before edits that must survive a later process. Original executable bytes are unchanged; edits invalidate immutable snapshots. Windows P0 does not admit database mutations. For naming only, `set_address_name` / `set_addresses_names` also work with Ghidra: they name function entries or data addresses (primary labels) and return compact per-address success booleans instead of a dossier. Ghidra rejects batch addresses that request the same name or a name another selected address holds; `raw_result.failures` gives each failure's reason.
@@ -438,39 +784,160 @@ architecture, provider-owned mapping and selected-image commitments. Equal
numeric addresses alone cannot establish correspondence; inspect retained
source Evidence and coverage when the match remains ambiguous.
## Other artifact guides
## Offline PE resource inventory
Read the owning page for offline formats, containers or firmware. These explicit
anchors preserve older links without creating duplicate searchable sections.
Use `inspect_pe_resources` with an absolute `path`, or
`rea inspect-pe-resources ./selected.exe --json`, for portable PE32/PE32+
resource inspection without opening a native target. Preserve returned Evidence:
type/name/language IDs and UTF-16 names, original directory/data/payload ranges,
code pages, payload hashes and static icon-group references. Same-language icon
resolution is explicit; missing references and size mismatch remain visible.
Other payloads are opaque, and this operation does not decode images or simulate
Windows resource loading. Consult the budgets and supported tree profile in
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/guides/native/binary-diagnostics.md).
- <a id="go-build-metadata"></a>[Go build metadata](format-inspectors.md#go-build-metadata)
- <a id="model-files"></a>[Model files](format-inspectors.md#model-files)
- <a id="il2cpp-metadata"></a>[IL2CPP metadata](format-inspectors.md#il2cpp-metadata)
- <a id="rust-dependency-metadata"></a>[Rust dependency metadata](format-inspectors.md#rust-dependency-metadata)
- <a id="pe-authenticode-certificate-tables"></a>[PE Authenticode certificate tables](format-inspectors.md#pe-authenticode-certificate-tables)
- <a id="pe-import-and-delay-load-tables"></a>[PE import and delay-load tables](format-inspectors.md#pe-import-and-delay-load-tables)
- <a id="pe-tls-callbacks-and-load-configuration"></a>[PE TLS callbacks and load configuration](format-inspectors.md#pe-tls-callbacks-and-load-configuration)
- <a id="binary-hardening"></a>[Binary hardening](format-inspectors.md#binary-hardening)
- <a id="elf-note-tables"></a>[ELF note tables](format-inspectors.md#elf-note-tables)
- <a id="apple-ips-crash-reports"></a>[Apple IPS crash reports](format-inspectors.md#apple-ips-crash-reports)
- <a id="linux-kernel-modules"></a>[Linux kernel modules](format-inspectors.md#linux-kernel-modules)
- <a id="cpython-pyc-files"></a>[CPython pyc files](format-inspectors.md#cpython-pyc-files)
- <a id="erlang-beam-files"></a>[Erlang BEAM files](format-inspectors.md#erlang-beam-files)
- <a id="sqlite-snapshots"></a>[SQLite snapshots](format-inspectors.md#sqlite-snapshots)
- <a id="recorded-linux-crashes"></a>[Recorded Linux crashes](format-inspectors.md#recorded-linux-crashes)
- <a id="offline-elf-layout"></a>[Offline ELF layout](format-inspectors.md#offline-elf-layout)
- <a id="offline-wasm"></a>[Offline WASM](format-inspectors.md#offline-wasm)
- <a id="offline-apk-signatures"></a>[Offline APK signatures](format-inspectors.md#offline-apk-signatures)
- <a id="packages-and-extraction"></a>[Packages and extraction](packages-and-archives.md#packages-and-extraction)
- <a id="offline-pe-resource-inventory"></a>[Offline PE resource inventory](format-inspectors.md#offline-pe-resource-inventory)
- <a id="offline-pe-and-elf-export-tables"></a>[Offline PE and ELF export tables](format-inspectors.md#offline-pe-and-elf-export-tables)
- <a id="godot-pck"></a>[Godot PCK](packages-and-archives.md#godot-pck)
- <a id="esp-images"></a>[ESP images](firmware-and-raw-images.md#esp-images)
- <a id="esp-flash-images"></a>[ESP flash images](firmware-and-raw-images.md#esp-flash-images)
- <a id="bun-standalone-executables"></a>[Bun standalone executables](packages-and-archives.md#bun-standalone-executables)
- <a id="release-exposure"></a>[Release exposure](format-inspectors.md#release-exposure)
- <a id="windows-shell-link-shortcuts"></a>[Windows Shell Link shortcuts](format-inspectors.md#windows-shell-link-shortcuts)
- <a id="elf-and-mach-o-initializer-functions"></a>[ELF and Mach-O initializer functions](format-inspectors.md#elf-and-mach-o-initializer-functions)
- <a id="elf-unwind-function-ranges"></a>[ELF unwind function ranges](format-inspectors.md#elf-unwind-function-ranges)
- <a id="linux-kernel-build-config"></a>[Linux kernel build config](format-inspectors.md#linux-kernel-build-config)
- <a id="deno-compile-executables"></a>[Deno compile executables](packages-and-archives.md#deno-compile-executables)
## Offline PE and ELF export tables
Use `inspect_native_exports` with an absolute `path`, or
`rea inspect-native-exports ./libexample.so --json`, to read a PE export
directory or ELF dynamic symbol table on any host without opening a native
target. Ordinal-only, forwarded and hidden-version exports stay distinct;
partial or unsupported coverage means the inventory is incomplete, not that
exports are absent. Use `inspect_macho` for Mach-O. See
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/guides/native/binary-diagnostics.md).
For exact core-module import/export differences, use `compare_wasm_interfaces`
with two complete inspection Evidence records (or current-session retained
references) and explicit scope. Preserve unknown types/duplicate names; static
interface equality is not runtime compatibility. CLI accepts complete inline
Evidence in `rea compare-wasm-interfaces input.json --json`, or exact references
with `--evidence-bundle /absolute/path/evidence.json`. Use the complete parent
ID when the inspection returned a summary.
## Godot PCK
For a standalone PCK, `open_binary` then `inspect_artifact` with
`detail: "summary"` retains the complete static inventory. Use the returned
`parent_evidence_id` with `inspect_analysis_view` inventory occurrences filtered
by exact `logical_paths` or `occurrence_ids`, and join `artifact_id` to nodes
filtered by `artifact_ids`. Select only needed fields; identity links remain.
Prefer `inspect-artifact --detail summary --evidence_output PATH` for CLI tasks;
the same CLI discovers embedded packs via their EOF trailer without opening a
native analysis provider. Normal native opening remains unchanged.
Distinguish declared MD5 from observed SHA-256 and graph/page completeness from
`source_coverage`. Encrypted directories and sparse bundles have unknown entry
coverage; encrypted members have unknown content identity. An empty filtered
page cannot prove absence in unknown coverage. Retained views are historical,
not fresh filesystem checks. No decryption, Godot resource decoding, GDScript
decompilation or target execution is provided. See
[Godot PCK](https://github.com/morluto/rea/blob/main/docs/godot-pck.md).
UnityFS AssetBundle containers (`.bundle`, `.ab`, YooAsset packages and other
byte-identified files) list `CAB-*` SerializedFiles and `.resS` members with
header and node facts; none/LZ4/LZ4HC member blocks stream on demand. Bundles
declaring asset-bundle encryption keep header facts with unknown directory
coverage; LZMA member bytes retain named occurrences with unavailable hashes.
Virtual member offsets are not physical carrier locations. Format 6–8 readers
cache one decoded block and support retained filtered/paged views. No
decryption or SerializedFile object decoding is provided. See
[UnityFS](https://github.com/morluto/rea/blob/main/docs/unityfs.md).
WeChat mini-program packages (`.wxapkg` files with `0xBE`/`0xED` marks) list
members with declared names and absolute carrier offsets; member bytes are
stored verbatim. `V1MMWX` PC-client packages report `encrypted` directory
status with unknown coverage and are refused on extraction; no decryption is
attempted. See
[wxapkg](https://github.com/morluto/rea/blob/main/docs/wxapkg.md).
U-Boot legacy uImages use the same inspection/extraction workflows. Prefer a
retained summary and selected inventory views for header facts or component
IDs. Header addresses/CPU/OS are declarations; payloads remain as stored.
Multi-file components retain their aligned physical ranges; the full payload
is also materialized. Unknown name text retains its original 32 bytes. See
[U-Boot uImage](https://github.com/morluto/rea/blob/main/docs/uimage.md).
## ESP images
For an ESP32-family app or bootloader image, `open_binary` then
`inspect_artifact` with `detail: "summary"`. `root_format_metadata` holds the
observed `header` and `descriptor` (its `kind` is the app or bootloader role)
and the derived `chip`, `checksum`, `appended_sha256` and
`undescribed_trailing` ranges. Segment rows (`segment-NN.bin`) carry
`load_addr`, `classification` (`mapped`, `loaded`, `not-loaded` or `unknown`,
per ESP-IDF `should_map`/`should_load`; RAM ranges are not checked) and
`source_location`; select them with `inspect_analysis_view` and
`logical_paths`. Signature blocks are `unverified`; `public_key_sha256`
matches eFuse key digests.
Unknown `source_coverage` means truncation or undescribed trailing bytes, such
as the flash segments of a `--ram-only-header` image; do not infer absence
from it. Erased flash is described. Integrity mismatches are facts, not
refusals. ESP8266 images are refused, and flash-encrypted images are not
recognized. Segments are not disassembled, and RISC-V or Xtensa segments
cannot be opened as raw-binary. See
[ESP images](https://github.com/morluto/rea/blob/main/docs/esp-image.md).
## ESP flash images
A whole-flash dump opens as `esp-flash-image`. `root_format_metadata` holds
`partition_table` (its `method`: `default`, `caller` or a derived
`scan`), `bootloader` and `apps` ESP image facts, the `otadata` records,
`partitions_outside_dump` and `boot_selection`. Every table has a
`boot_selection`: its `basis` names the bootloader's branch (`no-otadata`
without otadata, `none` when the bootloader rejects the table, `unknown`
when a record is missing), and it lists its `assumptions`;
`running_partition` is always `unknown`. Each image names its entry
`path`. With `detail: "summary"` the root kind is `esp-flash-image-summary`
and each image is a compact `summary` (chip, entry point, segment count,
classified load ranges, signature block count, descriptor identity,
integrity); complete detail keeps every segment. Partition rows
(`partitions/<label>.bin`) carry the declared `type`, `subtype`, names,
`flash_address`, `size` and flags; `encrypted` is a storage flag, not
proof of ciphertext. `written_offset`/`written_end` bound the bytes that
are not erased: an app slot whose facts say `erased` can still hold an older
image, and a limitation says so. For a dump read from another flash address, call
`open_binary` with `format: "esp-flash-image"` and `flash_offset`; an
`esp-image` whose `flash_dump` is set names that offset in a next probe; a
relocated table can be named with `partition_table_offset`. No table found
means unknown, often flash encryption. Extraction writes `nvs_keys` as
ordinary data. See
[ESP flash images](https://github.com/morluto/rea/blob/main/docs/esp-flash-image.md).
## Bun standalone executables
A `bun build --compile` executable stores its module graph in Mach-O
`__BUN,__bun`, a PE or ELF `.bun` section, or an appended trailer.
`open_binary` then `inspect_artifact` reports root format `bun-standalone` with
the carrier, structural layout, module count, entry point, raw flags and
compile exec-argv; each module keeps its `/$bunfs/...` source name, loader,
encoding, sizes and SHA-256, with sourcemap, bytecode and module-info regions as
separate children. Use summary root metadata `entry_point_logical_path` as the
exact inventory `logical_paths` selector, then read the selected content node by
its artifact ID; the reader owns virtual-name normalization and indexed fallback. `extract-artifact` writes stored contents through the safe
output tree; route extracted JavaScript to `analyze_javascript_application`.
Layout labels describe record shapes, not a detected compiler version; unknown
or ambiguous layouts are unsupported. Bytecode is retained, not decoded, and the
executable is never run. See
[Bun standalone executables](https://github.com/morluto/rea/blob/main/docs/bun-standalone.md).
## Deno compile executables
A `deno compile` executable stores its application in a Mach-O `__SUI,d3n0l4nd`
section, an Intel Mach-O `<~sui-data~>` trailer, an ELF `SUI` note of type
`0x53554901`, or a PE `RT_RCDATA` resource named `D3N0L4ND`.
`open_binary` then `inspect_artifact` reports root format `deno-standalone`
with the carrier, entry point, recorded options, and one occurrence per virtual
file, including size and SHA-256. Transpiled output, CJS export analysis, and
source maps are separate occurrences with the file's `index` and their own
`role`. They normally use a `.deno-*` suffix on the file path and move to an
indexed path on a collision, so use the reported `logical_path`. Symlink targets
stay recorded path parts. A virtual filesystem that cannot be listed leaves
coverage unknown.
Environment-file values and CA certificate bytes are retained in local Evidence
with their counts and byte lengths. The npm snapshot length is reported and not
decoded. `extract-artifact` writes
stored files through the safe output tree; route extracted JavaScript to
`analyze_javascript_application`. Other layouts are unsupported, and the
executable is never run. See
[Deno compile executables](https://github.com/morluto/rea/blob/main/docs/deno-standalone.md).
@@ -1,146 +0,0 @@
# Packages and archives
Read only the sections needed for the selected task.
- [Packages and extraction](#packages-and-extraction)
- [Godot PCK](#godot-pck)
- [Bun standalone executables](#bun-standalone-executables)
- [Deno compile executables](#deno-compile-executables)
## Packages and extraction
For PyInstaller executables, the CArchive is the application layer; the native
bootloader is only its prefix. Prefer summary delivery for large inventories,
then retained inventory views with selected member fields/pages. Runtime options
are metadata, not files. Compact delivery still inventories and hashes members.
A small complete inventory can cost less than extra calls and schemas. See the
[Python packaged applications guide](https://github.com/morluto/rea/blob/main/docs/python-packaged-applications.md).
Use `inspect_artifact` for application bundles, archives, ZIP/APK/IPA/MSIX/AppX,
ASAR, or DMG inputs when the artifact graph and findings help answer the
question. It returns the complete artifact graph inline. Cite graph manifest
IDs when using them.
For an IPA or a macOS `.app`, ZIP, or DMG, pass the inventory Evidence to
`project_apple_application_graph`. It reports application roots and nested
bundles by path convention: app extensions, XPC services, frameworks, login
items, system and driver extensions, and plug-ins. It also lists privileged
helpers, launchd plists, symlinks, and each bundle's `info_plist_path` and
executable candidates. Roles are path conventions, not parsed plists; read the
listed plists with `inspect_plist`.
After `open_binary` on a `.app` or Mach-O, use `trace_dylib_resolution` to see
which file each `@rpath`, `@loader_path` and `@executable_path` load reaches for
every executable in the bundle. It also lists missing or weak loads and earlier
`@rpath` candidates that are absent. Narrow large bundles with `roots` or
`architecture`. System paths stay undetermined because the dyld shared cache
provides them.
Use `extract_artifact` when materialized files from a supported format are needed.
It extracts the active target and accepts optional `integrity_policy`; see the [MCP contracts](https://github.com/morluto/rea/blob/main/docs/reference/mcp-contracts.md).
It materializes all regular files into a fresh temporary directory chosen by
REA. Symlinks and encrypted entries are inventory facts, not extractable files.
The result reports the materialized directory; callers do not choose the
destination. No separate permission grant is needed.
For classic iNES cartridges, `open_binary` recognizes the byte signature without
an executable architecture. `inspect_artifact` retains header facts and each
PRG/CHR file unit's offset, length and SHA-256; its nested inventory's
`ines.reconstruction` verifies the actual ordered region bytes against the
whole-file digest. Extraction includes the original header, optional trainer
and uninterpreted trailing bytes. These file units are not mapper/CPU bank
maps. NES 2.0, UNIF, dirty legacy headers and truncated payloads are explicitly
refused. No emulator or 6502 analysis is performed. See the
[iNES guide](https://github.com/morluto/rea/blob/main/docs/ines-containers.md).
Native DMG traversal automatically uses a read-only mount on supported macOS
hosts, with an owned temporary mount directory and cleanup. It needs no approval
flag. Unsupported hosts or mount failures remain explicit limitations; do not
claim child inventory when only root identity is available. DMG extraction is
unavailable through `extract_artifact`. To access DMG files on macOS, create a
read-only mount with your own tools, select files from that mount for analysis,
and detach the mount you created when finished. REA's inspection mount is
temporary, not a retained export of the image's contents.
Use `inspect_byte_entropy` to triage high- and low-entropy ranges before extraction.
## Godot PCK
For a standalone PCK, `open_binary` then `inspect_artifact` with
`detail: "summary"` retains the complete static inventory. Use the returned
`parent_evidence_id` with `inspect_analysis_view` inventory occurrences filtered
by exact `logical_paths` or `occurrence_ids`, and join `artifact_id` to nodes
filtered by `artifact_ids`. Select only needed fields; identity links remain.
Prefer `inspect-artifact --detail summary --evidence_output PATH` for CLI tasks;
the same CLI discovers embedded packs via their EOF trailer without opening a
native analysis provider. Normal native opening remains unchanged.
Distinguish declared MD5 from observed SHA-256 and graph/page completeness from
`source_coverage`. Encrypted directories and sparse bundles have unknown entry
coverage; encrypted members have unknown content identity. An empty filtered
page cannot prove absence in unknown coverage. Retained views are historical,
not fresh filesystem checks. No decryption, Godot resource decoding, GDScript
decompilation or target execution is provided. See
[Godot PCK](https://github.com/morluto/rea/blob/main/docs/godot-pck.md).
UnityFS AssetBundle containers (`.bundle`, `.ab`, YooAsset packages and other
byte-identified files) list `CAB-*` SerializedFiles and `.resS` members with
header and node facts; none/LZ4/LZ4HC member blocks stream on demand. Bundles
declaring asset-bundle encryption keep header facts with unknown directory
coverage; LZMA member bytes retain named occurrences with unavailable hashes.
Virtual member offsets are not physical carrier locations. Format 6–8 readers
cache one decoded block and support retained filtered/paged views. No
decryption or SerializedFile object decoding is provided. See
[UnityFS](https://github.com/morluto/rea/blob/main/docs/unityfs.md).
WeChat mini-program packages (`.wxapkg` files with `0xBE`/`0xED` marks) list
members with declared names and absolute carrier offsets; member bytes are
stored verbatim. `V1MMWX` PC-client packages report `encrypted` directory
status with unknown coverage and are refused on extraction; no decryption is
attempted. See
[wxapkg](https://github.com/morluto/rea/blob/main/docs/wxapkg.md).
U-Boot legacy uImages use the same inspection/extraction workflows. Prefer a
retained summary and selected inventory views for header facts or component
IDs. Header addresses/CPU/OS are declarations; payloads remain as stored.
Multi-file components retain their aligned physical ranges; the full payload
is also materialized. Unknown name text retains its original 32 bytes. See
[U-Boot uImage](https://github.com/morluto/rea/blob/main/docs/uimage.md).
## Bun standalone executables
A `bun build --compile` executable stores its module graph in Mach-O
`__BUN,__bun`, a PE or ELF `.bun` section, or an appended trailer.
`open_binary` then `inspect_artifact` reports root format `bun-standalone` with
the carrier, structural layout, module count, entry point, raw flags and
compile exec-argv; each module keeps its `/$bunfs/...` source name, loader,
encoding, sizes and SHA-256, with sourcemap, bytecode and module-info regions as
separate children. Use summary root metadata `entry_point_logical_path` as the
exact inventory `logical_paths` selector, then read the selected content node by
its artifact ID; the reader owns virtual-name normalization and indexed fallback. `extract-artifact` writes stored contents through the safe
output tree; route extracted JavaScript to `analyze_javascript_application`.
Layout labels describe record shapes, not a detected compiler version; unknown
or ambiguous layouts are unsupported. Bytecode is retained, not decoded, and the
executable is never run. See
[Bun standalone executables](https://github.com/morluto/rea/blob/main/docs/bun-standalone.md).
## Deno compile executables
A `deno compile` executable stores its application in a Mach-O `__SUI,d3n0l4nd`
section, an Intel Mach-O `<~sui-data~>` trailer, an ELF `SUI` note of type
`0x53554901`, or a PE `RT_RCDATA` resource named `D3N0L4ND`.
`open_binary` then `inspect_artifact` reports root format `deno-standalone`
with the carrier, entry point, recorded options, and one occurrence per virtual
file, including size and SHA-256. Transpiled output, CJS export analysis, and
source maps are separate occurrences with the file's `index` and their own
`role`. They normally use a `.deno-*` suffix on the file path and move to an
indexed path on a collision, so use the reported `logical_path`. Symlink targets
stay recorded path parts. A virtual filesystem that cannot be listed leaves
coverage unknown.
Environment-file values and CA certificate bytes are retained in local Evidence
with their counts and byte lengths. The npm snapshot length is reported and not
decoded. `extract-artifact` writes
stored files through the safe output tree; route extracted JavaScript to
`analyze_javascript_application`. Other layouts are unsupported, and the
executable is never run. See
[Deno compile executables](https://github.com/morluto/rea/blob/main/docs/deno-standalone.md).
@@ -1,58 +0,0 @@
# Recorded captures and decoder discovery
## Historical web captures
Retained HAR/SAZ/native mitmproxy/aioquic qlog capture: `inspect_web_network_capture`. Select
original record ordinals when useful; inspect producer fields and byte/number
sidecars without fetching recorded URLs or claiming live attribution. For a decoder heap refusal, follow the reported `capture.decoder_old_generation_mib`
setting and bounds with `update_analysis_settings` (default 192 MiB, floor 8,
maximum 16384); the launch seed
`REA_CAPTURE_DECODER_OLD_GENERATION_MIB` affects future REA processes. Host memory
and `NODE_OPTIONS` do not raise that decoder allowance. See
[historical captures](https://github.com/morluto/rea/blob/main/docs/guides/web/web-network-captures.md) for the exact upstream
profile and credential exclusions. Qlog support starts with aioquic 1.5.0
JSON 0.3; stream coverage and endpoint loss declarations do not recover bodies
or establish network loss. Prefer detail summary, then kind qlog trace/stream
pages; select one trace, stream ID and direction for contribution/range pages.
Facts retain all-recorded-events-before-selection scope. Other qlog layouts
and PCAP correlation are unsupported.
## Packet fields and packet captures
Packet-field discovery: `inspect_packet_fields` searches installed protocols
and field names/types before choosing fields for `inspect_packet_capture`.
Query/page the relevant records; registrations do not establish observed traffic.
## Packet capture inspection
Recorded packet captures: `inspect_packet_capture` with selected TShark fields,
display filters and decode-as rules. Requires Linux, util-linux prlimit and
TShark 4.x on an absolute `PATH` entry or via the optional absolute
`REA_TSHARK_COMMAND` override; see
[capture guide](https://github.com/morluto/rea/blob/main/docs/guides/data/packet-captures.md).
Packet limits count input packets before filtering. Output defaults to 50 matching
frames; use `match_offset` / `match_limit` for later pages, or explicit null
limit for complete matching output. Preserve the source digest across pages.
This does not perform live capture.
For address/DNS/SNI/protocol summaries use `inspect_pcap_capture` with MCP
detail summary, then retained kind pcap pages. Requires Wireshark 4.x tshark
and capinfos, with Linux prlimit or Windows Job Objects; see
[packet summaries](https://github.com/morluto/rea/blob/main/docs/guides/data/pcap-analysis.md).
Select an exact DNS question for address pages; page exhaustion does not prove
network completeness. This reads recordings and does not acquire traffic.
## Signal decoder discovery
Decoder discovery: `inspect_signal_decoders` searches installed decoder IDs
or selected decoder help, including channels and options. Query and page the
relevant details before choosing settings; this does not scan hardware.
## Recorded logic and signals
Recorded logic/signal traces: `decode_signal_recording` with explicit decoder,
channel and protocol settings. On Linux with util-linux prlimit, supply sigrok-cli 0.7.2 through
`REA_SIGROK_COMMAND`; see [recording guide](https://github.com/morluto/rea/blob/main/docs/guides/signals/signal-recordings.md).
Output defaults to 50 literal lines; select annotation classes and page with
`line_offset` / `line_limit`, or explicit null for all remaining output.
Window counts are display lines, not annotations or samples. Preserve source digests across pages.
This reads existing files; it does not acquire hardware signals.
@@ -1,12 +1,5 @@
# Runtime observation
Read only the sections needed for the selected task.
- [Native runtime questions](#native-runtime-questions)
- [Browser and Electron](#browser-and-electron)
- [Who performs the action](#who-performs-the-action)
- [Fiddler Everywhere traffic debugging](#fiddler-everywhere-traffic-debugging)
## Native runtime questions
Start from the requested observation. State the selected target, input, expected
@@ -61,18 +54,6 @@ and [native calls/UI](https://github.com/morluto/rea/blob/main/docs/guides/nativ
## Browser and Electron
Passive traffic capture of a SPA: the default scope is the page's own origin,
and data APIs usually live on another origin. Discover API origins from
absolute URLs in loaded bundles or script text via JavaScript analysis tools
(another passive capture without those origins still scopes to the page
origin, and `completeness.excluded` counts lack URLs). Pass both the page
origin and those API origins in `allowed_origins` — the list replaces the
default scope — and read `completeness.excluded` before reporting that an
API is absent. For long captures with body shapes, use
`export_evidence_bundle` instead of expecting an inline result.
User-owned Electron runtime already open: `list_electron_targets`. Before an electron-main Inspector attempt, check packaged fuses with `inspect_electron_fuses`; disabled `EnableNodeCliInspectArguments` means the app ignores `--inspect`.
Supply the existing browser or Electron instance's literal-port loopback CDP
endpoint in the request. No separate permission configuration or per-call
approval flag is needed. Browser discovery lists eligible HTTP(S) pages;
+3 -1
View File
@@ -755,7 +755,9 @@ jobs:
needs: [changes, static, build]
if: needs.changes.outputs.apple == 'true'
runs-on: macos-15
timeout-minutes: 15
# Passing runs take 10-15 minutes on macOS runners of varying speed, so a
# 15-minute budget cancelled about a third of runs. Keep a hang bounded.
timeout-minutes: 25
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+3 -1
View File
@@ -464,7 +464,9 @@ class ReaJadxBridge {
if (element.getTagName().equals("uses-sdk")) {
minSdk = element.getAttributeNS(android, "minSdkVersion");
targetSdk = element.getAttributeNS(android, "targetSdkVersion");
} else if (element.getTagName().equals("uses-permission")) {
} else if (element.getTagName().equals("uses-permission")
|| element.getTagName().equals("uses-permission-sdk-23")
|| element.getTagName().equals("uses-permission-sdk-m")) {
String permission = element.getAttributeNS(android, "name");
if (!permission.isEmpty()) permissions.add(permission);
}
-9
View File
@@ -958,15 +958,6 @@ class _BridgeServer(threading.Thread):
proof.update(fragment.encode("utf-8"))
return proof.hexdigest()
def _encode_authenticated_response(
self, value: dict[str, Any], client_nonce: str, server_nonce: str
) -> bytes | None:
"""Return one exact frame. The socket path writes the same pieces."""
proof = self._authenticated_proof(value, client_nonce, server_nonce)
if proof is None:
return None
return b"".join(self._iter_authenticated_frame(value, proof))
def _iter_authenticated_frame(self, value: dict[str, Any], proof: str):
yield _AUTHENTICATED_PREFIX
for fragment in _response_fragments(value):
-7
View File
@@ -154,13 +154,6 @@ class HopperApiFacade:
)
def _configure_hopper_api(document_provider):
"""Inject Hopper's document provider or a standalone test facade."""
global _hopper_api, _rea_session_document
_hopper_api = HopperApiFacade(document_provider)
_rea_session_document = None
def _api():
"""Resolve Hopper globals lazily so importing this module needs no Hopper."""
global _hopper_api
+24 -27
View File
@@ -238,7 +238,9 @@ broad changes or diagnosing CI, rather than after every edit. Package/install
changes additionally need package verification; provider changes need actual
provider evidence.
Vitest projects use up to two workers, bounded by host parallelism.
Vitest projects use up to two workers, bounded by host parallelism. GitHub
Actions jobs own their runner and use up to four; see
[test deadlines](#developer-commands) for how deadlines scale there.
`process-boundary` runs later with serial files because process-tree sampling
shares host resources. Acceptance and process-global cases use isolated forks
without serial scheduling. Pure domain/service, composition, conformance,
@@ -283,7 +285,7 @@ coverage. Ordinary CI also exhausted the former 384 MiB budget after earlier
files loaded the shared source/contract graph, while later command registration
and schema/help rendering still needed transient allocation space. This is a
measured capacity control, not a suite-wide speedup claim. The worker count
remains bounded at two, and spawned REA processes retain their own heap settings.
remains bounded, and spawned REA processes retain their own heap settings.
Coverage enablement still controls instrumentation, test deadlines and coverage
thresholds; explicitly disabling it or selecting only a provider does not enable
instrumentation.
@@ -299,6 +301,21 @@ work from fixture generation, schema compilation, subprocess startup and cleanup
Choose elapsed, CPU and process-family memory limits before the run; a worker
count or test timeout alone does not enforce those limits.
A test deadline turns a hang into a named failure; it is not a performance
assertion. `vitest.config.ts` applies the deadlines in
`tests/support/testDeadlines.ts` per project: 30 seconds for source projects
and 120 seconds for built projects, with hooks sharing their project's
deadline. Coverage doubles every deadline, and so does hosted CI, where
CPU-bound cases share every core with other workers and their subprocesses.
Do not pass a per-test timeout to fit a slow host. A child run through a
test process registry (`processTest`, `cli.run`, or `createTestProcessRegistry`)
gets the running test's remaining deadline less a reporting reserve, so a hung
child fails with its captured output. Pass `timeoutMs` only when the child's deadline is itself under test. Tag a bounded workload that needs longer with
`{ tags: ["long-running"] }`, or a whole file with
`// @module-tag long-running`. Assert elapsed time explicitly when speed is the
behavior under test. Derive an explicit deadline only when a test must outlive
an operation deadline whose diagnostics it checks.
Keep capacity fixtures intact when diagnosing a test failure. For byte equality
of large buffers, use `Buffer.equals` or `Buffer.compare`; a generic structural
diff can allocate far more than the production operation.
@@ -1077,32 +1094,12 @@ existing four-minute timing collection plus the bounded diagnostic stages.
Fast built shards target under ten minutes of uncontended shard-job execution.
That figure is the `test-shard` job. The job waits for classification and the
build, and the portable-test merge runs after the slowest shard. On 2026-10-20,
run `39451548945` measured those surrounding jobs with runner queue excluded:
classification 15 seconds, build 49 seconds, and the portable-test merge 29
seconds. Shard 1/5 of that run spent 22 seconds before built Vitest and 3
seconds after it, including `npm ci` at 9 seconds. `node_modules` is about 674
MiB, so publishing it as a shared artifact would transfer more data than that
install saves. The npm download cache stays the setup cache.
File timings from that run put the built suite at 7944 seconds of module time.
The sequencer keeps each file's full duration on one worker. Extra workers
overlap other files in the same sequence group, and `process-boundary` runs
after that group. Scheduling the unsplit inventory onto five shards peaks at
820 seconds of tests. That run's slowest built shard job lasted 925 seconds
(shard 4/5), and shard 1/5 spent 874 seconds in built Vitest, so hosted job
time sits above the file schedule. Six shards of the same inventory peak at
684 seconds of tests. Eight shards, after the 578-second loader file is split
by case count (54 cases stay in that file and 46 move into each of three
files), peak at 513 seconds of tests. With the measured 25-second shard setup,
that shard job is about 538 seconds. Adding the measured classification, build,
and merge makes an uncontended portable critical path of about 631 seconds,
still before the gap between this file schedule and hosted Vitest. Runner queue
is separate and grows when more shard jobs compete for runners.
build, and the portable-test merge runs after the slowest shard. Runner queue is
separate and grows when more shard jobs compete for runners. The npm download
cache stays the setup cache rather than a shared `node_modules` artifact.
`process-boundary` stays serial because process-tree sampling shares the host.
Raising the worker cap from two to four on this four-core host cut a six-file
acceptance sample from 96–101 seconds to 71 seconds and did not remove the
single-file floor, so the cap stays two. Full qualification keeps six covered
Hosted CI jobs use four workers; other hosts stay at two
because they are shared with other checks. Full qualification keeps six covered
shards; its longer instrumentation is outside this fast budget.
The separate `CI timings` workflow batches recent completed CI and Release runs
@@ -215,7 +215,10 @@ Resolution follows dyld:
image that loaded it, up to the process root.
- Dependencies load dependents-first in load-command order, and each image
loads once per process. A later @rpath request whose install name matches an
already loaded image reuses it.
already loaded image reuses it. Loading an image and expanding its dependencies
are separate: an already loaded sibling can first be expanded through another
dependency, inheriting that traversal's rpath stack. Cycles do not expand an
image twice, and lazy edges do not establish a launch-time traversal chain.
- Symlinks such as `Versions/Current` are followed inside the analyzed root.
An escape through `..` or an absolute link is `escapes-target`.
- Dyld path components are POSIX names. On Windows hosts, link text uses `\`
@@ -252,8 +255,12 @@ one of:
A `conditional` image is still traced, because it is an in-root candidate
that may load. Every edge below it has `loader_conditional: true`, and a
request that reuses it by install name is `conditional` too. An in-root dylib
with no platform load command remains unknown unless the root is an executable
request that reuses it by install name is `conditional` too. A definitely loaded
image can also first expand through a conditional chain: its dependency edges
then have `loader_conditional: true`, while later reuse of the image retains its
definite first-load result.
An in-root dylib with no platform load command remains unknown unless the root is an executable
with observed macOS platform and the candidate's CPU type is x86_64, i386, or arm64. In that macOS context,
[Apple's dyld](https://github.com/apple-oss-distributions/dyld/blob/fd8d0c4d52320ebf64db34f3cb280310d905c5ae/mach_o/UnsafeHeader.cpp#L1137-L1145) supplies an implicit macOS platform to legacy dylibs on those CPU
types. The candidate records `platform_basis: "macos-dyld-implicit"`; the
+14 -8
View File
@@ -222,7 +222,7 @@ cannot answer, such as an address without one exact file offset, omits this
hint; follow its `remediation.action`. `recovery.kind=inspect_timeout_state` names
the operation and the REA server/provider deadline. Inspect partial Evidence
and reported cleanup state, then follow the returned remediation. Change a
request field or REA_* setting only when the returned remediation explicitly
request field or REA\_\* setting only when the returned remediation explicitly
recommends that change. Restart REA when that text says the value is read at
startup. A mention that a control does not raise the deadline is not a
recommendation to change it. When it names no setting, reduce supported work;
@@ -1065,6 +1065,17 @@ from an older request does not override a newer outcome. Request deadlines and
cancellations neither mark nor clear availability. A new provider client starts
without these outcomes.
A `provider_missing` group may also list `alternatives`: advertised tools the
current provider makes available for part of that operation's question. When
the provider does not declare `next_address` or `prev_address`, they list
whichever of `inspect_native_instruction`, `read_function_instructions` and
`procedure_assembly` qualify. These answer instruction-level questions only:
none navigates arbitrary analyzed objects such as data or addresses outside a
procedure, and neither listing order nor address plus length proves executed
control flow. Calling the missing operation returns `capability_unavailable`
with the same tools in `details.alternatives` and a remediation stating each
one's scope. Without a qualifying alternative, both keep their generic advice.
Standalone command operations (such as `inspect_with_objdump` and
`execute_rizin_command`) similarly reflect observed executable launch failures:
an unprobed executable is reported available; once a launch failure is observed,
@@ -1911,13 +1922,10 @@ support for omitted historical values.
known and differ. If only one value is known, storage comparison is unknown;
when both are omitted, storage is not compared.
<!-- BEGIN:compare-artifacts:compared-format-metadata-kinds -->
`compare_artifacts` also compares the declarations in the `format_metadata`
of UnityFS members (`unityfs-entry`), PyInstaller entries
`compare_artifacts` also compares the declarations in the `format_metadata` of <!-- BEGIN:compare-artifacts:compared-format-metadata-kinds -->UnityFS members (`unityfs-entry`), PyInstaller entries
(`pyinstaller-entry`), Node SEA members (`node-sea-entry`),
ESP image segments (`esp-image-segment`), DfuSe elements (`dfuse-element`),
ESP flash partitions (`esp-flash-partition`) and FIT images (`fit-image`);
ESP flash partitions (`esp-flash-partition`) and FIT images (`fit-image`)<!-- END:compare-artifacts:compared-format-metadata-kinds -->;
each format's guide names the compared fields. A changed declaration is a
metadata change, including when content is unavailable. A declaration that
only one side records, or that the other side records as another kind, is
@@ -1928,8 +1936,6 @@ container or from caller options that identify the target, or repeat
occurrence fields, except Godot PCK source paths, which
[Godot PCK archives](../godot-pck.md) explains.
<!-- END:compare-artifacts:compared-format-metadata-kinds -->
Every occurrence records `entry_kind`, `executable` and `encrypted`. A
different entry kind or `encrypted` value is a `metadata` difference, and a
different `executable` value is an `executable` difference. Occurrence and node
+6
View File
@@ -96,6 +96,10 @@ Persistent command Evidence retains the caller-selected and resolved startup pat
Session status retains up to 64 recent protocol frames. Each history entry keeps a complete-character UTF-8 prefix of at most 16 KiB, plus a truncation marker when needed; `recent_output_truncated` reports omitted history. This history budget also accounts for replacement characters from invalid UTF-8. It does not shorten command Evidence, whose output budget defaults to 16 MiB. Rizin debug startup waits 30000 ms with no runtime override. Each command frame defaults to 120000 ms; its deadline, command-output budget, session capacity and pending-command queue use the [shared debugger launch settings](#debugger-launch-settings). The 16777216-byte frame cap and 4096-frame command cap remain fixed. Evidence names the cap that stopped the command. The one-shot `command_timeout_ms` setting does not change a debug session.
### Stopped debugger sessions
A Rizin or GDB session stops accepting commands when REA stops it, for example after an output or frame limit, a command deadline, cancellation or a protocol failure, or when the debugger process exits. REA keeps the stopped session and the first reason it observed until `close_rizin_debug_session` or `close_gdb_session` releases it, including after a retried cleanup succeeds. `rizin_debug_session_status` and `gdb_session_status` then report `state: "closed"` with that reason in `closed_reason`. A later `rizin_debug_command` or `gdb_console` on the session fails with `capability_unavailable`; `details.reason` carries the same reason, and the remediation is to close the session and start a new one. A `session_id` that this server never returned, or that close already released, fails with `invalid_request` instead.
### Debugger launch settings
GDB and Rizin share these settings in the environment that launches REA:
@@ -136,6 +140,8 @@ rea debug-with-rizin --inferior-tty /dev/pts/7 ./program dr
rea execute-rizin-command ./program iI
```
Rizin exits with status 0 even when its shell refuses a statement, so the exit code does not show whether the command ran. Rizin 0.6 through 0.9.1 log one of four stderr lines for such a refusal: `ERROR: Command '…' does not exist.`, ``ERROR: Wrong number of arguments passed to `…`…``, `ERROR: core: Error while executing command: …` (an invalid statement), or `ERROR: core: Error while parsing command: …`. Rizin then skips the refused statement and every later one in the command. When the completed process's stderr contains one of these lines, REA reports `completion_status: "rejected"` and adds a limitation naming the refusal. stdout then holds only the output of earlier statements, so empty stdout is not an empty result. Other `ERROR:` lines, such as `ERROR: Cannot find function at …`, come from commands that ran; REA keeps them in stderr and reports `complete`. A command that lowers `log.level` or redirects stderr hides refusals from REA.
Captured stdout and stderr use the same `max_output_bytes` (CLI: `--max-output-bytes`) admission and recovery described above, default 16 MiB. One-shot commands independently use `command_timeout_ms`. Raising output admission cannot recover an earlier capture; command effects and cleanup must be checked before another invocation.
### Decoder and map inputs
@@ -10,8 +10,8 @@
4. Verifier: `appTaskPython.ts` renders `READER_RUNNER`, `READERS`,
`_reader_views` and `evaluate_with_readers` only for a task with readers,
so a task without renders as before.
5. Application: `readTaskReaderViews` and `readerViewsOfBytes` in
`DesktopTaskReaders.ts`; `runReaderHere` in `src/desktop/TaskReaderProcess.ts`;
5. Application: `readerViewsOfBytes` in `DesktopTaskReaders.ts`;
`runReaderHere` in `src/desktop/TaskReaderProcess.ts`;
the witness runs readers through `AgentEnvComputer.exec`; the export runs
them on `shippedSeedBytes` and the end's retained content.
6. Docs: the tasks reference, the witness page, the build skill's recipes for
+8 -22
View File
@@ -842,28 +842,14 @@ branches are not producers. A tier with no qualifying artifact leaves history
unset, and Vitest uses its own assignment. A later schedule that reuses
qualification without uploading timings leaves the previous artifact in place.
On 2026-10-21 the newest full history was run
`39545047294`, recorded before compiled-CLI session reuse. Fast run
`39552964873` packed its eight built shards from that history, and those shard
jobs executed for 319–591 seconds. Replaying that run's file durations through
the sequencer's two-worker schedule balances every shard at about 404 seconds
of file time. The same replay with the stale history weights leaves the slowest
shard near 547 seconds and the fastest near 293. That spread is file-schedule
time, not runner queue, which is why each tier keeps its own history.
CI assigns discovered files by historical duration, with a deterministic
fallback weight for new files, rather than dividing only by file count.
Each file keeps its full duration on one worker. Extra workers overlap other
files in the same sequence group, and `process-boundary` runs after that
group. History published before the competing-loader split still names only
`javascriptLiteralPercentPaths.test.ts`. Until an artifact records
`javascriptLiteralLoaderOwnership.test.ts`, the sequencer assigns that
recorded duration by the pre-split case counts: 54/192 stays with the
percent-path file and 138/192 goes to the consolidated loader file. History
that names the retired Early, Middle, and Late shards sums those durations
onto the consolidated file instead, and scales a partial set as three equal
shares. That weight is the old dual-surface cost. The demoted file runs every
loader once through the shared stdio MCP session, so the first schedule
overestimates it. Every discovered file still belongs to exactly one shard;
Each tier keeps its own history because stale weights from another tier, or
from before a large test change, unbalance the shards.
CI assigns discovered files by historical duration rather than dividing only
by file count. A file with no recorded duration, such as a newly added or
renamed test, is weighted at the median recorded duration until a published
artifact records it. Each file keeps its full duration on one worker. Extra
workers overlap other files in the same sequence group, and `process-boundary`
runs after that group. Every discovered file still belongs to exactly one shard;
worker limits and project ordering remain unchanged. Fast qualification keeps
eight built shards without coverage. The merge job rejects successful reports
with missing, unexpected, or duplicate portable files. Coverage instrumentation
+1
View File
@@ -3,6 +3,7 @@
"entry": [
"src/main.ts",
"src/cli.ts",
"src/cliOneShot.ts",
"src/cliOutput.ts",
"src/cliBuiltinArguments.ts",
"src/cli/cliResultOutput.ts",
+1
View File
@@ -3,6 +3,7 @@
"entry": [
"src/main.ts!",
"src/cli.ts!",
"src/cliOneShot.ts!",
"src/cliOutput.ts!",
"src/cliBuiltinArguments.ts!",
"src/cli/cliResultOutput.ts!",
+3 -2
View File
@@ -5,7 +5,8 @@ import { fileURLToPath, pathToFileURL } from "node:url";
/**
* Reduce filesystem resolution during CLI and MCP startup. Keep dependencies
* external, dynamic imports lazy, and original compiled worker entrypoints.
* external and original compiled worker entrypoints. Dynamic imports can split
* into lazy chunks unless the same module is also statically reachable.
*/
export const bundleRuntime = async (root) => {
const compiledRoot = resolve(root, "dist");
@@ -23,7 +24,7 @@ export const bundleRuntime = async (root) => {
"cli-runtime",
{
cli: "cli.js",
cliOutput: "cliOutput.js",
cliOneShot: "cliOneShot.js",
cliBuiltinArguments: "cliBuiltinArguments.js",
cliResultOutput: "cli/cliResultOutput.js",
mcpDoctor: "mcpDoctor.js",
+1 -1
View File
@@ -1,5 +1,5 @@
import { execFile } from "node:child_process";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { mkdir, writeFile } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";
+5
View File
@@ -19,6 +19,11 @@ if (
const child = spawn(
process.execPath,
[
// Reuse the development runner's POSIX process-group owner. Windows
// source tests must remain usable without the built native Job Object.
...(process.platform === "win32"
? []
: [resolve("scripts/run-exclusive.mjs"), "--unlocked", process.execPath]),
resolve("node_modules/vitest/vitest.mjs"),
"run",
...projects.flatMap(({ name }) => ["--project", name]),
+8 -7
View File
@@ -37,21 +37,22 @@ const generated = addSkillManifestToolCount(
FULL_CATALOG_IDENTITY.counts.mcp_tools,
);
if (!generated.ok) throw generated.error;
const source = generated.value.replaceAll(latestSpecifier, pinnedSpecifier);
const paths = await filePaths(sourceRoot);
// Rebuild this owned output directory so removed references cannot survive a build.
if (!check) await rm(outputRoot, { recursive: true, force: true });
for (const relativePath of paths) {
const path = join(outputRoot, relativePath);
if (!check) await mkdir(dirname(path), { recursive: true });
const content =
relativePath === "SKILL.md"
? generated.value
: await readFile(join(sourceRoot, relativePath), "utf8");
await ensureGeneratedFile({
path,
source: content
.replaceAll("\r\n", "\n")
.replaceAll(latestSpecifier, pinnedSpecifier),
source:
relativePath === "SKILL.md"
? source
: (await readFile(join(sourceRoot, relativePath), "utf8")).replaceAll(
"\r\n",
"\n",
),
check,
generateCommand: "npm run docs:generate",
});
+11
View File
@@ -2,6 +2,17 @@ import type {
Client,
CallToolRequestOptions,
} from "@modelcontextprotocol/client";
import type { VerifierCommands } from "./verify-commands.mjs";
/** Keep the opened target and its owned stdio server alive through verification. */
export function withArtifactMcp(
target: string,
verify: (client: Client) => Promise<void>,
options?: {
readonly commands?: VerifierCommands;
readonly signal?: AbortSignal;
},
): Promise<void>;
/** Call with the server's advertised schema; callers cannot replace it. */
export function artifactMcpCall(
+1
View File
@@ -15,6 +15,7 @@ export function parseDevelopmentTestRequest(
export function developmentTestPlan(
request: DevelopmentTestRequest,
baseCommit?: string,
resolvedPaths?: readonly string[],
): {
readonly needsBuild: boolean;
readonly artifactTasks: readonly string[];
+7 -3
View File
@@ -58,7 +58,11 @@ const artifactConsumers = {
};
/** Plan source feedback or explicit tests with their runtime/artifact prerequisites. */
export const developmentTestPlan = (request, baseCommit) => {
export const developmentTestPlan = (
request,
baseCommit,
resolvedPaths = request.paths,
) => {
const explicit = request.paths.length > 0;
if (
request.mode === "changed" &&
@@ -70,12 +74,12 @@ export const developmentTestPlan = (request, baseCommit) => {
? []
: SOURCE_TEST_PROJECTS.map(({ name }) => name);
return {
needsBuild: request.paths.some(
needsBuild: resolvedPaths.some(
(path) => projectForTest(path).group === "built",
),
artifactTasks: Object.entries(artifactConsumers).flatMap(
([task, consumers]) =>
request.paths.some((path) => consumers.includes(path)) ? [task] : [],
resolvedPaths.some((path) => consumers.includes(path)) ? [task] : [],
),
vitestArguments: [
"run",
+5 -2
View File
@@ -10,7 +10,6 @@ import { access, readFile, readdir, stat } from "node:fs/promises";
import { dirname, join } from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import { promisify } from "node:util";
import { GHIDRA_ANNOTATIONS_RETAINED } from "../../dist/ghidra/GhidraProviderClient.js";
import Ajv from "ajv/dist/2020.js";
import addFormats from "ajv-formats";
import { OFFICIAL_TOOL_CONTRACTS } from "../../dist/contracts/officialToolContracts.js";
@@ -571,7 +570,11 @@ export async function verifyGhidraBoundaries(
// The producer reason describes the failure; continuing-session guidance
// is projected separately and the following live calls verify it.
assert.match(regexError.details.reason, /stack while matching pattern/u);
assert.ok(regexError.message.includes(GHIDRA_ANNOTATIONS_RETAINED));
assert.ok(
regexError.message.includes(
"The active analysis session and annotations remain available.",
),
);
rejectedCalls++;
const nestedPattern = "(".repeat(12000) + "a" + ")".repeat(12000);
const compileFailure = await client.callTool(
+35 -13
View File
@@ -2,10 +2,7 @@ import { randomUUID } from "node:crypto";
import type { RetainedResourceScope } from "../../src/application/AnalysisProvider.js";
import { AnalysisCleanupLease } from "../../src/artifacts/AnalysisCleanupLease.js";
import {
ArtifactResourceScope,
type ArtifactResourceOwner,
} from "../../src/artifacts/ArtifactResourceScope.js";
import { ArtifactResourceScope } from "../../src/artifacts/ArtifactResourceScope.js";
import {
prepareOwnedCommand,
runOwnedCommand,
@@ -61,6 +58,7 @@ export interface VerifierCommands {
arguments_: readonly string[],
options?: VerifierCommandOptions,
): Promise<VerifierCommandOutput>;
/** Retain a live resource until close, independently of ordinary command execution. */
adopt(owner: VerifierCleanupOwner, resource: string): void;
readonly cleanupResources: readonly string[];
close(): Promise<void>;
@@ -152,13 +150,17 @@ const commandLimits = (options: VerifierCommandOptions) => {
};
};
type AdoptedVerifierOwner = {
readonly kind: "lease";
readonly resource: string;
readonly lease: AnalysisCleanupLease;
};
class VerifierCommandOwner implements VerifierCommands {
readonly #resources = new ArtifactResourceScope();
readonly #controller = new AbortController();
readonly #adopted = new WeakMap<
VerifierCleanupOwner,
ArtifactResourceOwner
>();
readonly #adopted = new WeakMap<VerifierCleanupOwner, AdoptedVerifierOwner>();
readonly #liveAdopted = new Set<AdoptedVerifierOwner>();
readonly #resourceOwner: RetainedResourceScope;
#closed = false;
#released = false;
@@ -170,13 +172,21 @@ class VerifierCommandOwner implements VerifierCommands {
) => Promise<SpawnedOwnedProviderProcess>,
) {
this.#resourceOwner = {
retain: (owner, resource) => this.adopt(owner, resource),
retain: (owner, resource) =>
this.#resources.retain(this.#adoptedOwner(owner, resource)),
run: (work) => this.#resources.run(work),
};
}
get cleanupResources(): readonly string[] {
return this.#resources.cleanupResources;
return [
...new Set([
...this.#resources.cleanupResources,
...[...this.#liveAdopted].flatMap(({ lease, resource }) =>
lease.released ? [] : (lease.cleanupResources ?? [resource]),
),
]),
];
}
exec(
@@ -233,6 +243,15 @@ class VerifierCommandOwner implements VerifierCommands {
}
adopt(owner: VerifierCleanupOwner, resource: string): void {
const retained = this.#adoptedOwner(owner, resource);
if (this.#closed) this.#resources.retain(retained);
else this.#liveAdopted.add(retained);
}
#adoptedOwner(
owner: VerifierCleanupOwner,
resource: string,
): AdoptedVerifierOwner {
if (this.#released)
throw new Error("Verifier command resource release has completed");
let retained = this.#adopted.get(owner);
@@ -244,15 +263,18 @@ class VerifierCommandOwner implements VerifierCommands {
};
this.#adopted.set(owner, retained);
}
// An admitted command can transfer a late preparation/cleanup capability
// while close is joining it. Acquisition must precede this scope's release.
this.#resources.retain(retained);
return retained;
}
close(): Promise<void> {
this.#closed = true;
this.#controller.abort(new Error("Verifier command owner closed"));
if (this.#closing !== undefined) return this.#closing;
// Live adoption owns a lifetime, not a failed cleanup. Only closure moves
// these leases into the retry scope; command failures enter it immediately.
// Late transfers during admitted work still join that same close below.
for (const owner of this.#liveAdopted) this.#resources.retain(owner);
this.#liveAdopted.clear();
let attempt: Promise<void>;
attempt = this.#resources
.close()
+1 -1
View File
@@ -54,7 +54,7 @@ if (await pathExists(join(root, "src"))) {
"dist/main.js",
"dist/mcp/main.js",
"dist/cli-runtime/cli.js",
"dist/cli-runtime/cliOutput.js",
"dist/cli-runtime/cliOneShot.js",
"dist/cli-runtime/cliBuiltinArguments.js",
"dist/cli-runtime/cliResultOutput.js",
"dist/cli-runtime/mcpDoctor.js",
+54 -142
View File
@@ -64,7 +64,7 @@ const runtimeFiles = [
? ["dist/mcp/main.js", "dist/cli-runtime/mcpDoctor.js"]
: [
"dist/cli-runtime/cli.js",
"dist/cli-runtime/cliOutput.js",
"dist/cli-runtime/cliOneShot.js",
"dist/cli-runtime/cliResultOutput.js",
]),
];
@@ -104,154 +104,66 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
);
}
} else {
const {
cliConfigurationError,
renderCliOutputArgumentError,
renderEmptyFilteredCliOutput,
renderVersionReport,
parseCliOutputArguments,
} = await import("../dist/cli-runtime/cliOutput.js");
const { runOneShotCli } = await import("../dist/cli-runtime/cliOneShot.js");
guardClosedStdout();
const cliArgs = isShellCompletion
? args
: builtinArguments.normalizeCliArguments(args);
// Completion words are transport data, including partial output flags.
const outputOptions = parseCliOutputArguments(
isShellCompletion ? [] : cliArgs,
);
const outputArguments = outputOptions.validation;
// `--version` with an explicit format reports the runtime as a document;
// plain `--version` stays Incur's bare version string.
const versionOutput = renderVersionReport(
outputOptions,
{
packageName: packageJson.name,
packageVersion: packageJson.version,
versions: process.versions,
platform: process.platform,
architecture: process.arch,
},
process.stdout.isTTY === true,
);
const configurationError = cliConfigurationError(process.env);
let dispatchFailure;
let closeCliResources;
// Incur can exit from the callback below before this function returns.
// That callback owns the status, including a closed-pipe exit of 141.
let exiting = false;
try {
// Doctor is the readiness report for this failure. Other commands still stop
// here so they do not run with an unusable log level.
if (
configurationError !== undefined &&
(isShellCompletion ||
builtinArguments.splitIncurBuiltinArguments(cliArgs).command[0] !==
"doctor")
) {
process.stderr.write(`${configurationError}\n`);
process.exitCode = 1;
} else if (!outputArguments.ok) {
writeStdout(renderCliOutputArgumentError(outputArguments));
process.exitCode = 1;
} else if (versionOutput !== undefined) {
writeStdout(versionOutput);
} else if (
!isShellCompletion &&
builtinArguments.isProjectScopedAmpRegistration(cliArgs)
) {
// Amp has global settings only. Share the parsed scope fact with the
// registration workflow and refuse before loading the command runtime.
//
// A caller that asked for a structured document gets the refusal in
// that document, through the same pre-execution error renderer the
// output-argument branch uses; anything else keeps the plain message.
const refusalMessage =
"Amp does not support project-scoped registration. Run without --no-global to register Amp globally, or select a client with project settings. No client settings were changed.";
const refusalFormat = outputOptions.format;
if (
refusalFormat === "json" ||
refusalFormat === "jsonl" ||
refusalFormat === "yaml"
) {
writeStdout(
renderCliOutputArgumentError({
ok: false,
format: refusalFormat,
code: "AMP_PROJECT_SCOPE_UNSUPPORTED",
message: refusalMessage,
}),
);
} else {
process.stderr.write(`${refusalMessage}\n`);
}
process.exitCode = 1;
} else {
const cliRuntime = await import("../dist/cli-runtime/cli.js");
closeCliResources = cliRuntime.closeCliResources;
const { createCli } = cliRuntime;
const { createCliResultOutput } =
await import("../dist/cli-runtime/cliResultOutput.js");
let wroteOutput = false;
const resultOutput = createCliResultOutput(outputOptions, process.stdout);
try {
await createCli(process.env, resultOutput).serve(cliArgs, {
stdout: (output) => {
if (resultOutput?.handled) {
if (resultOutput.failed && !stdoutClosed)
process.stderr.write(output);
return;
}
if (output.length > 0) wroteOutput = true;
if (resultOutput?.captureSdkOutput(output)) return;
writeStdout(output);
},
// A command failure caused by the closed pipe keeps the pipe's status.
// Worker shutdown is shared with the dispatch finally below. A failed
// shutdown is reported here because this callback is what exits.
exit: (code) => {
exiting = true;
void (async () => {
let status = typeof code === "number" ? code : 0;
try {
await closeCliResources();
} catch (cleanup) {
process.stderr.write(
`Failed to close CLI resources: ${cleanupFailureText(cleanup)}\n`,
);
if (status === 0) status = 1;
}
try {
await resultOutput?.finishSdkOutput(status);
} catch (cause) {
if (!stdoutClosed)
process.stderr.write(`${cleanupFailureText(cause)}\n`);
if (status === 0) status = 1;
}
process.exit(stdoutClosed ? SIGPIPE_EXIT_STATUS : status);
})();
},
});
} catch (cause) {
if (wroteOutput) throw cause;
const recovered = await resultOutput
?.recoverFormattingFailure(cause)
.catch((writeFailure) => {
// Recovery writes outside Incur's command boundary. A reader that
// declined this document keeps the same quiet SIGPIPE outcome.
if (!stdoutClosed) throw writeFailure;
return true;
});
if (!recovered) throw cause;
process.exitCode = stdoutClosed ? SIGPIPE_EXIT_STATUS : 1;
}
if (!wroteOutput && !resultOutput?.handled)
writeStdout(renderEmptyFilteredCliOutput(outputOptions) ?? "");
// An SDK failure exits through the callback, which owns its pending write.
if (!exiting)
await resultOutput?.finishSdkOutput(
typeof process.exitCode === "number" ? process.exitCode : 0,
);
}
await runOneShotCli(args, {
environment: process.env,
runtime: {
packageName: packageJson.name,
packageVersion: packageJson.version,
versions: process.versions,
platform: process.platform,
architecture: process.arch,
},
stdoutIsTerminal: process.stdout.isTTY === true,
resultDestination: process.stdout,
closedStdoutStatus: SIGPIPE_EXIT_STATUS,
writeStdout,
writeStderr: (text) => {
process.stderr.write(text);
},
stdoutClosed: () => stdoutClosed,
exitCode: () =>
typeof process.exitCode === "number" ? process.exitCode : 0,
setExitCode: (code) => {
process.exitCode = code;
},
ownCliResources: (close) => {
closeCliResources = close;
},
exitRequested: () => exiting,
// A command failure caused by the closed pipe keeps the pipe's status.
// Worker shutdown is shared with the dispatch finally below. A failed
// shutdown is reported here because this callback is what exits.
exit: (code, finishOutput) => {
exiting = true;
void (async () => {
let status = code;
try {
await closeCliResources();
} catch (cleanup) {
process.stderr.write(
`Failed to close CLI resources: ${cleanupFailureText(cleanup)}\n`,
);
if (status === 0) status = 1;
}
try {
await finishOutput(status);
} catch (cause) {
if (!stdoutClosed)
process.stderr.write(`${cleanupFailureText(cause)}\n`);
if (status === 0) status = 1;
}
process.exit(stdoutClosed ? SIGPIPE_EXIT_STATUS : status);
})();
},
});
} catch (cause) {
if (cause?.code === "EPIPE") {
stdoutClosed = true;
+17 -3
View File
@@ -1,12 +1,13 @@
import { spawn } from "node:child_process";
import { stat } from "node:fs/promises";
import { resolve } from "node:path";
import { realpath, stat } from "node:fs/promises";
import { relative, resolve, sep } from "node:path";
import { fileURLToPath } from "node:url";
import {
developmentTestPlan,
parseDevelopmentTestRequest,
} from "./lib/development-tests.mjs";
import { projectForTest } from "./lib/test-projects.mjs";
let child;
let forceTimer;
@@ -29,9 +30,22 @@ for (const signal of Object.keys(signalExitCodes))
try {
const [mode, ...arguments_] = process.argv.slice(2);
const request = parseDevelopmentTestRequest(mode, arguments_);
const resolvedPaths = [];
const repository = await realpath(".");
for (const path of request.paths) {
if (!(await stat(path)).isFile())
throw new Error(`Test path is not a file: ${path}`);
// Ownership follows the filesystem's canonical spelling. Execution keeps
// the caller path so exact discovery still rejects excluded symlink aliases.
const resolvedPath = relative(repository, await realpath(path))
.split(sep)
.join("/");
if (
request.mode !== "focused" &&
projectForTest(resolvedPath).group !== "source"
)
throw new Error(`Use test:focused for built test projects: ${path}`);
resolvedPaths.push(resolvedPath);
}
let baseCommit;
if (request.mode === "changed") {
@@ -42,7 +56,7 @@ try {
);
baseCommit = base.output.trim();
}
const plan = developmentTestPlan(request, baseCommit);
const plan = developmentTestPlan(request, baseCommit, resolvedPaths);
if (request.dryRun) {
console.log(JSON.stringify({ ...request, baseCommit, ...plan }, null, 2));
} else {
+44 -30
View File
@@ -1,22 +1,23 @@
#!/usr/bin/env node
import assert from "node:assert/strict";
import { isUtf8 } from "node:buffer";
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { lstat, mkdir, readFile, writeFile } from "node:fs/promises";
import { isAbsolute, join } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { parseEvidence } from "../dist/domain/evidence.js";
import { goBinarySchema } from "../dist/domain/go/goBinary.js";
import { PrivateRuntimeRoot } from "../dist/process/PrivateRuntimeRoot.js";
import { AnalysisCleanupLease } from "../dist/artifacts/AnalysisCleanupLease.js";
import { artifactMcpCall } from "./lib/artifact-e2e.mjs";
import { requireMcpOutput } from "./lib/mcp-verifier-results.mjs";
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
import { createVerifierCommands } from "./lib/verify-commands.mjs";
import { createOwnedMcpStdioTransport } from "./lib/owned-mcp-stdio.mjs";
import { closeVerifierResources } from "./lib/verifier-cleanup.mjs";
const execute = promisify(execFile);
const commands = await createVerifierCommands();
const compiler = process.env.REA_VERIFY_GO_COMMAND ?? "go";
if (process.env.REA_VERIFY_GO_COMMAND !== undefined && !isAbsolute(compiler))
throw new Error(
@@ -51,13 +52,16 @@ const goEnvironment = {
// Go's own verifier seam isolates telemetry; no global configuration is changed.
TEST_TELEMETRY_DIR: join(root.path, "telemetry"),
};
const client = new Client({ name: "go-binary-verifier", version: "1" });
const transport = new StdioClientTransport({
command: process.execPath,
args: [entrypoint, "mcp"],
env: environment,
stderr: "pipe",
});
let client;
let transport;
const cleanup = new AnalysisCleanupLease(async () => {
await closeVerifierResources([
() => client?.close(),
() => transport?.close(),
() => commands.close(),
]);
await root.close();
}, [root.path]);
const targets = [
["linux", "amd64", "elf", 64, "little"],
["linux", "386", "elf", 32, "little"],
@@ -73,10 +77,19 @@ const verified = [];
const omittedCoverage = [];
const failures = [];
try {
client = new Client({ name: "go-binary-verifier", version: "1" });
transport = await createOwnedMcpStdioTransport({
command: process.execPath,
arguments: [entrypoint, "mcp"],
hostEnvironment: environment,
cwd: process.cwd(),
expectedCommand: null,
sweepTokenOwnedProcesses: true,
});
await prepareFixture();
try {
compilerVersion = (
await execute(compiler, ["version"], {
await commands.exec(compiler, ["version"], {
cwd: root.path,
env: goEnvironment,
timeout: 10_000,
@@ -185,7 +198,7 @@ try {
const path = join(root.path, "linux-amd64-byte-strings");
const expected = JSON.parse(
(
await execute(oracle, ["--build-byte-fixture", compiler, path], {
await commands.exec(oracle, ["--build-byte-fixture", compiler, path], {
cwd: sourceRoot,
env: { ...goEnvironment, GOOS: "linux", GOARCH: "amd64" },
timeout: 120_000,
@@ -198,21 +211,16 @@ try {
} catch (cause) {
failures.push(cause);
} finally {
for (const close of [
() => client.close(),
() => transport.close(),
() => root.close(),
]) {
try {
await close();
} catch (cause) {
failures.push(cause);
}
try {
await cleanup.release();
} catch (cause) {
failures.push(cause);
}
}
const verifier = await completeVerifierRun(run);
try {
await assert.rejects(lstat(root.path), { code: "ENOENT" });
if (cleanup.released)
await assert.rejects(lstat(root.path), { code: "ENOENT" });
if (verifier.process_lineage.status === "verified")
assert.deepEqual(verifier.process_lineage.descendants, []);
} catch (cause) {
@@ -226,15 +234,21 @@ if (failures.length > 0) {
compiler: compilerVersion,
verified,
omitted_coverage: omittedCoverage,
retained_root: cleanup.released ? null : root.path,
verifier,
},
null,
2,
),
);
throw new AggregateError(
failures,
"Real Go metadata verification failed; cleanup failures are retained.",
throw Object.assign(
new AggregateError(
failures,
cleanup.released
? "Real Go metadata verification failed; cleanup failures are retained."
: `Real Go metadata verification failed; cleanup is incomplete at ${root.path}.`,
),
{ cleanupLeases: cleanup.released ? [] : [cleanup] },
);
}
console.log(
@@ -256,7 +270,7 @@ console.log(
);
async function go(args, overrides = {}) {
return execute(compiler, args, {
return commands.exec(compiler, args, {
cwd: sourceRoot,
env: { ...goEnvironment, ...overrides },
timeout: 120_000,
@@ -356,7 +370,7 @@ async function inspect(mode, path, includeStringLiterals = false) {
...(includeStringLiterals ? ["--include-string-literals"] : []),
"--json",
];
const { stdout } = await execute(process.execPath, args, {
const { stdout } = await commands.exec(process.execPath, args, {
env: environment,
timeout: 30_000,
maxBuffer: 16 * 1024 * 1024,
@@ -424,7 +438,7 @@ async function verify(
byteExpected ??
JSON.parse(
(
await execute(oracle, [oraclePath], {
await commands.exec(oracle, [oraclePath], {
timeout: 10_000,
maxBuffer: 1024 * 1024,
})
-2
View File
@@ -1,4 +1,3 @@
import { deepStrictEqual } from "node:assert";
import {
lstat,
mkdir,
@@ -13,7 +12,6 @@ import { dirname, isAbsolute, join } from "node:path";
import { parse as parseToml } from "smol-toml";
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
import { PRODUCT_IDENTITY } from "../dist/identity.js";
import { pathExists } from "./lib/verify-package-core.mjs";
import { parse as parseJsonc } from "jsonc-parser";
import { skillReferenceIssues } from "./lib/docs-facts.mjs";
+3 -5
View File
@@ -99,8 +99,7 @@ async function verifyPublishedUpdate({
environment,
);
await commands.exec(
"npm",
await commands.execNpm(
[
"install",
"--global",
@@ -118,7 +117,7 @@ async function verifyPublishedUpdate({
);
const npmRoot = (
await commands.exec("npm", ["root", "--global"], {
await commands.execNpm(["root", "--global"], {
cwd: canaryRoot,
env: environment,
timeout: 30000,
@@ -186,8 +185,7 @@ if (result.status === "failed") process.exitCode = 1;
}
async function findPreviousPublishedVersion(targetVersion, cwd, environment) {
const { stdout } = await commands.exec(
"npm",
const { stdout } = await commands.execNpm(
["view", "rea-agents", "versions", "--json"],
{ cwd, env: environment, timeout: 30000, maxBuffer: 16 * 1024 * 1024 },
);
-4
View File
@@ -1,7 +1,6 @@
#!/usr/bin/env node
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { readFile, mkdtemp, mkdir, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -13,7 +12,6 @@ import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
import { ArtifactResourceScope } from "../dist/artifacts/ArtifactResourceScope.js";
import { readGhidraLanguageCatalog } from "../dist/ghidra/GhidraLanguageCatalog.js";
import { parseEvidence } from "../dist/domain/evidence.js";
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
import { functionDossierSchema } from "../dist/domain/native/functionDossier.js";
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
import { buildGameCubeDolFixture } from "../tests/conformance/ghidra/dol-fixture.mjs";
@@ -96,8 +94,6 @@ async function languageInstalled(languageId) {
}
}
const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
/** One MCP session plus CLI check. `languageOverride` is set only for the Gekko pass. */
async function runPass({ fixture, languageOverride, label, layout }) {
const env = { ...baseEnv };
+2 -7
View File
@@ -151,7 +151,7 @@ export async function verifyAndroidFailureRecovery({
failure_code: "runtime_unavailable",
},
availabilityReason: "provider_unavailable",
availabilityRemediation: /selected Java runtime/u,
availabilityRemediation: /selected Java launcher/u,
message: /selected Java runtime/u,
remediation: /selected launcher/u,
});
@@ -194,12 +194,7 @@ export async function verifyAndroidFailureRecovery({
repository,
available: false,
unavailableReason: scenario.availabilityReason ?? "provider_missing",
remediation:
scenario.availabilityRemediation ??
new RegExp(
scenario.observation.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"),
"u",
),
remediation: scenario.availabilityRemediation ?? scenario.remediation,
});
console.log(`PASS CLI applicable recovery: ${scenario.label}`);
}
+40 -11
View File
@@ -94,29 +94,36 @@ const stringBindings = (sourceFile, symbols, unresolved) => {
ts.forEachChild(node, collect);
};
collect(sourceFile);
const potential = (node) => {
const potential = (node, matchesCandidate) => {
const expression = unwrapExpression(node);
if (exactLimit(literalText(expression)) !== undefined) return true;
if (ts.isIdentifier(expression)) return candidates.has(expression.text);
if (ts.isIdentifier(expression)) return matchesCandidate(expression);
if (ts.isPropertyAccessExpression(expression))
return (
exactLimit(expression.name.text) !== undefined ||
potential(expression.expression)
potential(expression.expression, matchesCandidate)
);
if (ts.isElementAccessExpression(expression))
return (
expression.argumentExpression !== undefined &&
potential(expression.argumentExpression)
potential(expression.argumentExpression, matchesCandidate)
);
if (ts.isObjectLiteralExpression(expression))
return expression.properties.some(
(property) =>
ts.isPropertyAssignment(property) && potential(property.initializer),
ts.isPropertyAssignment(property) &&
potential(property.initializer, matchesCandidate),
);
if (ts.isConditionalExpression(expression))
return potential(expression.whenTrue) || potential(expression.whenFalse);
return (
potential(expression.whenTrue, matchesCandidate) ||
potential(expression.whenFalse, matchesCandidate)
);
if (ts.isBinaryExpression(expression))
return potential(expression.left) || potential(expression.right);
return (
potential(expression.left, matchesCandidate) ||
potential(expression.right, matchesCandidate)
);
return false;
};
let changed = true;
@@ -125,13 +132,34 @@ const stringBindings = (sourceFile, symbols, unresolved) => {
for (const declaration of declarations) {
if (
!candidates.has(declaration.name.text) &&
potential(declaration.initializer)
potential(declaration.initializer, (identifier) =>
candidates.has(identifier.text),
)
) {
candidates.add(declaration.name.text);
changed = true;
}
}
}
// Names narrow the search cheaply, but only the lexical declaration can
// establish that a particular use is an alias of a limit.
const lexicalCandidates = new Set();
changed = true;
while (changed) {
changed = false;
for (const declaration of declarations) {
if (
candidates.has(declaration.name.text) &&
!lexicalCandidates.has(declaration) &&
potential(declaration.initializer, (identifier) =>
lexicalCandidates.has(symbols(identifier)?.valueDeclaration),
)
) {
lexicalCandidates.add(declaration);
changed = true;
}
}
}
const unknown = (node, reason) => {
const path = sourceFile.fileName;
const offset = node.getStart();
@@ -152,11 +180,12 @@ const stringBindings = (sourceFile, symbols, unresolved) => {
declaration === undefined ||
!ts.isVariableDeclaration(declaration) ||
!ts.isVariableDeclarationList(declaration.parent) ||
declaration.initializer === undefined
declaration.initializer === undefined ||
!lexicalCandidates.has(declaration)
)
return undefined;
if ((declaration.parent.flags & ts.NodeFlags.Const) === 0) {
unknown(identifier, "mutable setting-key or value alias");
unknown(declaration.name, "mutable setting-key or value alias");
return undefined;
}
if (declaration.pos >= identifier.pos) {
@@ -890,7 +919,7 @@ if (direct) {
const entries = parseNumericLimitRegistry(registry);
const listed = new Map();
for (const entry of entries) {
const { name, policy, read, reason } = entry;
const { name, read, reason } = entry;
if (listed.has(name)) failures.push(`${name} is listed twice`);
listed.set(name, entry);
if (reason.trim().length < 40)
+31 -6
View File
@@ -20,6 +20,7 @@ it.each([
source:
'process.stdout.write("partial-雪"); process.stderr.write("waiting\\n"); setInterval(() => {}, 1000)',
code: "provider_timeout",
timeoutMs: 500,
stdout: "partial-雪",
stderr: "waiting\n",
},
@@ -28,6 +29,7 @@ it.each([
source:
'process.stdout.write("partial-雪"); process.stderr.write("adb: error: device offline\\n"); process.exitCode = 1',
code: "unsupported_target",
expectedPath: "caller-device",
stdout: "partial-雪",
stderr: "adb: error: device offline\n",
},
@@ -36,6 +38,7 @@ it.each([
source:
'process.stdout.write("partial-雪"); process.stderr.write("adb: error: cannot stat \'/host/token file\': Permission denied\\n"); process.exitCode = 1',
code: "unsupported_target",
expectedPath: "/host/token file",
stdout: "partial-雪",
stderr: "adb: error: cannot stat '/host/token file': Permission denied\n",
},
@@ -54,7 +57,7 @@ it.each([
},
["-e", testCase.source],
{
timeoutMs: testCase.name === "timeout" ? 500 : 5_000,
timeoutMs: testCase.timeoutMs ?? 5_000,
maxOutputBytes: testCase.maxOutputBytes ?? 1024,
},
undefined,
@@ -75,12 +78,34 @@ it.each([
},
});
if (testCase.code === "unsupported_target")
expect(projected.details?.path).toBe(
testCase.name === "local path refusal"
? "/host/token file"
: "caller-device",
);
expect(projected.details?.path).toBe(testCase.expectedPath);
if (testCase.code === "provider_timeout") {
expect(projected.details?.timeout_ms).toBe(testCase.timeoutMs);
expect(projected.details?.limit).toBeUndefined();
}
expect(JSON.stringify(projected)).not.toContain(
"ambient-value-must-not-be-persisted",
);
});
it.each([true, false])(
"projects a truncated adb capture as retryable only for volatile output (%s)",
async (volatileOutput) => {
const result = await runAdbCapture(
volatileOutput ? "read_adb_logcat" : "install_adb_package",
{ device: "device" },
{ binary: process.execPath, pathSource: "environment", environment: {} },
["-e", 'process.stdout.write("X".repeat(4096))'],
{ timeoutMs: 5_000, maxOutputBytes: 1024, volatileOutput },
undefined,
);
if (result.ok) throw new Error("Expected a capture allowance refusal");
const projected = projectAnalysisError(result.error);
expect(projected.retryable).toBe(volatileOutput);
expect(projected).toMatchObject({
code: "resource_constraint",
details: { reported_limits: { max_output_bytes: 1024 } },
});
expect(projected.details?.limit).toBeUndefined();
},
);
-60
View File
@@ -1,5 +1,4 @@
import { expect, it, vi } from "vitest";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import {
ExecFileOutputCancelledError,
execFileBytes,
@@ -212,65 +211,6 @@ it("does not infer cancellation from a foreign ABORT_ERR code", async () => {
});
});
it.each([true, false])(
"projects a truncated adb capture as retryable only for volatile output (%s)",
async (volatileOutput) => {
vi.mocked(execFileBytes).mockRejectedValueOnce(
Object.assign(new Error("too much"), {
code: 1,
stdout: "",
stderr: "",
signal: null,
killed: false,
outputTruncated: true,
}),
);
const result = await runAdbCapture(
volatileOutput ? "read_adb_logcat" : "install_adb_package",
{ device: "device" },
{ binary: "/host/adb", pathSource: "environment", environment: {} },
volatileOutput ? ["logcat", "-d"] : ["install", "app.apk"],
{ timeoutMs: 100, maxOutputBytes: 1024, volatileOutput },
undefined,
);
if (result.ok) throw new Error("Expected a capture allowance refusal");
const projected = projectAnalysisError(result.error);
expect(projected.retryable).toBe(volatileOutput);
expect(projected).toMatchObject({
code: "resource_constraint",
details: { reported_limits: { max_output_bytes: 1024 } },
});
expect(projected.details?.limit).toBeUndefined();
},
);
it("reports the effective fixed adb deadline", async () => {
vi.mocked(execFileBytes).mockRejectedValueOnce(
Object.assign(new Error("deadline"), {
code: "ETIMEDOUT",
stdout: "",
stderr: "",
signal: null,
killed: true,
}),
);
const result = await runAdbCapture(
"pull_adb_file",
{ device: "device" },
{ binary: "/host/adb", pathSource: "environment", environment: {} },
["pull", "/remote"],
{ timeoutMs: 600_000, maxOutputBytes: 1024 },
undefined,
);
if (result.ok) throw new Error("Expected an adb deadline");
const projected = projectAnalysisError(result.error);
expect(projected).toMatchObject({
code: "provider_timeout",
details: { timeout_ms: 600_000 },
});
expect(projected.details?.limit).toBeUndefined();
});
it("keeps a physical failure even when the later abort reason is that same error", async () => {
const controller = new AbortController();
const failure = new Error("physical command failure");
+1 -1
View File
@@ -191,7 +191,7 @@ fi`,
await Promise.allSettled([inspection, closing]);
await provider.close();
}
}, 15_000);
});
it("fails closed when adb exits successfully without a recognized version", async () => {
const stdout = `unexpected adb output ${"x".repeat(700)}\n`;
+5 -7
View File
@@ -84,6 +84,7 @@ import {
import { RegularFileOperationCancelled } from "../filesystem/RegularFile.js";
import { OwnedFileHandle } from "../filesystem/OwnedFileHandle.js";
import { createEvidence } from "../domain/evidence.js";
import { createAnalysisExecutionEvidence } from "../domain/analysisObservation.js";
import {
PrivateRuntimeRoot,
type PrivateRuntimeRootOptions,
@@ -993,15 +994,12 @@ export class AdbProvider {
const partialObservation =
execution === undefined
? undefined
: createEvidence(execution.subject ?? undefined, execution.provider, {
: createAnalysisExecutionEvidence(
operation,
parameters,
result: execution.result,
rawResult: execution.rawResult,
limitations: execution.limitations,
locations: execution.locations,
confidence: "observed",
});
execution,
"observed",
);
return new ProviderCleanupError(
ADB_PROVIDER_IDENTITY.id,
resources,
+48 -32
View File
@@ -47,19 +47,32 @@ it("reports the fixed adb version-probe deadline", async () => {
});
it("reports the fixed adb version-output cap", async () => {
vi.mocked(execFileOutput).mockRejectedValueOnce(
Object.assign(new Error("too much"), {
code: "ERR_CHILD_PROCESS_STDIO_MAXBUFFER",
stdout: "",
stderr: "",
signal: null,
killed: false,
outputTruncated: true,
}),
);
const failure = await inspectAdbClient(selection).catch(
(cause: unknown) => cause,
);
let selected: string = selection.binary;
if (process.platform === "win32") {
vi.mocked(execFileOutput).mockRejectedValueOnce(
Object.assign(new Error("too much"), {
code: "ERR_CHILD_PROCESS_STDIO_MAXBUFFER",
stdout: "",
stderr: "",
signal: null,
killed: false,
outputTruncated: true,
}),
);
} else {
const root = await mkdtemp(join(tmpdir(), "rea-adb-version-cap-"));
onTestFinished(() => rm(root, { recursive: true, force: true }));
selected = join(root, "adb");
await writeFile(
selected,
`#!${process.execPath}\nprocess.stdout.write("x".repeat(100_000));\n`,
{ mode: 0o755 },
);
}
const failure = await inspectAdbClient({
...selection,
binary: selected,
}).catch((cause: unknown) => cause);
const projected = projectAnalysisError(
failure as Parameters<typeof projectAnalysisError>[0],
);
@@ -74,25 +87,28 @@ it("reports the fixed adb version-output cap", async () => {
expect(projected.details?.limit).toBeUndefined();
});
it("distinguishes execute denial from missing or unreadable version output", async () => {
vi.mocked(execFileOutput).mockRejectedValueOnce(
Object.assign(new Error("denied"), {
code: "EACCES",
spawnFailed: true,
stdout: "",
stderr: "",
signal: null,
killed: false,
}),
);
await expect(inspectAdbClient(selection)).rejects.toMatchObject({
code: "binary_not_executable",
diagnostics: {
selected_binary: selection.binary,
filesystem_error: "EACCES",
},
});
});
it.skipIf(process.platform !== "win32")(
"classifies an EACCES execute refusal on Windows",
async () => {
vi.mocked(execFileOutput).mockRejectedValueOnce(
Object.assign(new Error("denied"), {
code: "EACCES",
spawnFailed: true,
stdout: "",
stderr: "",
signal: null,
killed: false,
}),
);
await expect(inspectAdbClient(selection)).rejects.toMatchObject({
code: "binary_not_executable",
diagnostics: {
selected_binary: selection.binary,
filesystem_error: "EACCES",
},
});
},
);
it("names an EPERM execute refusal separately from missing permission", async () => {
vi.mocked(execFileOutput).mockRejectedValueOnce(
+12 -16
View File
@@ -58,7 +58,7 @@ export const androidSnapshotCopy = (
: undefined;
/** Adopt a snapshot descriptor whose close rejected, keyed by its exact path. */
export type AndroidSnapshotCloseRetention = (
type AndroidSnapshotCloseRetention = (
path: string,
owner: OwnedFileHandle,
) => void;
@@ -69,8 +69,8 @@ export const snapshotAndroidTarget = async (
sha256: string,
root: string,
operation: AndroidOperation,
signal?: AbortSignal,
retainFailedClose?: AndroidSnapshotCloseRetention,
signal: AbortSignal | undefined,
retainFailedClose: AndroidSnapshotCloseRetention,
): Promise<string> => {
const snapshot = join(root, "target.apk");
try {
@@ -105,8 +105,8 @@ export const snapshotAndroidEngine = async (
sha256: string,
root: string,
operation: AndroidOperation,
signal?: AbortSignal,
retainFailedClose?: AndroidSnapshotCloseRetention,
signal: AbortSignal | undefined,
retainFailedClose: AndroidSnapshotCloseRetention,
): Promise<{ path: string; sha256: string }> => {
const path = join(root, "engine.jar");
try {
@@ -159,17 +159,14 @@ const copyAndroidSnapshot = async (
snapshotPath: string,
expectedSha256: string,
signal: AbortSignal | undefined,
retainFailedClose: AndroidSnapshotCloseRetention | undefined,
retainFailedClose: AndroidSnapshotCloseRetention,
): Promise<AndroidSnapshotCopy> => {
assertRegularFileActive(signal);
let removeSnapshot = false;
const retain = (path: string) =>
retainFailedClose === undefined
? {}
: {
retainFailedClose: (owner: OwnedFileHandle) =>
retainFailedClose(path, owner),
};
const retain = (path: string) => ({
retainFailedClose: (owner: OwnedFileHandle) =>
retainFailedClose(path, owner),
});
try {
// Close each descriptor through its owner. A rejected close keeps the
// copy outcome and stays retained until that owner is retried; the
@@ -287,7 +284,7 @@ const openSnapshotSource = async (
path: string,
signal: AbortSignal | undefined,
retention: {
readonly retainFailedClose?: (owner: OwnedFileHandle) => void;
readonly retainFailedClose: (owner: OwnedFileHandle) => void;
},
): Promise<StableRegularFileDescriptor> => {
try {
@@ -305,10 +302,9 @@ const openSnapshotSource = async (
};
const retainAdmissionOwner = (
retain: ((owner: OwnedFileHandle) => void) | undefined,
retain: (owner: OwnedFileHandle) => void,
owner: OwnedFileHandle,
): RegularFileCleanupFailure["retention"] => {
if (retain === undefined) return { kind: "not_requested" };
try {
retain(owner);
return { kind: "callback_returned" };
+4 -4
View File
@@ -313,10 +313,10 @@ export class JadxProvider implements AndroidAnalysisPort {
: AbortSignal.any([signal, this.#shutdown.signal]),
});
this.#inspections.add(pending);
void pending.then(
() => this.#inspections.delete(pending),
() => this.#inspections.delete(pending),
);
const remove = (): void => {
this.#inspections.delete(pending);
};
void pending.then(remove, remove);
return pending;
}
+3 -3
View File
@@ -415,11 +415,11 @@ export class ApkSignatureProvider implements ApkSignaturePort {
const { v1, decoded: v1Decoded, duplicates } = v1Read.value;
const v2 = decodeScheme(block.value, APK_BLOCK_ID.signatureV2, "v2");
if (!v2.ok) return err(v2.error);
if (!v2.ok) return v2;
const v3 = decodeScheme(block.value, APK_BLOCK_ID.signatureV3, "v3");
if (!v3.ok) return err(v3.error);
if (!v3.ok) return v3;
const v31 = decodeScheme(block.value, APK_BLOCK_ID.signatureV31, "v3.1");
if (!v31.ok) return err(v31.error);
if (!v31.ok) return v31;
// Account for representation expansion before allocating the report.
const projected = projectedOutputBytes(
+16 -8
View File
@@ -1,5 +1,6 @@
import * as commandOwner from "../process/OwnedCommand.js";
import { failedInspectionDescriptor } from "../../tests/fixtures/processInspectionCleanup.js";
import { awaitAcknowledgement } from "../../tests/fixtures/lifecycleAcknowledgement.js";
import {
AnalysisError,
primaryAnalysisError,
@@ -486,6 +487,7 @@ it("cancels a pending filesystem lookup and owns its eventual rejection", async
const pendingStat = new Promise((_resolve, reject) => {
rejectLookup = reject;
});
void pendingStat.catch(() => undefined);
const observed = new Promise<void>((resolve) => {
started = resolve;
});
@@ -502,14 +504,20 @@ it("cancels a pending filesystem lookup and owns its eventual rejection", async
"win32",
controller.signal,
);
await observed;
controller.abort();
await expect(pending).rejects.toMatchObject({
_tag: "AnalysisCancelledError",
});
rejectLookup(new Error("filesystem completed after cancellation"));
await Promise.resolve();
expect(lookup.stat).toHaveBeenCalledTimes(1);
try {
await awaitAcknowledgement(observed, pending, "apktool filesystem lookup");
controller.abort();
await expect(pending).rejects.toMatchObject({
_tag: "AnalysisCancelledError",
});
rejectLookup(new Error("filesystem completed after cancellation"));
await Promise.resolve();
expect(lookup.stat).toHaveBeenCalledTimes(1);
} finally {
controller.abort();
rejectLookup(new Error("fixture cleanup"));
await pending.catch(() => undefined);
}
});
it("bounds the entire pending PATH lookup by the selection deadline", async () => {
@@ -58,7 +58,6 @@ it.skipIf(process.platform === "win32")(
await fixture.cleanup([probe]);
}
},
30_000,
);
let commands = new ApktoolCommandCleanup();
+23 -4
View File
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, onTestFinished } from "vitest";
import { createDeferred } from "../../tests/fixtures/binarySession.js";
import { awaitAcknowledgement } from "../../tests/fixtures/lifecycleAcknowledgement.js";
import {
getNavigationContext,
@@ -500,7 +501,16 @@ it("waits for late facet cleanup after cancellation and retains its owner", asyn
settled = true;
return result;
});
await started.promise;
onTestFinished(async () => {
controller.abort();
late.resolve(err(failure));
try {
await pending;
} finally {
await lease.release();
}
});
await awaitAcknowledgement(started.promise, pending, "facet execution");
controller.abort();
await Promise.resolve();
expect(settled).toBe(false);
@@ -530,7 +540,12 @@ it("reports cancellation after a late successful facet that ignored its signal",
{ address: "0x401000", document: "fixture" },
controller.signal,
);
await started.promise;
onTestFinished(async () => {
controller.abort();
late.resolve(ok(createAnalysisExecution("finished", provider)));
await pending;
});
await awaitAcknowledgement(started.promise, pending, "facet execution");
controller.abort();
late.resolve(ok(createAnalysisExecution("finished", provider)));
const result = await pending;
@@ -586,7 +601,11 @@ it("preserves a provider's cancellation for the same signal", async () => {
{ address: "0x401000", document: "fixture" },
controller.signal,
);
await started.promise;
onTestFinished(async () => {
controller.abort(reason);
await pending;
});
await awaitAcknowledgement(started.promise, pending, "provider execution");
controller.abort(reason);
const result = await pending;
if (result.ok) throw new Error("expected cancellation");
+5
View File
@@ -53,6 +53,11 @@ export interface ExecutionOptions {
* supplied it.
*/
readonly selectorOrigin?: "provider";
/**
* Whether the caller's catalog advertises an operation; refusal recovery
* names only advertised operations. Absent means every operation is.
*/
readonly isOperationAdvertised?: (operation: AnalysisOperation) => boolean;
}
/** Closed operations accepted by provider-backed analysis sessions. */
-9
View File
@@ -6,7 +6,6 @@ import {
resolveWindowsChangeTimeSettle,
} from "../config/windowsChangeTimeSettle.js";
import { pwntoolsInputBytes } from "../native/pwntools/PwntoolsInputLimit.js";
import { resolveBridgeMaxRequestBytes } from "../config/bridgeMaxRequestBytes.js";
import { resolveCaptureDecoderHeap } from "../config/captureDecoderHeap.js";
import { resolveRegexMatchingDeadline } from "../config/regexMatchingDeadline.js";
import { resolveRegexWorkerHeapRequest } from "../config/regexWorkerHeapRequest.js";
@@ -366,7 +365,6 @@ it("reads an accepted override before an invalid launch seed", () => {
{
revision: 1,
overrides: {
"bridge.max_request_bytes": 8192,
"capture.decoder_old_generation_mib": 64,
"regex.matching_deadline_ms": 250,
"regex.worker_heap_request_mib": 128,
@@ -375,9 +373,6 @@ it("reads an accepted override before an invalid launch seed", () => {
},
},
() => ({
bridge: resolveBridgeMaxRequestBytes({
REA_BRIDGE_MAX_REQUEST_BYTES: "nope",
}),
capture: resolveCaptureDecoderHeap({
REA_CAPTURE_DECODER_OLD_GENERATION_MIB: "0",
}),
@@ -393,10 +388,6 @@ it("reads an accepted override before an invalid launch seed", () => {
}),
}),
);
expect(resolved.bridge).toMatchObject({
maximumBytes: 8192,
source: "runtime",
});
expect(resolved.capture).toMatchObject({
oldGenerationMiB: 64,
source: "runtime",
@@ -4,7 +4,7 @@ import { join } from "node:path";
import { expect, it } from "vitest";
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
import { resolveAppBundleExecutable } from "./AppBundleExecutable.js";
import { resolveAppleBundleExecutable } from "./AppBundleExecutable.js";
it.each([
["no comment", ""],
@@ -22,7 +22,7 @@ it.each([
await writeFile(infoPlist, source);
await writeFile(executable, "fixture - never executed");
expect(await resolveAppBundleExecutable(bundle)).toEqual({
expect(await resolveAppleBundleExecutable(bundle)).toEqual({
ok: true,
value: { executable, infoPlist },
});
+9 -13
View File
@@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest";
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
import { binaryPlistObjectTable } from "../../tests/fixtures/binaryPlistObjectTable.js";
import { resolveAppBundleExecutable } from "./AppBundleExecutable.js";
import { resolveAppleBundleExecutable } from "./AppBundleExecutable.js";
const binaryPlistExecutableWithThreeByteUid = (): Buffer => {
const ascii = (value: string): Buffer => {
@@ -53,7 +53,7 @@ it.each([
);
await writeFile(executable, "inert fixture; never executed");
const result = await resolveAppBundleExecutable(bundle);
const result = await resolveAppleBundleExecutable(bundle);
expect(result).toEqual({
ok: true,
value: { executable, infoPlist: plistPath },
@@ -71,7 +71,7 @@ describe("app bundle Info.plist magic", () => {
await writeFile(plistPath, binaryPlistExecutableWithThreeByteUid());
await writeFile(executable, "inert fixture; never executed");
expect(await resolveAppBundleExecutable(bundle)).toEqual({
expect(await resolveAppleBundleExecutable(bundle)).toEqual({
ok: true,
value: { executable, infoPlist: plistPath },
});
@@ -86,17 +86,13 @@ describe("app bundle Info.plist magic", () => {
const plist = Buffer.from("bplist00");
plist[0] = (plist[0] ?? 0) | 0x80;
await writeFile(plistPath, plist);
let decoded = false;
const result = await resolveAppBundleExecutable(bundle, {
readFile: async () => plist,
realpath: async (path) => path,
decodeBinaryPlist: async () => {
decoded = true;
return "Fixture";
const result = await resolveAppleBundleExecutable(bundle);
expect(result).toMatchObject({
ok: false,
error: {
cause: { name: "XmlPlistTextDecodeError", reason: "invalid-bytes" },
},
});
expect(decoded).toBe(false);
expect(result.ok).toBe(false);
} finally {
await rm(root, { recursive: true, force: true });
}
@@ -118,7 +114,7 @@ describe("app bundle program directory containment", () => {
await writeFile(join(bundle, "Contents", "Info.plist"), plist);
let childResolved = false;
const result = await resolveAppBundleExecutable(bundle, {
const result = await resolveAppleBundleExecutable(bundle, {
readFile: async () => Buffer.from(plist),
realpath: async (path) => {
if (path === bundle) return canonicalBundle;
+4 -22
View File
@@ -88,10 +88,9 @@ export interface ResolvedAppBundleResourceRoot {
}
/** Filesystem operations used by the resolver, injectable for boundary tests. */
export interface AppBundleFileSystem {
interface AppBundleFileSystem {
readonly readFile: (path: string) => Promise<Buffer>;
readonly realpath: (path: string) => Promise<string>;
readonly decodeBinaryPlist?: (path: string) => Promise<string>;
}
const defaultFileSystem: AppBundleFileSystem = { readFile, realpath };
@@ -102,7 +101,7 @@ const defaultFileSystem: AppBundleFileSystem = { readFile, realpath };
* a wrapper whose `Wrapper` directory holds the iOS bundle; that bundle is
* found by listing `Wrapper`, never by following the `WrappedBundle` link.
*/
export const resolveAppBundleExecutable = async (
const resolveAppBundleExecutable = async (
path: string,
fileSystem: AppBundleFileSystem = defaultFileSystem,
): Promise<Result<ResolvedAppBundle, BinaryTargetError>> => {
@@ -396,15 +395,6 @@ const codeDescription = (cause: unknown): string => {
: "";
};
const isSystemErrorCode = (cause: unknown): boolean => {
const code = errorCode(cause);
return (
typeof code === "string" &&
/^[A-Z][A-Z0-9_]+$/u.test(code) &&
!code.startsWith("ERR_")
);
};
const isPermissionDenied = (cause: unknown): boolean =>
errorCode(cause) === "EACCES" || errorCode(cause) === "EPERM";
@@ -461,20 +451,12 @@ const resolveLayoutExecutable = async (
let name: string;
try {
name =
hasBinaryPlistSignature(plist) &&
fileSystem.decodeBinaryPlist !== undefined
? await fileSystem.decodeBinaryPlist(plistPath)
: executableName(decodeInfoPlistValue(plist));
name = executableName(decodeInfoPlistValue(plist));
} catch (cause: unknown) {
return err(
new BinaryTargetError(
plistPath,
isPermissionDenied(cause)
? `permission denied decoding ${noun} Info.plist ${plistPath}${codeDescription(cause)}`
: isSystemErrorCode(cause)
? `could not decode ${noun} Info.plist ${plistPath}${codeDescription(cause)}`
: `${noun} Info.plist is malformed, unsupported, or lacks CFBundleExecutable: ${plistPath}${cause instanceof Error ? ` (${cause.message})` : ""}`,
`${noun} Info.plist is malformed, unsupported, or lacks CFBundleExecutable: ${plistPath}${cause instanceof Error ? ` (${cause.message})` : ""}`,
{
cause,
// A bundle without code, such as a resource bundle, declares no
@@ -3,7 +3,7 @@ import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { resolveAppBundleExecutable } from "./AppBundleExecutable.js";
import { resolveAppleBundleExecutable } from "./AppBundleExecutable.js";
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
describe("OpenStep Info.plist nesting during app target resolution", () => {
@@ -20,7 +20,7 @@ describe("OpenStep Info.plist nesting during app target resolution", () => {
`{ CFBundleExecutable = App; payload = ${"(".repeat(depth)}x${")".repeat(depth)}; }`,
);
const result = await resolveAppBundleExecutable(app);
const result = await resolveAppleBundleExecutable(app);
if (depth === 127) {
expect(result).toMatchObject({
@@ -52,7 +52,7 @@ describe("OpenStep Info.plist nesting during app target resolution", () => {
`{ CFBundleExecutable = App; }${"(".repeat(3000)}x${")".repeat(3000)}`,
);
expect(await resolveAppBundleExecutable(app)).toMatchObject({
expect(await resolveAppleBundleExecutable(app)).toMatchObject({
ok: true,
value: { executable: join(programs, "App") },
});
+3 -3
View File
@@ -208,7 +208,7 @@ export const parseBinaryTarget = async (
throwIfTargetResolutionCancelled(signal);
const resolved = await resolveAppBundle(canonical);
throwIfTargetResolutionCancelled(signal);
if (!resolved.ok) return err(resolved.error);
if (!resolved.ok) return resolved;
const { executable: path, infoPlist } = resolved.value;
const bundle =
infoPlist === undefined ? {} : { bundleInfoPlist: infoPlist };
@@ -747,8 +747,8 @@ const throwIfTargetResolutionCancelled = (signal?: AbortSignal): void => {
const detectArtifactFormat = async (
path: string,
source: StableRegularFileDescriptor,
signal?: AbortSignal,
sourcePath = path,
signal: AbortSignal | undefined,
sourcePath: string,
): Promise<
| Exclude<
BinaryTarget["format"],
+30 -3
View File
@@ -56,6 +56,7 @@ import {
type AnalysisCleanupRetry,
} from "../domain/analysisErrorRecovery.js";
import { isBundleResourceOperation } from "./binary/BundleResourceSession.js";
import { offeredOperationAlternatives } from "./OperationAlternatives.js";
const FRIDA_OPERATION_NAMES: ReadonlySet<string> = new Set(
FRIDA_TOOL_CONTRACTS.map(({ name }) => name),
@@ -126,6 +127,8 @@ export type AvailabilityPolicy = {
type AvailabilityFacts = {
readonly defaultModeAvailable?: boolean;
readonly modes?: readonly ToolAvailabilityMode[];
/** Advertised operations the current provider offers for part of the question. */
readonly alternatives?: readonly AnalysisOperation[];
};
type Availability = AvailabilityFacts &
(
@@ -162,6 +165,7 @@ type AvailabilityContext = {
readonly targetFormat: string | undefined;
readonly descriptors: ReadonlyMap<string, ProviderDescriptor>;
readonly policy: AvailabilityPolicy;
readonly isToolAdvertised: (name: string) => boolean;
};
type EnhancedContractName = Extract<
@@ -322,10 +326,15 @@ const ADDRESS_CONTEXT_EFFECT_COMPOSITION: ModeComposition = {
],
};
/** Build stable per-operation availability for discovery and tool visibility. */
/**
* Build stable per-operation availability for the caller's advertised tools.
* Each tool's availability still derives from the complete provider facts;
* advertisement narrows only the reported tools and the alternatives named.
*/
export const buildCapabilityInventory = (
sessionStatus: JsonValue,
policy: AvailabilityPolicy,
isToolAdvertised: (name: string) => boolean = () => true,
): readonly ToolAvailability[] => {
const status = statusSchema.parse(sessionStatus);
const descriptors = new Map<string, ProviderDescriptor>(
@@ -345,6 +354,7 @@ export const buildCapabilityInventory = (
targetFormat: status.format,
descriptors,
policy,
isToolAdvertised,
});
const effects = composedEffects(
contract.name,
@@ -398,8 +408,13 @@ export const buildCapabilityInventory = (
available: false,
reason: availability.reason,
remediation: availability.remediation,
...(availability.alternatives === undefined
? {}
: { alternatives: [...availability.alternatives] }),
};
}).sort((left, right) => compareUnicodeCodePoints(left.name, right.name));
})
.filter(({ name }) => isToolAdvertised(name))
.sort((left, right) => compareUnicodeCodePoints(left.name, right.name));
};
type ClientRequirements = ToolAvailability["client_requirements"];
@@ -445,6 +460,9 @@ export function compactToolAvailability(
? {}
: { default_mode_available: entry.default_mode_available }),
...(entry.modes === undefined ? {} : { modes: [...entry.modes] }),
...(entry.available || entry.alternatives === undefined
? {}
: { alternatives: [...entry.alternatives] }),
...(JSON.stringify(entry.client_requirements) ===
JSON.stringify(clientRequirements)
? {}
@@ -959,6 +977,7 @@ const providerAvailability = ({
kind,
name,
policy,
isToolAdvertised,
}: AvailabilityContext): Availability => {
if (kind === "enhanced") return composedAvailability(name, descriptors);
const launchFailure =
@@ -976,12 +995,20 @@ const providerAvailability = ({
kind === "native-provider")
)
return { reason: "available", remediation: null };
if (descriptor === undefined)
if (descriptor === undefined) {
const offered = offeredOperationAlternatives(
name,
(alternative) =>
descriptors.get(alternative)?.available === true &&
isToolAdvertised(alternative),
);
return {
reason: "provider_missing",
remediation:
"Install or configure a provider that declares this operation.",
...(offered === undefined ? {} : { alternatives: offered.operations }),
};
}
return descriptor.available
? { reason: "available", remediation: null }
: {
+6 -1
View File
@@ -93,7 +93,12 @@ it("joins concurrent shutdowns and seals ownership before asynchronous cleanup s
try {
await awaitAcknowledgement(entered, first, "capability cleanup");
second = scope.close();
expect(second).toBe(first);
let secondSettled = false;
void second.then(() => {
secondSettled = true;
});
await Promise.resolve();
expect(secondSettled).toBe(false);
expect(close).toHaveBeenCalledTimes(1);
expect(() => scope.own("later-owner", {}, async () => undefined)).toThrow(
"after host shutdown",
@@ -1,15 +1,14 @@
import { CLAUDE_DESKTOP_PACKAGE_FAMILY } from "../../tests/fixtures/claudeDesktop.js";
import { basename, dirname, join, sep } from "node:path";
import { describe, expect, it } from "vitest";
import {
CLAUDE_DESKTOP_PACKAGE_FAMILY,
resolveWindowsClaudeDesktopLocation,
type ClaudeDesktopLocationFileSystem,
} from "./ClaudeDesktopConfigLocation.js";
import {
packagePrivateLauncherBlocker,
packagePrivateStorageFamily,
redirectedWriteBlocker,
type MsixPackageView,
windowsAppDataRoots,
@@ -642,15 +641,19 @@ describe("package-private REA launchers", () => {
"rea.mjs",
);
it("names the package that privately stores a path", () => {
expect(packagePrivateStorageFamily(stored, packages)).toBe(
CLAUDE_DESKTOP_PACKAGE_FAMILY,
);
expect(packagePrivateStorageFamily(launcher, packages)).toBeUndefined();
it("blocks private cache paths without treating other package paths as private", () => {
const identity = (path: string) => path;
expect(
packagePrivateStorageFamily(
join(packages, CLAUDE_DESKTOP_PACKAGE_FAMILY, "LocalState", "x"),
packagePrivateLauncherBlocker([stored], packages, identity),
).toContain(CLAUDE_DESKTOP_PACKAGE_FAMILY);
expect(
packagePrivateLauncherBlocker([launcher], packages, identity),
).toBeUndefined();
expect(
packagePrivateLauncherBlocker(
[join(packages, CLAUDE_DESKTOP_PACKAGE_FAMILY, "LocalState", "x")],
packages,
identity,
),
).toBeUndefined();
});
@@ -29,7 +29,7 @@ const PROBES_PER_DIRECTORY = 2;
* publisher ID Windows derives from `CN="Anthropic, PBC", O="Anthropic, PBC",
* L=San Francisco, S=California, C=US, …`.
*/
export const CLAUDE_DESKTOP_PACKAGE_FAMILY = "Claude_pzs8sxrjxfjjc";
const CLAUDE_DESKTOP_PACKAGE_FAMILY = "Claude_pzs8sxrjxfjjc";
/** Observed Claude package names plus Windows' 13-character publisher ID. */
const CLAUDE_PACKAGE_FAMILY =
+3 -3
View File
@@ -23,14 +23,14 @@ export interface ClientConfigurationSource extends ClientConfigurationDocument {
const invalidEnvironment = (
path: string,
error: ConfigurationError,
overrides: Readonly<Record<string, string>> = {},
platform: NodeJS.Platform = process.platform,
overrides: Readonly<Record<string, string>> | undefined,
platform: NodeJS.Platform,
): ClientConfigurationFileError => {
const constraints = error.settings
.map(({ setting, constraint }) => `${setting}: ${constraint}`)
.join("; ");
const overrideNames = new Set(
Object.keys(overrides).map((name) =>
Object.keys(overrides ?? {}).map((name) =>
platform === "win32" ? name.toUpperCase() : name,
),
);
@@ -1,15 +1,9 @@
import { validateRegistrationEnvironment } from "./RegistrationEnvironmentValidation.js";
import { clientRegistrationSource } from "./ClientRegistrationSource.js";
import type { SetupClient } from "./SupportedClients.js";
import {
inspectAnalysisSettingSeeds,
launchAnalysisSetting,
} from "../config/analysisSettings.js";
import {
parseEnvironment,
parseEnvironmentValues,
type Environment,
} from "../config/environment.js";
import { launchAnalysisSetting } from "../config/analysisSettings.js";
import type { Environment } from "../config/environment.js";
import {
DEFAULT_GHIDRA_STARTUP_TIMEOUT_MS,
DEFAULT_HOPPER_STARTUP_TIMEOUT_MS,
@@ -314,34 +308,24 @@ export const clientRegistrationEnvironmentDetailsWithBudget = async (
const validatedDetails = (
environment: Readonly<Record<string, string>>,
validation: "fragment" | "effective",
platform: NodeJS.Platform = process.platform,
expressionSettings: ReadonlySet<string> = new Set(),
platform: NodeJS.Platform,
expressionSettings: ReadonlySet<string>,
): Result<ClientRegistrationEnvironmentDetails, ConfigurationError> => {
const runtimeEnvironment = snapshotEnvironment(environment, platform);
const unresolved = new Set(
Object.keys(
snapshotEnvironment(
Object.fromEntries([...expressionSettings].map((name) => [name, ""])),
platform,
),
),
const validated = validateRegistrationEnvironment(
environment,
expressionSettings,
validation,
platform,
);
const validated =
validation === "fragment"
? parseEnvironmentValues(runtimeEnvironment, unresolved)
: parseEnvironment(runtimeEnvironment, unresolved);
if (!validated.ok) return validated;
const seedIssues = inspectAnalysisSettingSeeds(
runtimeEnvironment,
unresolved,
);
if (seedIssues.length > 0)
return err(
new ConfigurationError("Invalid REA environment configuration", {
settings: seedIssues,
}),
);
return ok(environmentDetails(environment, validated.value, unresolved));
return validated.ok
? ok(
environmentDetails(
environment,
validated.value.configuration,
validated.value.unresolvedSettings,
),
)
: validated;
};
const admittedTimeout = (
@@ -397,7 +381,7 @@ const registrationTimeoutInputs = (
const environmentDetails = (
environment: Readonly<Record<string, string>>,
configuration: Environment,
unresolvedSettings: ReadonlySet<string> = new Set(),
unresolvedSettings: ReadonlySet<string>,
): ClientRegistrationEnvironmentDetails => ({
values: Object.fromEntries(
Object.entries(environment).sort(([left], [right]) =>
+1 -1
View File
@@ -649,7 +649,7 @@ const packageVersionRemediation = (
const unavailableStatus = (
client: SetupClient,
state: "missing" | "invalid",
configPath: string = client.configPath,
configPath: string,
): ClientRegistrationStatus => ({
client: client.name,
config_path: configPath,
+8 -11
View File
@@ -67,10 +67,7 @@ import type { AnalysisProfileCommitment } from "../domain/analysisProfile.js";
import { profilelessNativeDispatchProfile } from "./binary/ProfilelessNativeWorkflowSnapshot.js";
import { err, ok, type Result } from "../domain/result.js";
import type { AnalysisSnapshot } from "../domain/analysisSnapshot.js";
import type {
AnalysisExecution,
ProviderIdentity,
} from "./AnalysisProvider.js";
import type { AnalysisExecution } from "./AnalysisProvider.js";
import { createAnalysisExecutionEvidence } from "../domain/analysisObservation.js";
import { REA_WORKFLOW_PROVIDER } from "../domain/investigationProviders.js";
import { workflowAnalysisProfile } from "./WorkflowAnalysisProfile.js";
@@ -296,7 +293,7 @@ export const runProviderAnalysis = async (
delivery.detail,
operationSignal,
);
return result.ok ? ok(result.value) : err(cliError(result.error, path));
return result.ok ? result : err(cliError(result.error, path));
}
const admitted = admitOneShotOperation(tool, arguments_, operationSignal);
if (!admitted.ok) return err(cliError(admitted.error, path));
@@ -445,14 +442,14 @@ const runAnalysis = async (
let isDirectory = false;
if (BUNDLE_RESOURCE_OPERATIONS.has(tool)) {
const inspected = await inspectBundleResourceInput(path);
if (!inspected.ok) return err(inspected.error);
if (!inspected.ok) return inspected;
isDirectory = inspected.value;
}
if (BUNDLE_RESOURCE_OPERATIONS.has(tool) && isDirectory) {
const opened = await session.openBundleResources(path, { signal });
if (!opened.ok) return err(opened.error);
if (!opened.ok) return opened;
const execution = await session.execute(tool, arguments_, { signal });
if (!execution.ok) return err(execution.error);
if (!execution.ok) return execution;
return ok(
withEvidenceCallerInputPath(
createAnalysisExecutionEvidence(
@@ -472,7 +469,7 @@ const runAnalysis = async (
signal,
...targetSelection(options),
});
if (!prepared.ok) return err(prepared.error);
if (!prepared.ok) return prepared;
const { snapshot } = prepared.value;
let resolvedTarget: ResolvedSessionOpen | undefined;
if (snapshot !== undefined && prepared.value.target !== undefined) {
@@ -484,7 +481,7 @@ const runAnalysis = async (
? {}
: { providerId: options.providerId }),
});
if (!preview.ok) return err(preview.error);
if (!preview.ok) return preview;
resolvedTarget = preview.value;
const route = preview.value.route;
const bindingProfile =
@@ -548,7 +545,7 @@ const runAnalysis = async (
resolvedTarget === undefined
? await session.open(path, openOptions)
: await session.openResolvedTarget(resolvedTarget, openOptions);
if (!opened.ok) return err(opened.error);
if (!opened.ok) return opened;
const evidenceProfile = analysisProfileForEvidence(session, tool);
const executed = await executeAnalysisTool({
session,
+11 -38
View File
@@ -1,18 +1,11 @@
import { inspectAnalysisSettingSeeds } from "../config/analysisSettings.js";
import { validateRegistrationEnvironment } from "./RegistrationEnvironmentValidation.js";
import { isMap, isSeq, type Node, type YAMLMap } from "yaml";
import {
parseEnvironment,
parseEnvironmentValues,
type Environment,
} from "../config/environment.js";
import type { Environment } from "../config/environment.js";
import { ConfigurationError } from "../domain/configurationErrors.js";
import { err, ok, type Result } from "../domain/result.js";
import type { ClientConfigurationDocument } from "./ClientConfigurationDocument.js";
import { CordisExpression, DshCordisYaml } from "./DshCordisYaml.js";
import {
mergeEnvironmentOverrides,
snapshotEnvironment,
} from "../process/snapshotEnvironment.js";
import { mergeEnvironmentOverrides } from "../process/snapshotEnvironment.js";
export const DSH_ENTRY_ID = "rea-mcp";
export const DSH_CLIENT_PACKAGE = "@deepseek-ai/dsh-mcp-client";
@@ -255,35 +248,15 @@ export class DshCordisDocument {
);
literals[key] = value;
}
const runtimeEnvironment = snapshotEnvironment(literals, platform);
const unresolved = new Set(
Object.keys(
snapshotEnvironment(
Object.fromEntries([...unresolvedSettings].map((key) => [key, ""])),
platform,
),
),
const validated = validateRegistrationEnvironment(
literals,
unresolvedSettings,
validation,
platform,
);
const validated =
validation === "fragment"
? parseEnvironmentValues(runtimeEnvironment, unresolved)
: parseEnvironment(runtimeEnvironment, unresolved);
if (!validated.ok) return validated;
const seedIssues = inspectAnalysisSettingSeeds(
runtimeEnvironment,
unresolved,
);
if (seedIssues.length > 0)
return err(
new ConfigurationError("Invalid REA environment configuration", {
settings: seedIssues,
}),
);
return ok({
values: literals,
configuration: validated.value,
unresolvedSettings: unresolved,
});
return validated.ok
? ok({ values: literals, ...validated.value })
: validated;
}
desired(
+1 -3
View File
@@ -307,9 +307,7 @@ const projectWrite = (
bundle: EvidenceBundle,
written: Result<PublishedEvidenceFile, AnalysisError>,
): Result<JsonValue, AnalysisError> =>
written.ok
? ok(evidenceBundleExportReceipt(bundle, written.value))
: err(written.error);
written.ok ? ok(evidenceBundleExportReceipt(bundle, written.value)) : written;
/** Standalone bundle discovery shares the same read owner as import and comparison. */
export const inspectEvidenceBundleCommand = async (
+6 -11
View File
@@ -31,16 +31,11 @@ import {
import { readJsonFile, writeTextParts } from "./JsonFiles.js";
import type { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
type EvidenceReadFailure =
type EvidenceBundleFileFailure =
| EvidenceFileError
| EvidenceIntegrityError
| AnalysisResourceConstraintError
| AnalysisCancelledError;
type EvidenceWriteFailure =
| EvidenceFileError
| EvidenceIntegrityError
| AnalysisCancelledError
| AnalysisResourceConstraintError;
/** Project a published bundle with record and residual-unknown revision counts. */
export const evidenceBundleExportReceipt = (
@@ -59,7 +54,7 @@ export const readEvidenceBundle = (
signal?: AbortSignal,
resources?: ArtifactResourceScope,
operation = "import_evidence_bundle",
): Promise<Result<EvidenceBundle, EvidenceReadFailure>> =>
): Promise<Result<EvidenceBundle, EvidenceBundleFileFailure>> =>
readBundle(path, false, signal, resources, operation);
/** Read an authenticated immutable snapshot for a subsequent session import. */
@@ -68,7 +63,7 @@ export const readImmutableEvidenceBundle = (
signal?: AbortSignal,
resources?: ArtifactResourceScope,
operation = "import_evidence_bundle",
): Promise<Result<EvidenceBundle, EvidenceReadFailure>> =>
): Promise<Result<EvidenceBundle, EvidenceBundleFileFailure>> =>
readBundle(path, true, signal, resources, operation);
/** Join both comparison reads and preserve cleanup owned by either failed input. */
@@ -109,7 +104,7 @@ const readBundle = async (
signal?: AbortSignal,
resources?: ArtifactResourceScope,
operation = "import_evidence_bundle",
): Promise<Result<EvidenceBundle, EvidenceReadFailure>> => {
): Promise<Result<EvidenceBundle, EvidenceBundleFileFailure>> => {
if (signal?.aborted)
return err(
new AnalysisCancelledError(operation, {
@@ -135,7 +130,7 @@ export const authenticateEvidenceBundleValue = (
value: unknown,
path: string,
options: { readonly operation: string; readonly immutable?: boolean },
): Result<EvidenceBundle, EvidenceReadFailure> => {
): Result<EvidenceBundle, EvidenceBundleFileFailure> => {
const { operation } = options;
const headroom = requireAuthenticationHeadroom(value, {
operation,
@@ -226,7 +221,7 @@ export const writeEvidenceBundle = async (
): Promise<
Result<
{ readonly path: string; readonly bytes: number },
EvidenceWriteFailure
EvidenceBundleFileFailure
>
> => {
if (signal?.aborted === true)
+24 -16
View File
@@ -2,6 +2,7 @@ import { realpath, stat } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { stripComments } from "jsonc-parser";
import { z } from "zod";
import { jsonRecordSchema } from "../domain/jsonRecord.js";
import { err, ok, type Result } from "../domain/result.js";
import { PRODUCT_IDENTITY } from "../identity.js";
import type { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
@@ -34,8 +35,7 @@ const folderTrustSchema = z
.optional(),
})
.passthrough();
const trustRulesSchema = z.record(
z.string(),
const trustRulesSchema = jsonRecordSchema(
z.enum(["TRUST_FOLDER", "TRUST_PARENT", "DO_NOT_TRUST"]),
);
const sourceFailure = (
@@ -47,18 +47,18 @@ const sourceFailure = (
detail: `Gemini settings in ${path} are malformed: ${cause instanceof Error ? cause.message : String(cause)}`,
});
// Gemini resolves symlinks when possible and compares macOS/Windows paths without case.
const normalizedPath = async (
path: string,
platform: NodeJS.Platform,
): Promise<string> => {
const physical = await realpath(path).catch(() => path);
const absolute = resolve(physical).replaceAll("\\", "/");
// Gemini normalizes trust-file keys before replacement and rule precedence.
const normalizedPath = (path: string, platform: NodeJS.Platform): string => {
const absolute = resolve(path).replaceAll("\\", "/");
return platform === "win32" || platform === "darwin"
? absolute.toLowerCase()
: absolute;
};
// Applicability additionally resolves symlinks, without changing rule identity.
const physicalPath = async (path: string, platform: NodeJS.Platform) =>
normalizedPath(await realpath(path).catch(() => path), platform);
const workspaceTrusted = async (
settings: NonNullable<SetupClient["geminiSettings"]>,
sources: readonly SettingsSource[],
@@ -78,21 +78,29 @@ const workspaceTrusted = async (
);
if (!text.ok) return text;
if (text.value === undefined) return ok(false);
let rules: z.output<typeof trustRulesSchema>;
let rules: ReadonlyMap<string, z.output<typeof trustRulesSchema>[string]>;
try {
// Gemini strips comments, but rejects trailing commas, BOMs and invalid trust levels.
rules = trustRulesSchema.parse(JSON.parse(stripComments(text.value)));
const parsed = trustRulesSchema.parse(
JSON.parse(stripComments(text.value)),
);
rules = new Map(
Object.entries(parsed).map(([path, level]) => [
normalizedPath(path, settings.platform),
level,
]),
);
} catch (cause: unknown) {
return err(sourceFailure(settings.trustedFoldersPath, cause));
}
const workspace = await normalizedPath(
const workspace = await physicalPath(
settings.workspaceDirectory,
settings.platform,
);
let longest = -1;
let trusted = false;
for (const [path, level] of Object.entries(rules)) {
const effective = await normalizedPath(
for (const [path, level] of rules) {
const effective = await physicalPath(
level === "TRUST_PARENT" ? dirname(path) : path,
settings.platform,
);
@@ -138,11 +146,11 @@ export const readClientPolicyBlock = async (
if (parsed.value !== undefined)
sources.push({ path, document: parsed.value });
}
const workspace = await normalizedPath(
const workspace = await physicalPath(
settings.workspaceDirectory,
settings.platform,
);
const home = await normalizedPath(
const home = await physicalPath(
dirname(dirname(client.configPath)),
settings.platform,
);
@@ -4,7 +4,6 @@ import { parseDocument } from "yaml";
import { ConfigurationError } from "../domain/configurationErrors.js";
import {
editGooseConfigurationYaml,
GooseConfigurationYamlError,
parseGooseConfigurationYaml,
stringifyGooseConfigurationYaml,
} from "./GooseConfigurationYaml.js";
@@ -111,11 +110,15 @@ describe("Goose YAML 1.2 source facts", () => {
} catch (cause: unknown) {
failure = cause;
}
expect(failure).toBeInstanceOf(GooseConfigurationYamlError);
expect(failure).toBeInstanceOf(ConfigurationError);
expect(failure).toMatchObject({ kind: "shadowed_member", path });
expect(failure).toHaveProperty(
"message",
`Goose YAML contains a duplicate mapping member at ${path.join(".")}`,
);
expect(() =>
editGooseConfigurationYaml(source, ["extensions", "rea"], extension),
).toThrow(GooseConfigurationYamlError);
).toThrow(`duplicate mapping member at ${path.join(".")}`);
},
);
+1 -1
View File
@@ -143,7 +143,7 @@ const gooseYamlOptions = {
} as const;
/** A duplicate YAML member cannot establish one selected configuration value. */
export class GooseConfigurationYamlError extends ConfigurationError {
class GooseConfigurationYamlError extends ConfigurationError {
readonly kind = "shadowed_member";
constructor(
readonly path: readonly string[],
-1
View File
@@ -1,7 +1,6 @@
import { expect, it } from "vitest";
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import { RegularFileCleanupFailure } from "../filesystem/OwnedRegularFile.js";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import { MANAGED_SKILL_FILES } from "./SetupSkill.js";
import { searchGuidanceOutputSchema } from "../contracts/guidanceContracts.js";
import { RetainedRegularFileCleanupFailure } from "./RegularFileRead.js";
+2 -2
View File
@@ -49,13 +49,13 @@ export type HopperRequestFailure = HopperDownloadFailure & {
export const HOPPER_DOWNLOAD_BYTES = 100_000_000;
/** Longest wait for the response or its next bytes before a download stalls. */
export const HOPPER_DOWNLOAD_STALL_MS = 30_000;
const HOPPER_DOWNLOAD_STALL_MS = 30_000;
/**
* Overall bound for one download. A slow connection that keeps delivering
* bytes is bounded here rather than by a deadline sized for fast networks.
*/
export const HOPPER_DOWNLOAD_TIMEOUT_MS = 15 * 60_000;
const HOPPER_DOWNLOAD_TIMEOUT_MS = 15 * 60_000;
/** Small release feed and key requests must not use the package bounds. */
export const HOPPER_METADATA_TIMEOUT_MS = 10_000;
+19 -46
View File
@@ -4,9 +4,8 @@ import { join } from "node:path";
import { describe, expect, it, vi } from "vitest";
import {
formatCommandForDisplay,
installLinuxHopper,
linuxPackageManagerCommands,
linuxHopperInstallDisclosure,
linuxSharedLibrariesAvailable,
parseLinuxDistribution,
supportsReaHost,
@@ -31,19 +30,6 @@ import * as nativeCommands from "../process/ExecFileOutput.js";
import * as pythonRuntime from "./LinuxHopperPython.js";
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
describe("Linux package-manager command display", () => {
it("preserves argument boundaries for paths with spaces and quotes", () => {
expect(
formatCommandForDisplay("pkexec", [
"pacman",
"-U",
"/tmp/rea test/hopper.arch",
"apostrophe'path",
]),
).toBe('pkexec pacman -U "/tmp/rea test/hopper.arch" "apostrophe\'path"');
});
});
class RecordingLinuxHost implements LinuxHopperInstallHost {
distributionValue: LinuxDistribution | undefined = {
id: "ubuntu",
@@ -263,47 +249,34 @@ describe("Linux Hopper installation", () => {
] as const)(
"selects the %s native package manager and dependencies",
(family, executable, packages) => {
expect(
linuxPackageManagerCommands(family, "/tmp/Hopper package", true),
).toContainEqual(
expect.objectContaining({
executable,
args: expect.arrayContaining([...packages]),
}),
const root = linuxHopperInstallDisclosure(family, true).commands;
const nonRoot = linuxHopperInstallDisclosure(family, false).commands;
expect(root.some((command) => command.startsWith(`${executable} `))).toBe(
true,
);
for (const dependency of packages)
expect(
root.some((command) => command.split(" ").includes(dependency)),
).toBe(true);
expect(
linuxPackageManagerCommands(family, "/tmp/Hopper package", false),
).toContainEqual(
expect.objectContaining({
executable: "pkexec",
args: expect.arrayContaining([executable, "/tmp/Hopper package"]),
}),
);
nonRoot.some(
(command) =>
command.startsWith(`pkexec ${executable} `) &&
command.includes(`"<verified-hopper.${family}>"`),
),
).toBe(true);
},
);
it.each([true, false])(
"keeps installed apt dependencies at their versions while Hopper may upgrade (root: %s)",
(isRoot) => {
const prefix = isRoot ? [] : ["pkexec"];
const prefix = isRoot ? "" : "pkexec ";
expect(
linuxPackageManagerCommands("deb", "/tmp/Hopper package", isRoot).map(
({ executable, args }) => [executable, ...args],
),
linuxHopperInstallDisclosure("deb", isRoot).commands.slice(0, 2),
).toEqual([
[
...prefix,
"apt-get",
"install",
"-y",
"--no-upgrade",
"xvfb",
"xauth",
"python3",
"libx11-6",
"libxtst6",
],
[...prefix, "apt-get", "install", "-y", "/tmp/Hopper package"],
`${prefix}apt-get install -y --no-upgrade xvfb xauth python3 libx11-6 libxtst6`,
`${prefix}apt-get install -y "<verified-hopper.deb>"`,
]);
},
);
+3 -3
View File
@@ -511,7 +511,7 @@ export const installLinuxHopper = async (
};
/** Canonical launcher path for a legacy user-local Linux Hopper installation. */
export const linuxHopperLauncherPath = (home: string): string =>
const linuxHopperLauncherPath = (home: string): string =>
appendCallerPath(home, ".local/share/rea/hopper/bin/Hopper", "linux");
/** REA's managed Linux install path, preferred when no explicit launcher is set. */
@@ -796,7 +796,7 @@ const runPackageManagerCommand = async (
};
/** Preserve argument boundaries in a command shown to the user. */
export const formatCommandForDisplay = (
const formatCommandForDisplay = (
executable: string,
args: readonly string[],
): string =>
@@ -807,7 +807,7 @@ export const formatCommandForDisplay = (
.join(" ");
/** Select the native package manager and authorization boundary without shell evaluation. */
export const linuxPackageManagerCommands = (
const linuxPackageManagerCommands = (
family: LinuxPackageFamily,
archive: string,
isRoot: boolean,
@@ -6,16 +6,11 @@ import { err, ok } from "../domain/result.js";
import * as pythonRuntime from "./LinuxHopperPython.js";
import {
linuxHopperInstallDisclosure,
linuxPackageManagerCommands,
systemLinuxHopperInstallHost,
} from "./LinuxHopper.js";
/** Package transactions that install Hopper before any runtime library repair. */
const debCommands = linuxPackageManagerCommands(
"deb",
"/tmp/verified-hopper.deb",
false,
).length;
const debCommands = 2;
const missingPython = {
interpreter: "/usr/bin/python3",
@@ -41,6 +36,34 @@ const commandSuccess = {
};
describe("Linux Hopper package runtime", () => {
it("preserves package path boundaries in the reported failed command", async () => {
const directory = await createTestTempDirectory("rea-hopper-command-");
const host = systemLinuxHopperInstallHost(
{ XDG_CACHE_HOME: directory },
async () => undefined,
);
const archive = "/tmp/rea test/hopper's package.rpm";
const command = vi
.spyOn(boundedCommands, "execFileBoundedOutput")
.mockResolvedValue({
exitCode: 1,
stdout: "",
stderr: "fixture failure",
outputTruncated: false,
});
try {
const result = await host.installPackage("rpm", archive);
expect(command.mock.calls[0]?.[1]).toContain(archive);
expect(result).toMatchObject({
status: "failed",
failure: { command: expect.stringContaining(`"${archive}"`) },
});
} finally {
command.mockRestore();
await host.close?.();
}
});
it.each([
{
name: "installs and verifies the matching future Python library",
+7 -20
View File
@@ -11,7 +11,7 @@ import {
import { tmpdir } from "node:os";
import { join, posix, relative } from "node:path";
import { installMacHopperBundle } from "./MacHopper.js";
import { linuxHopperLauncherPath } from "./LinuxHopper.js";
import { linuxHopperLauncherCandidates } from "./LinuxHopper.js";
import { homeDirectoryFromEnvironment } from "../config/homeDirectory.js";
import { describe, expect, it } from "vitest";
@@ -19,34 +19,17 @@ import {
localMacHopperRenamedLauncherCandidates,
macHopperCanonicalLauncherCandidates,
macHopperUserApplicationPath,
macHopperRenamedLauncherCandidates,
readMacHopperRenamedLauncherCandidates,
} from "./MacHopperPaths.js";
describe("macOS Hopper launcher paths", () => {
const home = "/fixture/home";
it("keeps canonical system and user candidates ahead of renamed bundles", () => {
it("keeps canonical system and user candidates in order", () => {
expect(macHopperCanonicalLauncherCandidates(home)).toEqual([
"/Applications/Hopper Disassembler.app/Contents/MacOS/hopper",
"/fixture/home/Applications/Hopper Disassembler.app/Contents/MacOS/hopper",
]);
expect(
macHopperRenamedLauncherCandidates(
home,
new Map([
[
"/Applications",
["Hopper z.app", "Hopper a.app", "Hopper Disassembler.app"],
],
["/fixture/home/Applications", ["Hopper User.app"]],
]),
),
).toEqual([
"/Applications/Hopper a.app/Contents/MacOS/hopper",
"/Applications/Hopper z.app/Contents/MacOS/hopper",
"/fixture/home/Applications/Hopper User.app/Contents/MacOS/hopper",
]);
});
it.skipIf(process.platform === "win32")(
@@ -84,7 +67,7 @@ describe("macOS Hopper launcher paths", () => {
expect(macHopperCanonicalLauncherCandidates(home)[1]).toBe(
`${home}/Applications/Hopper Disassembler.app/Contents/MacOS/hopper`,
);
expect(linuxHopperLauncherPath(home)).toBe(
expect(linuxHopperLauncherCandidates(home)[1]).toBe(
`${home}/.local/share/rea/hopper/bin/Hopper`,
);
const source = join(temporary, "source.app");
@@ -154,13 +137,17 @@ describe("macOS Hopper launcher paths", () => {
recursive: true,
});
await mkdir(join(applications, "Hopper Disassembler.app"));
await mkdir(join(applications, "Hopper Alpha.app"));
await mkdir(join(applications, "Hopper Zeta.app"));
await writeFile(join(applications, "Hopper File.app"), "");
const runtime = [...localMacHopperRenamedLauncherCandidates(home)];
expect(runtime.filter((path) => path.startsWith(home))).toEqual([
posix.join(home, "Applications/Hopper Alpha.app/Contents/MacOS/hopper"),
posix.join(
home,
"Applications/Hopper Preview.app/Contents/MacOS/hopper",
),
posix.join(home, "Applications/Hopper Zeta.app/Contents/MacOS/hopper"),
]);
expect(await readMacHopperRenamedLauncherCandidates(home)).toEqual(
runtime,
+1 -1
View File
@@ -43,7 +43,7 @@ export const macHopperCanonicalLauncherCandidates = (
];
/** Build deterministic candidates from the discovered app bundle names. */
export const macHopperRenamedLauncherCandidates = (
const macHopperRenamedLauncherCandidates = (
homeDirectory: string,
bundleNamesByRoot: ReadonlyMap<string, readonly string[]>,
): readonly string[] =>
+17 -11
View File
@@ -12,6 +12,7 @@ import { join, sep } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
import { awaitAcknowledgement } from "../../tests/fixtures/lifecycleAcknowledgement.js";
import { ok } from "../domain/result.js";
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
let resources: ArtifactResourceScope;
@@ -612,17 +613,22 @@ describe("Registration cancellation and sibling outcomes", () => {
entered();
await blocked;
});
await admitted;
const controller = new AbortController();
const queued = register(path, launcher, controller.signal);
controller.abort("queued cancellation");
unblock();
await first;
expect(await queued).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
expect(await readFile(path, "utf8")).toBe("{}");
try {
await awaitAcknowledgement(admitted, first, "registration transaction");
const controller = new AbortController();
const queued = register(path, launcher, controller.signal);
controller.abort("queued cancellation");
unblock();
await first;
expect(await queued).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
expect(await readFile(path, "utf8")).toBe("{}");
} finally {
unblock();
await first;
}
});
it("keeps successful siblings after ordinary failures and marks work not started after cancellation", async () => {
+164 -133
View File
@@ -5,7 +5,28 @@ import { z } from "zod";
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
import { createTestProcessRegistry } from "../../tests/support/process/processFixture.js";
import { resolveMcpRegistrationClientId } from "./McpRegistrationCatalog.js";
import { resolveMcpRegistrationClients } from "./McpRegistrationCatalog.js";
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
const selectAgent = async (agent: string) => {
const root = await createTestTempDirectory("rea-mcp-alias-");
const resources = new ArtifactResourceScope();
try {
return await resolveMcpRegistrationClients(
{
agents: [agent],
global: true,
cwd: root,
home: root,
platform: process.platform,
environment: {},
},
resources,
);
} finally {
await resources.close();
}
};
it.each([
["cline-vscode", "cline"],
@@ -23,15 +44,22 @@ it.each([
["kiro", "kiro-cli"],
["mastra", "mastracode"],
["pi-agent", "pi"],
])("retains helper alias %s as %s", (alias, expected) => {
expect(resolveMcpRegistrationClientId(alias.toUpperCase())).toBe(expected);
])("resolves registration alias %s as %s", async (alias, expected) => {
const result = await selectAgent(alias.toUpperCase());
if (!result.ok) throw result.error;
expect(result.value.map(({ id }) => id)).toEqual([expected]);
});
it("does not treat prototype names or unknown strings as client identities", () => {
expect(resolveMcpRegistrationClientId("__proto__")).toBeUndefined();
expect(resolveMcpRegistrationClientId("constructor")).toBeUndefined();
expect(resolveMcpRegistrationClientId("unknown-client")).toBeUndefined();
});
it.each(["__proto__", "constructor", "unknown-client"])(
"rejects unknown registration identity %s",
async (agent) => {
const result = await selectAgent(agent);
expect(result).toMatchObject({
ok: false,
error: { _tag: "ConfigurationError" },
});
},
);
const clientSchema = z.object({
id: z.string(),
@@ -256,133 +284,136 @@ const REVIEWED_CLIENT_IDS = [
"amp",
];
it("resolves selected-environment paths and scopes without external catalog readers", async () => {
const home = await createTestTempDirectory("rea-registration-catalog-");
const processes = createTestProcessRegistry();
try {
await mkdir(join(home, "config"));
const child = await processes.run(
process.execPath,
["--import", "tsx", "--input-type=module", "--eval", CATALOG_PROGRAM],
{
timeoutMs: 50_000,
env: {
PATH: process.env.PATH,
SystemRoot: process.env.SystemRoot,
WINDIR: process.env.WINDIR,
TMPDIR: process.env.TMPDIR,
TMP: process.env.TMP,
TEMP: process.env.TEMP,
HOME: home,
USERPROFILE: home,
XDG_CONFIG_HOME: join(home, "config"),
XDG_DATA_HOME: join(home, "data"),
XDG_CACHE_HOME: join(home, "cache"),
XDG_STATE_HOME: join(home, "state"),
APPDATA: join(home, "appdata"),
LOCALAPPDATA: join(home, "localappdata"),
it(
"resolves selected-environment paths and scopes without external catalog readers",
{ tags: ["long-running"] },
async () => {
const home = await createTestTempDirectory("rea-registration-catalog-");
const processes = createTestProcessRegistry();
try {
await mkdir(join(home, "config"));
const child = await processes.run(
process.execPath,
["--import", "tsx", "--input-type=module", "--eval", CATALOG_PROGRAM],
{
env: {
PATH: process.env.PATH,
SystemRoot: process.env.SystemRoot,
WINDIR: process.env.WINDIR,
TMPDIR: process.env.TMPDIR,
TMP: process.env.TMP,
TEMP: process.env.TEMP,
HOME: home,
USERPROFILE: home,
XDG_CONFIG_HOME: join(home, "config"),
XDG_DATA_HOME: join(home, "data"),
XDG_CACHE_HOME: join(home, "cache"),
XDG_STATE_HOME: join(home, "state"),
APPDATA: join(home, "appdata"),
LOCALAPPDATA: join(home, "localappdata"),
},
},
},
);
expect(child.exitCode, child.stderr).toBe(0);
const result = catalogObservationsSchema.parse(JSON.parse(child.stdout));
assertKiloNativeLayers(result, home);
expect(result.empty.ok).toBe(true);
if (!result.empty.ok) throw new Error(result.empty.error.message);
expect(result.empty.value.map((client) => client.id)).toEqual(
REVIEWED_CLIENT_IDS,
);
expect(result.layeredKilo.ok).toBe(true);
if (!result.layeredKilo.ok)
throw new Error(result.layeredKilo.error.message);
const kiloLayers = [
"config.json",
"kilo.json",
"kilo.jsonc",
"opencode.json",
"opencode.jsonc",
].map((name) => join(home, "layered", "kilo", name));
expect(result.layeredKilo.value[0]).toMatchObject({
configPath: kiloLayers.at(-1),
configPaths: kiloLayers,
});
expect(result.layeredOpenCode.ok).toBe(true);
if (!result.layeredOpenCode.ok)
throw new Error(result.layeredOpenCode.error.message);
expect(result.layeredOpenCode.value[0]?.configPaths).toEqual(
["config.json", "opencode.json", "opencode.jsonc"].map((name) =>
join(home, "config", "opencode", name),
),
);
expect(result.explicit.ok).toBe(true);
if (!result.explicit.ok) throw new Error(result.explicit.error.message);
expect(result.explicit.value.map((client) => client.id)).toEqual([
"codex",
"amp",
"gemini-cli",
"fx",
"windsurf",
]);
expect(result.explicit.value[0]?.configPath).toBe(
join(home, "selected-codex", "config.toml"),
);
expect(
result.explicit.value.find((client) => client.id === "fx"),
).toMatchObject({ format: "fx", privateProfile: true });
expect(result.detected.ok).toBe(true);
if (!result.detected.ok) throw new Error(result.detected.error.message);
expect(result.detected.value.map((client) => client.id)).toEqual([
"claude-code",
"cursor",
"amp",
]);
expect(result.project.ok, JSON.stringify(result.project)).toBe(true);
if (!result.project.ok) throw new Error(result.project.error.message);
expect(result.project.value[0]?.format).toBe("copilot_cli_project");
expect(
result.project.value.map(({ configPath, scope }) => ({
configPath,
scope,
})),
).toEqual([
{
configPath: join(home, "project", ".github", "mcp.json"),
scope: "project",
},
...(process.platform === "win32"
? []
: [
{
configPath: join(home, "project", ".kilocode", "kilo.jsonc"),
scope: "project",
},
]),
{
configPath: join(home, "project", "opencode.jsonc"),
scope: "project",
},
]);
for (const refused of [
result.onlyGlobal,
result.defaultProject,
result.projectAmp,
result.unknown,
...(result.blockedCandidate === null ? [] : [result.blockedCandidate]),
result.unknownDetection,
]) {
expect(refused.ok).toBe(false);
if (refused.ok) throw new Error("Expected preflight refusal.");
expect(refused.error.tag).toBe("ConfigurationError");
);
expect(child.exitCode, child.stderr).toBe(0);
const result = catalogObservationsSchema.parse(JSON.parse(child.stdout));
assertKiloNativeLayers(result, home);
expect(result.empty.ok).toBe(true);
if (!result.empty.ok) throw new Error(result.empty.error.message);
expect(result.empty.value.map((client) => client.id)).toEqual(
REVIEWED_CLIENT_IDS,
);
expect(result.layeredKilo.ok).toBe(true);
if (!result.layeredKilo.ok)
throw new Error(result.layeredKilo.error.message);
const kiloLayers = [
"config.json",
"kilo.json",
"kilo.jsonc",
"opencode.json",
"opencode.jsonc",
].map((name) => join(home, "layered", "kilo", name));
expect(result.layeredKilo.value[0]).toMatchObject({
configPath: kiloLayers.at(-1),
configPaths: kiloLayers,
});
expect(result.layeredOpenCode.ok).toBe(true);
if (!result.layeredOpenCode.ok)
throw new Error(result.layeredOpenCode.error.message);
expect(result.layeredOpenCode.value[0]?.configPaths).toEqual(
["config.json", "opencode.json", "opencode.jsonc"].map((name) =>
join(home, "config", "opencode", name),
),
);
expect(result.explicit.ok).toBe(true);
if (!result.explicit.ok) throw new Error(result.explicit.error.message);
expect(result.explicit.value.map((client) => client.id)).toEqual([
"codex",
"amp",
"gemini-cli",
"fx",
"windsurf",
]);
expect(result.explicit.value[0]?.configPath).toBe(
join(home, "selected-codex", "config.toml"),
);
expect(
result.explicit.value.find((client) => client.id === "fx"),
).toMatchObject({ format: "fx", privateProfile: true });
expect(result.detected.ok).toBe(true);
if (!result.detected.ok) throw new Error(result.detected.error.message);
expect(result.detected.value.map((client) => client.id)).toEqual([
"claude-code",
"cursor",
"amp",
]);
expect(result.project.ok, JSON.stringify(result.project)).toBe(true);
if (!result.project.ok) throw new Error(result.project.error.message);
expect(result.project.value[0]?.format).toBe("copilot_cli_project");
expect(
result.project.value.map(({ configPath, scope }) => ({
configPath,
scope,
})),
).toEqual([
{
configPath: join(home, "project", ".github", "mcp.json"),
scope: "project",
},
...(process.platform === "win32"
? []
: [
{
configPath: join(home, "project", ".kilocode", "kilo.jsonc"),
scope: "project",
},
]),
{
configPath: join(home, "project", "opencode.jsonc"),
scope: "project",
},
]);
for (const refused of [
result.onlyGlobal,
result.defaultProject,
result.projectAmp,
result.unknown,
...(result.blockedCandidate === null ? [] : [result.blockedCandidate]),
result.unknownDetection,
]) {
expect(refused.ok).toBe(false);
if (refused.ok) throw new Error("Expected preflight refusal.");
expect(refused.error.tag).toBe("ConfigurationError");
}
expect(result.cancelled).toMatchObject({
ok: false,
error: { tag: "AnalysisCancelledError" },
});
} finally {
await processes.shutdown();
await rm(home, { recursive: true, force: true });
}
expect(result.cancelled).toMatchObject({
ok: false,
error: { tag: "AnalysisCancelledError" },
});
} finally {
await processes.shutdown();
await rm(home, { recursive: true, force: true });
}
}, 60_000);
},
);
const assertKiloNativeLayers = (
result: z.infer<typeof catalogObservationsSchema>,
+1 -1
View File
@@ -356,7 +356,7 @@ const ALIASES = {
const isClientId = (value: string): value is McpRegistrationClientId =>
Object.hasOwn(MCP_REGISTRATION_CLIENTS, value);
export const resolveMcpRegistrationClientId = (
const resolveMcpRegistrationClientId = (
input: string,
): McpRegistrationClientId | undefined => {
const lower = input.toLowerCase();
+4 -18
View File
@@ -1,3 +1,4 @@
import { readElfAttributeUleb } from "../domain/elfAttributeUleb.js";
import type { MipsGnuAttributes } from "../domain/binaryTargetTypes.js";
import { err, ok, type Result } from "../domain/result.js";
@@ -22,21 +23,6 @@ export const parseMipsGnuAttributes = (
const readU32 = (offset: number): number =>
littleEndian ? bytes.readUInt32LE(offset) : bytes.readUInt32BE(offset);
const readUleb = (
offset: number,
end: number,
): { readonly value: number; readonly next: number } | undefined => {
let value = 0;
for (let index = 0; index < 5 && offset + index < end; index += 1) {
const byte = bytes[offset + index]!;
value += (byte & 0x7f) * 2 ** (7 * index);
if ((byte & 0x80) === 0)
return value <= 0xffffffff
? { value, next: offset + index + 1 }
: undefined;
}
return undefined;
};
let fpAbi: number | null = null;
let msa: number | null = null;
@@ -65,7 +51,7 @@ export const parseMipsGnuAttributes = (
let cursor = vendorEnd + 1;
while (cursor < end) {
const scopeStart = cursor;
const scope = readUleb(cursor, end);
const scope = readElfAttributeUleb(bytes, cursor, end);
if (scope === undefined)
return err("malformed MIPS GNU attribute scope tag");
if (scope.value !== TAG_FILE)
@@ -85,7 +71,7 @@ export const parseMipsGnuAttributes = (
const scopeEnd = scopeStart + scopeLength;
cursor = scope.next + 4;
while (cursor < scopeEnd) {
const tag = readUleb(cursor, scopeEnd);
const tag = readElfAttributeUleb(bytes, cursor, scopeEnd);
if (tag === undefined) return err("malformed MIPS GNU attribute tag");
if (
tag.value !== TAG_GNU_MIPS_ABI_FP &&
@@ -94,7 +80,7 @@ export const parseMipsGnuAttributes = (
return err(
`MIPS ELF declares GNU attribute tag ${tag.value}, which REA does not inspect`,
);
const value = readUleb(tag.next, scopeEnd);
const value = readElfAttributeUleb(bytes, tag.next, scopeEnd);
if (value === undefined)
return err("malformed MIPS GNU attribute value");
if (tag.value === TAG_GNU_MIPS_ABI_FP) {
+1 -1
View File
@@ -54,7 +54,7 @@ const pathWithin = (path: string, root: string): boolean => {
* processes see those files at virtual AppData locations; other processes do
* not, and Windows removes them with the package.
*/
export const packagePrivateStorageFamily = (
const packagePrivateStorageFamily = (
canonicalPath: string,
packagesDirectory: string,
): string | undefined => {
+84
View File
@@ -0,0 +1,84 @@
import type { AnalysisOperation } from "./AnalysisProvider.js";
/** One operation that observes part of what a missing operation answers. */
interface OperationAlternative {
readonly operation: AnalysisOperation;
/** What it returns, as a noun phrase. */
readonly observes: string;
}
/**
* Follow-ups for one question shape that an undeclared operation would have
* answered. They are conditional rather than substitutes: `limitation`
* states what none of them establishes.
*/
interface OperationAlternativeRelation {
readonly question: string;
readonly alternatives: readonly OperationAlternative[];
readonly limitation: string;
}
const INSTRUCTION_OBSERVATIONS: OperationAlternativeRelation = {
question: "instruction-level questions",
alternatives: [
{
operation: "inspect_native_instruction",
observes:
"the decoded instruction or data at one exact address, with its length and static flow",
},
{
operation: "read_function_instructions",
observes: "one analyzed procedure's instruction listing in address order",
},
{
operation: "procedure_assembly",
observes: "one analyzed procedure's assembly text",
},
],
limitation:
"None navigates arbitrary analyzed objects such as data or addresses outside a procedure, and neither listing order nor address plus length proves executed control flow.",
};
/** Operations absent from this relation keep their consumer's generic advice. */
const OPERATION_ALTERNATIVES: ReadonlyMap<
string,
OperationAlternativeRelation
> = new Map<AnalysisOperation, OperationAlternativeRelation>([
["next_address", INSTRUCTION_OBSERVATIONS],
["prev_address", INSTRUCTION_OBSERVATIONS],
]);
/** Declared alternatives the caller can run now, in declaration order. */
export interface OfferedOperationAlternatives {
readonly operations: readonly AnalysisOperation[];
/** Each offer's scope, what none establishes, and the provider choice. */
readonly remediation: string;
}
/**
* Select the declared alternatives for an operation the current provider does
* not declare. `isOffered` holds only for operations that provider makes
* available and the caller advertises. Undefined means none qualifies.
*/
export const offeredOperationAlternatives = (
operation: string,
isOffered: (alternative: AnalysisOperation) => boolean,
): OfferedOperationAlternatives | undefined => {
const relation = OPERATION_ALTERNATIVES.get(operation);
if (relation === undefined) return undefined;
const offered = relation.alternatives.filter((alternative) =>
isOffered(alternative.operation),
);
if (offered.length === 0) return undefined;
const described = offered.map(
({ operation: alternative, observes }) => `${alternative} (${observes})`,
);
const listed =
described.length === 1
? described[0]
: `${described.slice(0, -1).join(", ")} and ${described.at(-1)}`;
return {
operations: offered.map((alternative) => alternative.operation),
remediation: `For ${relation.question}, the current provider offers ${listed}. ${relation.limitation} Running ${operation} itself requires a provider that declares it.`,
};
};
@@ -34,7 +34,7 @@ export interface ParsedReferenceSourceEntries {
}
/** Preserve the observed reader failure and add entry-specific recovery guidance. */
export const projectReferenceSourceEntryFailure = (
const projectReferenceSourceEntryFailure = (
entry: Extract<ReferenceSourceEntry, { status: "failed" }>,
): string => `${entry.message} ${entryRecoveryGuidance(entry)}`;
@@ -1,7 +1,6 @@
import { describe, expect, it } from "vitest";
import {
projectReferenceSourceImportError,
referenceSourceImportAnalysisError,
type ReferenceSourceImportError,
} from "./ReferenceSourceImportTypes.js";
@@ -20,11 +19,13 @@ describe("reference-source import error projection", () => {
parse: "execution_failure",
} as const;
for (const [code, category] of Object.entries(expectedCategories)) {
const projected = projectReferenceSourceImportError({
tag: "reference-source-import",
code: code as ReferenceSourceImportError["code"],
message: "EIO while reading /selected/source/src/main.ts",
});
const projected = projectAnalysisError(
referenceSourceImportAnalysisError({
tag: "reference-source-import",
code: code as ReferenceSourceImportError["code"],
message: "EIO while reading /selected/source/src/main.ts",
}),
);
expect(analysisErrorProjectionSchema.safeParse(projected).success).toBe(
true,
);
@@ -5,10 +5,6 @@ import {
AnalysisOutputError,
} from "../domain/analysisErrorCore.js";
import { analysisErrorWithCancellation } from "../domain/analysisErrorCancellation.js";
import {
projectAnalysisError,
type AnalysisErrorProjection,
} from "../domain/analysisErrorProjection.js";
import type {
AnalysisError,
AnalysisErrorOptions,
@@ -125,12 +121,6 @@ export const referenceSourceImportAnalysisError = (
);
};
/** Canonical CLI/MCP projection, retaining source facts under diagnostic details. */
export const projectReferenceSourceImportError = (
error: ReferenceSourceImportError,
): AnalysisErrorProjection =>
projectAnalysisError(referenceSourceImportAnalysisError(error));
/** Import a caller-selected local source tree as historical reference. */
export interface ReferenceSourceImportOptions {
readonly root: string;
Loaded 100 of 1450 files, more files were not shown because too many files have changed in this diff. Show more