Compare commits

..
Author SHA1 Message Date
Kaoru0822 5755b2863f Merge remote-tracking branch 'origin/main' into fix/nib-boolean-value-types
# Conflicts:
#	src/artifacts/apple/NibArchive.test.ts
2026-10-21 02:48:04 +00:00
Kaoru0822andClaude Opus 5.5 26b65b8008 fix(apple): decode NIBArchive boolean value types with the correct polarity
The NIBArchive decoder read value type 4 as true and 5 as false, so
decode-interface-builder reversed every serialized boolean in compiled nibs
(for example NSControlContinuous). Type 4 is false and 5 is true. The test
fixture encoder mirrored the same reversal, which hid it; correct it and pin
the raw type bytes in a decoder test.

Fixes #5541

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:55:42 +00:00
4114 changed files with 60673 additions and 626723 deletions

No files matched your search

+3 -4
View File
@@ -68,10 +68,9 @@ closing-issue reporters. Grouping preserves all those credits in one line.
When Highlights, detailed entries or migrations reference the same work,
put its thanks in one of those entries, not all of them. Include issue links.
Direct-commit bullets carry the commit link and known human credit.
Exclude GitHub bots and known agent accounts. REA's observed Anthropic, Cursor,
Codex, and Copilot co-author trailers resolve to `@claude`, `@cursoragent`,
`@codex`, and `@Copilot` with GitHub type `User`; the credit policy excludes
them while retaining their metadata.
Exclude GitHub bots and known agent accounts. REA's observed Anthropic, Cursor
and Codex co-author trailers resolve to `@claude`, `@cursoragent` and `@codex`
with GitHub type `User`; the credit policy excludes them while retaining their metadata.
At the REA maintainer's request, omit `@morluto` self-thanks while preserving
authorship in the inventory. Other human contributors remain eligible.
Use explicit GitHub links for cross-repository references to avoid ambiguous
+18 -136
View File
@@ -1,14 +1,14 @@
---
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. 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.
description: Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA. 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: "49"
version: "44"
---
# REA
Use REA when a claim depends on a shipped binary or package, decompilation,
requested runtime evidence, or comparison with
passive application 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.
@@ -32,15 +32,6 @@ 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. 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.
1. Diagnose without changing REA or client configuration. `npx` may download
the package and write to npm's cache. For Codex, run
@@ -71,8 +62,9 @@ before asking for a restart. Read that guide before a second repair attempt.
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 a version-pinned `rea-agents` package or continue through the CLI.
software to repair
MCP registration. For an unsupported client, use manual stdio registration
with a version-pinned `rea-agents` package or continue through the CLI.
A concrete CLI fallback for an operator-supplied JavaScript tree or ASAR is:
@@ -88,7 +80,8 @@ 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.
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.
and target-execution boundaries. REA setup never installs or upgrades unrelated
software, including Node.js, npm, Homebrew, Java, Ghidra, IDA, JADX, Kuna, Binwalk or Unblob.
## Route the target first
@@ -98,15 +91,9 @@ 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).
- Archive/package member inventory (ZIP/APK/IPA/MSIX/AppX or DMG):
`open_binary` with the supplied local path; use `inspect_artifact` when its
graph and findings help answer the question.
- 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
@@ -122,30 +109,18 @@ explicit path or endpoint and do not need it.
- .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`.
- 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`.
- 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
sidecars without fetching recorded URLs or claiming live attribution. See
[historical captures](https://github.com/morluto/rea/blob/main/docs/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
@@ -185,18 +160,8 @@ explicit path or endpoint and do not need it.
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/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). It does not raise the fixed byte,
address-space, or CPU allowances. Preserve carrier/decoded digests and treat selectors,
`raw` or `hex` encoding. 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
@@ -210,8 +175,6 @@ explicit path or endpoint and do not need it.
absent metadata does not prove a binary is not Go, and unparsed text records
remain explicit. See the
[Go binary guide](https://github.com/morluto/rea/blob/main/docs/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
@@ -238,77 +201,8 @@ explicit path or endpoint and do not need it.
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).
- 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 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).
- 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.
Opened Mach-O and dSYM UUID joins remain unobserved. 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).
- 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
@@ -331,8 +225,6 @@ explicit path or endpoint and do not need it.
- 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.
@@ -384,9 +276,8 @@ selected provider. Do not treat those scopes as interchangeable.
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.
330 seconds is configurable; changing the server's environment requires a new
REA process.
## Work summary-first
@@ -394,13 +285,7 @@ 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`; request
`"detail": "complete"` only when the dossier must travel inline, as
`build_call_path` requires.
`inspect_analysis_view`.
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.
@@ -488,14 +373,11 @@ as complete while required questions remain open.
[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:
- Browser/Electron observation (passive and scenario) and static/runtime
reconciliation:
[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)
## Finish the task
@@ -15,11 +15,9 @@ instead of guessing.
The provider requires a separately supplied headless JADX MCP JAR 0.7.1 and
Java 17 or newer. `REA_JADX_MCP_JAR` selects the absolute JAR path; `JAVA_HOME`
can select Java. Use `rea doctor --provider jadx --json` to check the JADX
configuration and obtain remediation for missing prerequisites. Java and the
headless JADX MCP JAR remain caller-supplied; REA setup does not install or
configure them. Configuration readiness does not establish successful analysis
of a particular APK.
can select Java. REA setup does not install or configure that JAR, and doctor
provider scopes cover Hopper/Ghidra rather than JADX. Use existing tools and
report missing prerequisites for the Android task specifically.
Linux and macOS use the POSIX owned-process boundary. Windows x64 requires the
matching bundled native controls and uses an owned Job Object with bidirectional
@@ -27,12 +25,8 @@ stdio. A full JDK, including `jdk.compiler`, is required on every host; Windows
`JAVA_HOME` selects `bin\java.exe`. Consult the Android guide for actual host
verification coverage. Calls use one worker and provider deadlines; optional
`REA_JADX_HEAP_MIB` selects the JVM heap and `REA_JADX_OPERATION_TIMEOUT_MS`
the per-operation deadline (integer milliseconds from 1 to 2147483647, default
120000). To change that deadline on a running MCP server, set
`jadx.operation_timeout_ms` through `update_analysis_settings`, then retry after
work and cleanup settle. Newly accepted operations use the override; accepted
work keeps its budget. Changing the launch environment requires a new process.
The MCP client's tool timeout must also allow the selected deadline. The APK is never launched,
the per-operation deadline (default 120000 ms; the MCP client's tool timeout
must also allow it). The APK is never launched,
and no emulator is required.
Manifest entries and decompiled text are static observations, not execution or
@@ -7,23 +7,18 @@ 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 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
server association before concluding that it is absent. A shared prefix alone
does not establish which server an approval wildcard covers. See
[client tool names and discovery](https://github.com/morluto/rea/blob/main/docs/client-configuration.md#client-tool-names-and-discovery).
main and an older release expose the same arguments.
## Choose the remedy from the observed failure
| Observation | Meaning and next step |
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No REA tools in the active session | Inspect registration with `rea doctor --client codex --json`. A skill installation alone does not register MCP. |
| Missing, malformed or stale registration | Prepare a scoped setup plan; apply only authorized changes with backups. |
| Aligned registration, but no connected tools | Restart/reconnect the client. Doctor checks registration; it cannot establish the current agent's connection. Inspect the client's launch error if reconnection fails. |
| MCP connection dropped or REA could not start | If the 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 the same failure returns, tell the user REA's connection failed 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. Run `rea doctor` when REA could not start or tools stay missing after restart. Doctor cannot restore a live connection by itself. |
| `MCP tool call requires approval, but approval policy is never` | Codex rejected the call before REA executed it. Do not install an engine or repeat setup to solve this client-policy decision. See the approval section below. |
| REA returns `provider_unavailable` or an unavailable tool entry | Read that operation's provider diagnostics or `binary_session.result.tool_availability`. Expand each group's tool names and inherit the shared client requirements unless overridden. Use the operation's availability, reason, and remediation; a complete tool catalog does not imply every operation is available. |
| `rea` is missing in a child shell, or Node is incompatible | Inspect the actual shell PATH and launcher/runtime. A prefix installation may be absent from a login shell's PATH; this does not establish a broken MCP registration. |
| Observation | Meaning and next step |
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No REA tools in the active session | Inspect registration with `rea doctor --client codex --json`. A skill installation alone does not register MCP. |
| Missing, malformed or stale registration | Prepare a scoped setup plan; apply only authorized changes with backups. |
| Aligned registration, but no connected tools | Restart/reconnect the client. Doctor checks registration; it cannot establish the current agent's connection. Inspect the client's launch error if reconnection fails. |
| `MCP tool call requires approval, but approval policy is never` | Codex rejected the call before REA executed it. Do not install an engine or repeat setup to solve this client-policy decision. See the approval section below. |
| REA returns `provider_unavailable` or an unavailable tool entry | Read that operation's provider diagnostics or `binary_session.result.tool_availability`. Expand each group's tool names and inherit the shared client requirements unless overridden. Use the operation's availability, reason, and remediation; a complete tool catalog does not imply every operation is available. |
| `rea` is missing in a child shell, or Node is incompatible | Inspect the actual shell PATH and launcher/runtime. A prefix installation may be absent from a login shell's PATH; this does not establish a broken MCP registration. |
If the client cannot launch its shell/sandbox helper or read the installed skill,
inspect its launch diagnostic. For example, Codex can refuse helper aliases when
@@ -61,13 +56,8 @@ authorized CLI fallback. Do not silently broaden permissions or switch targets.
A returned REA `provider_timeout` instead means the **server/provider** reached
its deadline. Follow its `remediation.action`, reuse accessible partial Evidence,
and resolve reported incomplete cleanup before retrying. Change a request
field or a `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. Do not invent a setting the remediation does not
name. Raising only the client deadline does not change
this server budget. For static JavaScript,
and resolve reported incomplete cleanup before retrying. Raising only the
client deadline does not change this server budget. For static JavaScript,
`analysis_timeout_ms` (CLI `rea analyze-javascript-application <path>
--analysis-timeout-ms <milliseconds>`) controls each source's
startup, analysis and transfer deadline in **milliseconds**. Increase that
@@ -85,37 +75,6 @@ installed CLI's command help. Refresh the catalog if stale; do not guess fields
from an older skill or return the entire catalog to repair one field. Correct
all reported fields before retrying.
## Recovery ladder
Follow the returned remediation. Change a request field or a `REA_*` setting
only when that text explicitly recommends the change. Restart REA when it says
the value is read at startup. A mention that a control does not raise a limit
is not a recommendation to change it. Do not invent a setting. Host memory and
`NODE_OPTIONS` do not raise a REA admission unless that remediation says so.
- MCP connection dropped: 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 REA's connection failed and ask them to restart
the MCP client. Do not run `rea doctor` for the drop itself.
- REA could not start its connection: retry once, then tell the user and ask
them to restart the MCP client. Run `rea doctor` when REA could not start or
tools stay missing after that restart. Do not retry a startup failure that
returns the same unchanged result.
- `cleanup_incomplete`, or retained cleanup owners in the error details: retry
that exact cleanup owner. Do not start new work that needs those resources
until that owner confirms release. Repeating the original call is not that
retry.
- Capacity, when the result is `resource_constraint` and `retryable` is true:
wait for the active admission to finish, then retry within the reported
limits. Do not restart REA to free the slot.
- `configuration_invalid`: correct the named setting in the environment that
starts REA (the shell or the MCP registration), then restart REA. Run
`rea doctor` to see the `runtime-configuration:` check. Re-run `rea setup`
for that client when the registration must carry the new setting, then
reconnect.
- `invalid_request`: correct the reported fields from the current schema. Do
not repeat the unchanged call.
## Repair registration when needed
For Codex:
@@ -128,7 +87,7 @@ rea setup --client codex --dry-run --json
Use the current supported client's ID: `claude_code`, `claude_desktop`, `codex`,
`cursor`, `gemini_cli`, `windsurf`, `devin`, `opencode`, `antigravity`,
`copilot_cli`, `commandcode`, `qwen_code`, `vscode`, `trae`, `grok_build`, `omp`, `pi`,
`hermes`, `qoder`, `droid`, `zcode`, `dsh`, `freebuff`, or `grok_bot`. If unknown, inspect `doctor --json` before choosing a
`hermes`, `qoder`, `droid`, `zcode`, `dsh`, or `grok_bot`. If unknown, inspect `doctor --json` before choosing a
scope. `grok_bot` uses a chat registration step rather than local `mcp.json`.
For unsupported clients, use version-pinned manual stdio registration or CLI.
@@ -175,17 +134,6 @@ writing. Managed policy can constrain client settings. Consult the
for the installed client's supported settings. This is an optional recovery
choice, not a prerequisite for every REA task.
## Repair an editable REA limit
If `details.limit` names an editable setting and its bounds, call
`update_analysis_settings` directly with a `changes` entry containing `setting`,
`action: "set"` and `value`. `inspect_analysis_settings` is optional when those
facts are already present. Retry the same input after work and cleanup settle.
Runtime defaults affect new work only; explicit per-call arguments take
precedence and must be corrected directly when selected. Reset restores launch
values. Updates survive target close/open but disappear on REA restart. They do
not edit host deadlines, response buffers, registrations or fixed limits.
## Continue through an installed CLI
Check the command environment only if the launcher fails:
@@ -1,216 +0,0 @@
# Agent environments from a live application
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
confirmed. The workflow records the application once with
`capture_browser_scenario`, checks what the environment would serve, records
again if a hint names a gap, then writes the bundle with
`export_environment_bundle`. For a HAR the user already has, skip to
[export](#export-and-hand-off) through `inspect_web_network_capture`, or use
`rea environment-build <capture.har> <dir>`.
## Before recording
- Record only against an instance the user says is disposable or seeded for
this purpose: every write step creates, edits or deletes its data. If the
user has not said so, ask before any scenario that writes. REA does not
start, seed or reset applications.
- Credentials come from environment variables the user names. Declare each as a
scenario secret, `{secret_id, environment_variable}`; the variable must be set
in the environment REA's MCP server started with. Never ask for a password in
chat. Declared secrets are redacted as the capture records.
- Map the application first with `inspect_web_page` (and `capture_web_screenshot`
when layout matters): the login form's locators, and the API calls behind the
pages that list and edit the entities the user asked about.
## Record a session an environment can learn writes from
One `capture_browser_scenario` records the whole session. Start from this
shape, with the application's own locators, paths and headers:
```json
{
"browser": { "mode": "launch", "executable_path": "/usr/bin/chromium" },
"start_url": { "url": "http://127.0.0.1:3000/login" },
"secrets": [
{ "secret_id": "admin_password", "environment_variable": "APP_PASSWORD" }
],
"capture": {
"network": {
"request_body": true,
"response_body": true,
"header_values": true
}
},
"actions": [
{
"step_id": "user",
"action": "fill",
"timeout_ms": 15000,
"locator": { "kind": "css", "selector": "input[name=username]" },
"value": { "source": "literal", "value": "admin" }
},
{
"step_id": "password",
"action": "fill",
"timeout_ms": 15000,
"locator": { "kind": "css", "selector": "input[name=password]" },
"value": { "source": "secret", "secret_id": "admin_password" }
},
{
"step_id": "sign-in",
"action": "press",
"timeout_ms": 15000,
"locator": { "kind": "css", "selector": "input[name=password]" },
"key": "Enter"
},
{
"step_id": "signed-in",
"action": "wait_for",
"timeout_ms": 15000,
"locator": { "kind": "css", "selector": "nav a[href='/settings']" },
"state": "visible"
},
{
"step_id": "folders-1",
"action": "request",
"timeout_ms": 15000,
"method": "GET",
"path": "/api/folders",
"expected_status": [200]
},
{
"step_id": "create-folder-1",
"action": "request",
"timeout_ms": 15000,
"method": "POST",
"path": "/api/folders",
"json": { "title": "Scratch folder 1" },
"headers": [
{
"name": "X-CSRFToken",
"value": { "source": "cookie", "name": "csrftoken" }
}
],
"expected_status": [200, 201]
}
]
}
```
Give every step a `timeout_ms`. A step without one waits until the scenario
is cancelled, so a locator that never matches hangs the capture, and the
client with it. Match `wait_for` locators to exactly one element.
1. Sign in through the UI: `fill` the user and password fields (the password
as a secret reference), submit, then `wait_for` an element only a signed-in
user sees.
2. Read with `request` steps: each collection the environment needs, twice
before any write, so fields that change on their own show up; one item of
each.
3. Use `request` steps only when the API lets the caller choose each scratch
object's ID or a prior read already established it. Request paths are fixed
strings; no action substitutes a value from an earlier response. For
server-assigned IDs, create through the UI and open the new object's detail
view to record its read, edit and delete through UI actions. If that journey
cannot be scripted, leave the write unserved and report the limitation;
never guess an ID or create objects with no supported cleanup path.
Record two numbered scratch objects per write type,
with varied free text. After each write, and before the next one, read the
object back by id and then its list: create 1, read 1, list, create 2,
read 2, list; never several writes before their reads.
4. Leave more than a second between a create and its first edit
(`wait_for_timeout` with `duration_ms: 1100`).
5. Update both objects and read each back; delete both and read each back
(expect 404) and the list.
About `request` steps:
- `path` is on the start URL's origin; the page must be on that origin.
- Header values are literals, declared secrets, a cookie the browser holds
(`{source: "cookie", name}`), or a local storage entry
(`{source: "local_storage", key, json_pointer}`), with an optional
`prefix` such as `"Bearer "`. Copy the headers the application's own
requests send (a CSRF header, a bearer token). Values read from the page
are redacted from records made after they are read, not from the login
response that returned them, and values shorter than 8 characters are not
redacted at all.
- Reading a page value makes later bodies costlier to keep: after a CSRF
token, bodies over about 47 KiB are kept as `unavailable`. When the API
returns larger responses, raise `capture.limits`
(`max_network_body_allocation_bytes`, `max_total_event_allocation_bytes`).
- Request steps follow no redirects: a redirect fails the step, and
`expected_status` cannot name a 3xx status. Use the path the redirect names,
such as the one with the trailing slash.
- Set `expected_status` on every step, so a refused call fails its step and
the later steps report not having run, instead of recording read-backs of
nothing.
Keep it to about 60 to 160 requests. A scenario with no `request` step is
recorded exactly as before.
The capture returns its whole Evidence, every recorded body included, which is
often more than a client shows inline. Before checking coverage, confirm that
every entry of `normalized_result.steps` has `status: "completed"`; if the
client saved the result to a file, search it for `"status":"failed"` and read
that step's `error`. A failed sign-in or write cancels every step after it.
Use the result's `evidence_id` for coverage and export; do not paste the
Evidence back into another tool.
## Check coverage
`inspect_analysis_view` with `source: {kind: "retained-evidence",
evidence_id}` (the capture's id) and `view: {kind: "environment-coverage",
template_rules: ["after-uid-literal", "opaque-token", "sibling-variance",
"prefix-variance"]}`. It reports read tools
answered from the state or replayed (`reads.recorded_reasons`), the served write
tools, and each withheld write with its `reason` and `hint`. It does not apply
the serve-time catalog and state size checks; the export reports those.
Re-record a withheld write at most twice, and only for a recording gap. Stop at
once when the reason is application behaviour, and report the write as
withheld with its hint:
| Reason | What to do |
| --------------------- | --------------------------------------------------------------------------------------------- |
| `no-state-target` | Re-record: read the collection or item the write changes. |
| `no-observation` | Re-record: read the entity back by id after the write; read it first for an update or delete. |
| `unconfirmed` | Re-record: read the entity and its list after the write. |
| `incomplete-entities` | Re-record: read the entity through every view that lists it. |
| `unexplained-change` | Re-record once with a second, different call of the same write. |
| `no-call` | Re-record once with a write that succeeds. |
| `side-effect` | Stop: the application changes other entities. |
| `contradicted` | Stop: a later read disagrees with the modelled effect. |
| `body-unusable` | Stop: the body is not a JSON object. |
Use new scratch names on each attempt; the first attempt's objects may still
exist.
New browser scenario captures include request times, so a fully timed capture
can learn a write clock and serve updates that stamp modification times. Older
captures or ones missing a request start keep record order and learn no clock.
When coverage reports that limitation, record again with the current version
before concluding that the timestamp change cannot be served.
## Export and hand off
`export_environment_bundle` with `source: {kind: "retained-evidence",
evidence_id}` (or `{kind: "evidence-file", path}`), an absolute
`output_directory` that does not exist yet and whose parent does, and a `name`
(required when the application runs on `localhost` or an IP address). It returns
the coverage again, `tool_count`, the written files and warnings; `dry_run:
true` reports without writing. The bundle installs `rea-agents` of this REA's
version from the registry; when the user runs an unreleased REA build, pass the
`package_tarball` they give you (a packed `rea-agents` tarball) instead.
Tell the user how many tools answer from the state, which writes are served and
which are withheld and why, and the run command (`agent-env run <folder>`). Say
that the bundle ships the recorded responses and the capture's local paths
(for a browser scenario, the browser executable's path), and
list any `credential-named-members` warning pointers for review. Never publish
or share the bundle yourself.
See
[agent environments](https://github.com/morluto/rea/blob/main/docs/agent-environments.md)
for what the state, replay and the bundle establish, and
[evidence workflows](evidence-workflows.md) for the manifest and state views.
@@ -48,9 +48,7 @@ or absence:
- `compare_artifacts` compares inventory pages by occurrence path, content,
metadata, and graph relations.
- `compare_functions` compares explicit function Evidence, not fuzzy
whole-binary matches. It rejects an `analyze_function` summary reply; pass
the summary's `parent_evidence_id` as a `retained-evidence` reference or
the complete dossier.
whole-binary matches.
- `compare_bundles` compares canonical bundle membership and unknown history.
- `find_changed_behavior` combines existing comparisons; static differences
remain candidates, not causal proof.
@@ -95,32 +93,4 @@ Use `view: {kind: "environment-state"}` with the manifest's `template_rules` to
extract the data those reads observed instead: entity types and identity
fields, entities with recorded values, documents, and per-tool render plans
checked against the recordings. The state comes only from reads before the
first related write; types, identity and list membership are inferences. The
view also gives each state-changing tool a write plan, served only when a later
recorded read confirms its effect and none contradicts it.
`rea environment-serve manifest.json --evidence capture.json` derives that
state at startup, answers read tools from it, serves the write tools whose plan
is served, each applying its effect to that state, and serves `data/reset`,
`data/add` and `data/get` on `/agentenv`; other write tools are not served, and
each plan states why in its `reason`. It replays the capture instead with
`--replay-only` or when the state is unavailable (not derivable, or a catalog
over the discovery budget), and its stopped document's `state` says which
applied.
Use `view: {kind: "environment-coverage"}` with the same rules to see what an
environment built from a capture would serve: read tools answered from the
state or replayed with their reason, and each write tool served or withheld
with a hint about what to record next; it does not apply the serve-time
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.
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"],
fields: ["name"], offset: 0, limit: 32}` and the reported parent Evidence ID.
All same-name versions and aliases survive; use `source_index` with the parent ID
for stable identity. `collection: "issues"` pages interpretation failures. Page
coverage describes selected retained rows; `summary.coverage` or `exports.coverage`
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.
first related write; types, identity and list membership are inferences.
@@ -17,21 +17,11 @@ the complete artifact inventory and identity but reads source text and analyzes
only selected files. The result and summary record `source_selection`; files
outside scope and their relationships remain unknown, so partial application
coverage does not mean the selected source failed. Use repeated `--source-path`
on either static application CLI route, or `--source-paths-json` with a JSON
file for selections too long for one command line. Omit the selection when the
question requires complete application analysis; `source_paths: []` is invalid,
not an unfiltered request. `detail: "summary"` reduces delivery
on either static application CLI route. Omit the selection when the question
requires complete application analysis; `source_paths: []` is invalid, not an
unfiltered request. `detail: "summary"` reduces delivery
and does not by itself reduce analysis work.
ASAR header/index admission defaults to 16 MiB / 16777216 bytes;
an admission error names `asar.max_header_bytes` and its bounds (at most
256 MiB). Call `update_analysis_settings` with that setting, `action: "set"`
and enough bytes, then retry the same full-archive request. No inspection or
REA restart is required. `REA_ASAR_MAX_HEADER_BYTES` seeds future REA processes.
`source_paths`, `detail`, and `max_heap_mb` do not bypass or raise header admission.
Runtime JavaScript timeout/heap defaults are also editable; explicit per-call
`analysis_timeout_ms` and `max_heap_mb` take precedence.
Exact `#alias` specifiers resolve through the importer's
enclosing package `imports` map to package-local files; nested `node_modules`
packages use their own map. Wildcard, recursive, and external-package import
@@ -64,20 +54,6 @@ in the same session. A returned `error.details.partial_observation` reference
can be supplied directly to `inspect_analysis_view`. If the SDK cancels locally, retrieve its recorded ID from
the Evidence bundle before inspecting the partial analysis.
## Inventory route candidates without returning the application graph
When the advertised schema supports `view`, use `detail: "summary"` with
`view: {kind: "javascript-routes", collection: "routes", fields: ["path", "confidence"], limit: 20}`.
The same view works with `analyze_web_bundle` on already selected captured sources.
The complete source Evidence is retained; use its `parent_evidence_id` with
`inspect_analysis_view` for `summary`, `routes`, `occurrences`, `sources`, or
`unknowns`. Filter and batch IDs before requesting more fields, and follow
`coverage.next_offset`. CLI selections require `--evidence_output` to keep the
complete record. Static patterns, inferred deployment bases, heuristic system
classification, and unavailable sources never establish route reachability or
the complete application route denominator. See the
[route inventory guide](https://github.com/morluto/rea/blob/main/docs/javascript-route-inventory.md).
## Read a module without repeating analysis
Use the actual returned parent ID, on the same connection, as `source.evidence_id`.
@@ -4,10 +4,8 @@ For release comparisons, export `list_procedures` Evidence from each target befo
closing it. Close clears session records: import both bundles after collecting the
two targets, or supply portable inline Evidence. `match_native_functions` pairs unique exact names and, when supplied,
complete observed body bytes. Reuse optional same-context `list_names`,
`procedure_info`, `read_bytes` and existing `analyze_function` dossiers as
`left_observations`/`right_observations`; an MCP `analyze_function` summary
reply contributes its `parent_evidence_id` as a `retained-evidence` reference,
not the summary itself. Avoid decompiling every function merely to match it. Its summary retains a parent for
`procedure_info`, `read_bytes` and existing `analyze_function` Evidence; avoid
decompiling every function merely to match it. Its summary retains a parent for
`inspect_analysis_view` `function-matches` collections `matches` and `functions`.
Select columns, batch IDs and page individual facts; aliases remain unresolved.
Unpaired observations do not prove additions/removals. Existing matched dossier
@@ -47,8 +45,7 @@ See [desktop installations](https://github.com/morluto/rea/blob/main/docs/deskto
## Go build metadata
Use `inspect_go_binary` for an explicit local ELF, PE or Mach-O (universal
files need `architecture`) when the
Use `inspect_go_binary` for an explicit local ELF, PE or thin Mach-O when the
question concerns the embedded compiler version, module/dependency versions,
local replacements or build settings. No `open_binary`, Go installation or
target execution is required. The CLI equivalent is `inspect-go-binary PATH`.
@@ -87,8 +84,7 @@ See [IL2CPP metadata](https://github.com/morluto/rea/blob/main/docs/il2cpp-metad
## Rust dependency metadata
Use `inspect_rust_binary` for an explicit local ELF, PE, or Mach-O (universal
files need `architecture`) when
Use `inspect_rust_binary` for an explicit local ELF, PE, or thin Mach-O 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`.
@@ -98,112 +94,6 @@ 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).
## 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).
## 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).
## 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. Opened Mach-O and dSYM UUID joins remain unobserved. 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).
## 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
@@ -258,30 +148,6 @@ 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
After `open_binary`, use focused search, procedure, or function tools directly.
@@ -294,9 +160,6 @@ unknown rather than false.
For a completed `analyze_function` dossier, use `inspect_analysis_view` with
`view: {kind: "native", facet: "value_flow_operations", offset: 0, limit: 32}`
and the exact retained Evidence ID to read selected facts without reanalysis.
The default MCP reply is already a summary of that dossier: its
`normalized_result.parent_evidence_id` is the retained ID to page, and its
null counts mark facets the provider did not record or declared unavailable.
Native facets also include procedure, pseudocode, assembly, basic blocks,
comments, callers/callees, references, unresolved calls, referenced strings and
names, the native API record, and value-flow summaries. Pseudocode offsets and limits count UTF-16
@@ -316,10 +179,6 @@ run, use `observe_native_calls` with explicit breakpoints and a bounded
`duration_ms`/`max_events`. It launches a new process under LLDB, so the target
runs and may change files or use the network. Hardened-runtime targets need the
`get-task-allow` entitlement.
Function breakpoints use exact LLDB runtime names: a Darwin C `_main` static symbol
may be `main` at runtime. REA does not translate names. Set `module` to the image
file name when selecting that image only; otherwise loaded system images also
match. Inspect retained locations and module paths before attributing a call.
Use `binary_session` with no arguments to check the open target, selected
provider, and alignment. Its default `result.tool_availability` carries a
@@ -342,18 +201,14 @@ providers. Top-level session capabilities describe the current route, and
bridge startup, and readiness. The default 330,000 ms is configurable through
`REA_GHIDRA_STARTUP_TIMEOUT_MS`; a smaller query still needs the same cold import.
When the error names `ghidra.startup_timeout_ms`, call
`update_analysis_settings` with `action: "set"` and a larger value within the
reported bounds. Resolve retained cleanup before the next startup attempt.
No REA restart is needed; an already accepted startup keeps its original
budget. A new attempt repeats import. Preserve useful Evidence before closing
and reopening the target when remediation requires that lifecycle step.
For a per-function timeout, update `ghidra.decompile_timeout_seconds` and retry
the same function on the existing Program. Explicit `null` removes that
per-function deadline. Inspection is optional when the error supplies the
setting and bounds. Runtime updates disappear when REA exits; environment
changes only seed future REA processes.
Preserve useful Evidence before closing the session or restarting REA. Check
`close_binary` and resolve reported cleanup failures. Set a larger startup budget
in the environment that launches REA, then restart REA and reconnect the MCP
client before reopening the same target. Closing and reopening a binary in the
existing server keeps its original configuration and starts import again.
Environment changes in an agent's shell do not update an already running MCP
server. If its launch configuration cannot be changed, report that constraint
and the exact retry setting instead of claiming a fixed technical limit.
The client's tool-call deadline must allow startup plus the requested query;
server settings and progress notifications cannot override it. CLI analysis
@@ -370,35 +225,6 @@ Exported C and embedded resources remain useful independent evidence, but a
timeout does not establish complete analysis or absence of code. See the
[deadline and client guide](https://github.com/morluto/rea/blob/main/docs/mcp-contracts.md#ghidra-first-query-deadlines-and-recovery).
## Bridge request ceiling
Hopper and Cutter share `REA_BRIDGE_MAX_REQUEST_BYTES`. Unset, both sides use
16777216 bytes. The payload budget excludes the terminating LF. A decimal
integer from 7464 to 536870887 raises or lowers that ceiling. The floor fits
the largest bounded symbol-name command. A tight Cutter status still returns
its session id and up to 64 completion receipts. Its path is included when it
fits, and is unknown when it does not, so an older descriptor path is not
paired with the new generation. The maximum is the encoded payload, one byte under Node's string ceiling so
the framing LF still fits. Cutter measures, signs, and writes a fitting
response in bounded chunks. The bridge searches only the newly read bytes
for the framing LF. The client unescapes each socket chunk into one response
string before parsing it, so this ceiling does not keep the paged wire frame
and another concatenated copy. Invalid values are configuration errors and do not fall back to 16777216. Set the variable in the environment that starts REA, restart REA,
and start Hopper or Cutter from that same environment. An out-of-band bridge
without the setting stays at 16777216 bytes. The client reports that mismatch
during discovery only after the hello proves a live authenticated bridge, and
does not send a status request or the command. A stale descriptor is ignored.
Discovery fails if any such live ceiling differs, even when another live
session matches. A command response discarded for that ceiling stays an
unknown execution with `output_truncated`. A hello
that never sends its authenticated frame does not hold the accept loop.
At most 8 connection threads run; a further accept is closed, and a failure
to start a thread does not stop the listener. `completed_connection` is the
longest prefix of connections that have terminated. Dense scalar replies are
encoded in bounded batches.
The 25000 ms Cutter request deadline and the 256-candidate discovery budget
still have no runtime override.
## Static memory and original file bytes
For Ghidra, `read_bytes` reads initialized analysis memory, including loader
@@ -519,8 +345,8 @@ every executable in the bundle. It also lists missing or weak loads and earlier
`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/mcp-contracts.md).
Use `extract_artifact` when materialized files are needed. It extracts the active
target and accepts optional `integrity_policy`; see the [MCP contracts](https://github.com/morluto/rea/blob/main/docs/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
@@ -529,19 +355,14 @@ destination. No separate permission grant is needed.
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.
claim child inventory when only root identity is available. Extraction is a
separate filesystem-writing operation.
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/ghidra-dos.md).
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
@@ -570,16 +391,6 @@ 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/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/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
@@ -1,52 +1,4 @@
# Runtime observation
## Native runtime questions
Start from the requested observation. State the selected target, input, expected
result, failure signal and stopping condition before a run. Reuse static Evidence
to choose a function or hypothesis; `trace_native_values` follows recovered
static value dependencies and does not measure runtime values. Prefer static
inspection or existing capture comparison when it already answers the question.
| Question | Existing workflow | Host and effects |
| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Which selected native entry is reached, with which integer argument registers? | `observe_native_calls` on the active Mach-O, with exact function/Objective-C breakpoints and a duration/event bound | macOS with LLDB and required debugger access. Launches a new owned process, stops at entries, records and continues; target execution may write files, show UI or use the network. It never attaches to an existing process. Inspect termination and partial coverage after the run. |
| What does caller-authored instrumentation observe? | `list_frida_devices` / `list_frida_processes` when discovery is needed; `start_frida_session`, `load_frida_script`, then status/unload/close | Requires compatible installed Frida bindings and selected device access; the real REA verifier covers Linux. Attach selects an existing PID; spawn selects a program and starts it paused until `resume_frida_session`. Scripts execute in the target and can change memory, files, network activity and behavior. This is not passive inspection. |
| Which debugger state or controlled execution explains the behavior? | `start_gdb_session` and `gdb_console`, or `start_rizin_debug_session` and `rizin_debug_command` | Persistent debugger workflows are Linux-only. GDB starts a debugger; console commands choose/load/attach/run targets. Rizin debug start launches the selected target and requires a durable `inferior_tty`. Both consoles are unrestricted and can change target memory/registers, run shell commands, write files and use the network. |
| How do terminal output, process lifetime and selected files differ between runs? | `capture_process_scenario`, then `compare_process_captures` with compatible recorded inputs | New captures require Linux or macOS and a working native PTY backend; macOS also needs `xcrun`/Swift for ownership inspection. Capture launches an owned command with ordinary user permissions. Observation paths select snapshots, not a filesystem sandbox. Comparing retained captures does not execute targets and is also available on Windows. |
| What does an existing desktop window show, or what changes after a declared UI action? | `observe_native_ui`; use `capture_native_ui_scenario` only for requested actions | macOS 14+ with required Screen Recording/Accessibility access. Select an existing PID/window. Observation does not launch or own the app; scenario actions can change its data or trigger network activity, and leave its state as-is. |
Close each persistent session with its matching tool. `close_frida_session`
unloads scripts and detaches; it does not kill the target. A still-paused spawn
is resumed only after instrumentation cleanup is confirmed. GDB and Rizin close
can detach a live inferior and let it continue only after verifying its durable
output terminal. When disposition or cleanup is unresolved, preserve the owned
session and reported recovery; do not assume that closing the debugger terminated
the target. Process capture and LLDB call observation instead own their launched
processes and report their termination/cleanup outcome. Cancellation is not proof
that cleanup completed.
Unrestricted GDB/Rizin commands and relevant start/close operations retire an
active binary-analysis session in the same MCP server. Close an active Ghidra
session explicitly first; after target effects settle, reopen the artifact before
new static analysis. A Cutter bridge command is likewise unrestricted and may
affect its GUI/target; it is not a passive native observation substitute.
To test a static hypothesis across two builds, retain compatible static
comparison Evidence and `compare_process_captures` Evidence. Supply explicit
finding/dimension mappings to `correlate_static_and_runtime`. It does not ingest
arbitrary Frida, LLDB or debugger traces, auto-match names, or prove causality
from cochange. Preserve those traces as their own Evidence and leave unobserved
behavior unknown. Record truncation, unmatched breakpoints, changed target
identity and cleanup uncertainty as limitations, rather than treating no retained
event as proof that the behavior never occurs.
Detailed contracts and prerequisites: [Frida](https://github.com/morluto/rea/blob/main/docs/frida-instrumentation.md),
[GDB/Rizin/Cutter](https://github.com/morluto/rea/blob/main/docs/reverse-engineering-tools.md),
[process capture](https://github.com/morluto/rea/blob/main/docs/process-capture.md),
and [native calls/UI](https://github.com/morluto/rea/blob/main/docs/native-investigation.md#native-desktop-observation).
## Browser and Electron
# Browser and Electron runtime observation
Supply the existing browser or Electron instance's literal-port loopback CDP
endpoint in the request. No separate permission configuration or per-call
+1 -11
View File
@@ -17,10 +17,7 @@ describe the use case and observable outcome. -->
## Change and scope
<!-- Explain the chosen approach, why it fits REA's architecture, and what is
intentionally not included. If adding complexity for a speculative or vanishingly
unlikely race or security edge case, record the concrete failure mode, likelihood,
complexity cost, and the user's buy-in obtained before implementation. Do not
silently expand scope for theoretical robustness. -->
intentionally not included. -->
Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope).
Unresolved scope, authority, side effects, or required verification block
@@ -36,9 +33,6 @@ unchecked. Maintainers can help resolve open questions.
Use "none" or "not applicable" only when established. -->
- Semantic owner and earliest changed stage:
<!-- Name the producer of parsed facts or resources and its affected consumers.
Summarize superseded paths removed and any distinct policy, lifecycle,
lazy-loading, or compatibility responsibility retained. -->
- Contribution scope fit and linked maintainer decision:
- CLI and MCP/tool-catalog contract:
- Provider, bridge, target-format, or platform compatibility:
@@ -71,10 +65,6 @@ source, link, or rendered-document checks. -->
- User-visible CLI/MCP output (if applicable):
- Remaining proof gaps:
For boundary fixes, identify the applicable [boundary regressions](https://github.com/morluto/rea/blob/main/docs/testing.md#boundary-regressions)
and [lifecycle regressions](https://github.com/morluto/rea/blob/main/docs/testing.md#lifecycle-regressions)
covered by the evidence above, and explain relevant gaps.
For evidence-bearing changes:
- [ ] Observed, derived, and inferred claims remain distinguishable.
+7 -47
View File
@@ -72,7 +72,6 @@ jobs:
browser: ${{ steps.plan.outputs.browser }}
binary_layout: ${{ steps.plan.outputs.binary_layout }}
evm: ${{ steps.plan.outputs.evm }}
apk_signature: ${{ steps.plan.outputs.apk_signature }}
javascript_recovery: ${{ steps.plan.outputs.javascript_recovery }}
recorded_crash: ${{ steps.plan.outputs.recorded_crash }}
web_network: ${{ steps.plan.outputs.web_network }}
@@ -385,7 +384,6 @@ jobs:
needs.changes.outputs.browser == 'true' ||
needs.changes.outputs.binary_layout == 'true' ||
needs.changes.outputs.evm == 'true' ||
needs.changes.outputs.apk_signature == 'true' ||
needs.changes.outputs.javascript_recovery == 'true' ||
needs.changes.outputs.recorded_crash == 'true' ||
needs.changes.outputs.web_network == 'true' ||
@@ -411,9 +409,7 @@ jobs:
with:
save: ${{ github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
- run: npm run test:prepare
# Fast and full shards measure different file costs. Download only the
# matching history so one tier cannot pack the other with its weights.
- name: Find available trusted timing history
- name: Find available trusted full-suite timing history
id: history
continue-on-error: true
env:
@@ -421,14 +417,13 @@ jobs:
CI_REPOSITORY: ${{ github.repository }}
CI_REPOSITORY_ID: ${{ github.repository_id }}
CI_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
CI_TIMING_TIER: ${{ needs.changes.outputs.tier }}
run: node scripts/ci/test-history.mjs
- name: Download previous timing report
if: steps.history.outputs.run-id != ''
continue-on-error: true
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: ${{ steps.history.outputs.artifact-name }}
name: test-timings
run-id: ${{ steps.history.outputs.run-id }}
github-token: ${{ github.token }}
path: .cache/ci-test-history
@@ -560,22 +555,11 @@ jobs:
include-hidden-files: true
if-no-files-found: error
retention-days: 30
# Default-branch runs only. Pull-request artifacts share this name and
# the lookup reads one page, so publishing them would hide main.
- name: Publish successful timing history
if: >-
success() &&
needs.changes.outputs.code == 'true' &&
needs.test-shard.result == 'success' &&
(
needs.changes.outputs.tier == 'full' ||
(needs.changes.outputs.tier == 'fast' && needs.test-source.result == 'success')
) &&
github.ref == format('refs/heads/{0}', github.event.repository.default_branch) &&
(github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.tier == 'full' && success() && needs.test-shard.result == 'success'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: test-timings-${{ needs.changes.outputs.tier }}
name: test-timings
path: .cache/ci-test-history/test-results.json
include-hidden-files: true
if-no-files-found: error
@@ -764,7 +748,6 @@ jobs:
tests/boundary/mcp/nativeDemangleMcp.test.ts
tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts
tests/boundary/cli/nativeSignatureCli.test.ts
tests/boundary/cli/nativeMachoArm64_32Cli.test.ts
tests/boundary/mcp/nativeSignatureTargetBindingMcp.test.ts
src/process/capture/FilesystemSnapshot.test.ts
tests/process-global/filesystemSnapshotFallbackCancellation.test.ts
@@ -789,7 +772,7 @@ jobs:
id: npm-cache
uses: ./.github/actions/npm-cache
- run: npm ci
- name: Verify static CLI/MCP journeys and synthetic report conformance
- name: Run the public CLI/MCP reconstruction journey
env:
REA_READINESS_REPORT_PATH: readiness-report.json
run: |
@@ -822,11 +805,6 @@ jobs:
with:
save: "true"
- run: npm ci
- name: Typecheck with the official Windows compiler
env:
GOMAXPROCS: "2"
GOMEMLIMIT: "3GiB"
run: npm run typecheck -- --checkers 2
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: rea-build-cache
@@ -857,12 +835,7 @@ jobs:
npx vitest run --coverage.enabled=false
src/process/capture/FilesystemSnapshot.test.ts
tests/process-global/filesystemSnapshotFallbackCancellation.test.ts
tests/boundary/filesystem/typescriptCompiler.test.ts
tests/boundary/filesystem/releaseProposal.test.ts
tests/boundary/filesystem/binaryTarget.test.ts
src/contracts/sessionPathInputs.test.ts
tests/boundary/filesystem/directAnalysisSnapshotReplay.test.ts
tests/boundary/mcp/rawBinaryMcp.test.ts
tests/boundary/filesystem/safeOutputTree.test.ts
tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts
tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts
@@ -934,7 +907,7 @@ jobs:
tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22.23.4
node-version: 22.19.0
- name: Verify native path boundaries on Node 22
run: >-
npx vitest run --coverage.enabled=false
@@ -950,7 +923,7 @@ jobs:
retention-days: 30
docs:
name: Documentation
name: Docs Generation
needs: changes
if: needs.changes.outputs.docs == 'true'
runs-on: ubuntu-latest
@@ -965,8 +938,6 @@ jobs:
uses: ./.github/actions/npm-cache
- run: npm ci
- run: npm run docs:check
- name: Render documentation and validate page links
run: npm run docs:build
- name: Generation leaves authored files unchanged
run: git diff --exit-code
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
@@ -1009,16 +980,6 @@ jobs:
build_cache_artifact: rea-build-cache
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
apk_signature:
needs: [changes, static, build]
if: needs.changes.outputs.apk_signature == 'true'
secrets:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
uses: ./.github/workflows/real-apk-signature.yml
with:
build_cache_artifact: rea-build-cache
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
javascript_recovery:
needs: [changes, static, build]
if: needs.changes.outputs.javascript_recovery == 'true'
@@ -1119,7 +1080,6 @@ jobs:
browser,
binary_layout,
evm,
apk_signature,
javascript_recovery,
recorded_crash,
web_network,
+4 -20
View File
@@ -25,16 +25,16 @@ jobs:
node-version-file: .nvmrc
- name: Validate the SDK pin against the provider's verified release
run: node scripts/verify-ghidra-bridge-sources.mjs --check-sdk-pin >> "$GITHUB_ENV"
# The bridge probe imports only source-owned ARM/Thumb bytes and never
# executes target code. All SDK/project state stays in runner storage.
- name: Acquire checksum-verified Ghidra SDK in isolated runner storage
# Only the jars and application.properties are extracted: the check
# compiles against them and never launches Ghidra or imports a target.
- name: Acquire checksum-verified Ghidra jars in isolated runner storage
shell: bash
run: |
ghidra_root="${RUNNER_TEMP}/rea-ghidra"
mkdir -p "${ghidra_root}"
curl -fsSL --retry 3 --write-out 'ghidra_download_bytes=%{size_download} ghidra_download_seconds=%{time_total}\n' -o "${ghidra_root}/ghidra.zip" "${GHIDRA_RELEASE_URL}"
echo "${GHIDRA_RELEASE_SHA256} ${ghidra_root}/ghidra.zip" | sha256sum -c -
unzip -q "${ghidra_root}/ghidra.zip" -d "${ghidra_root}"
unzip -q "${ghidra_root}/ghidra.zip" '*/Ghidra/*.jar' '*/Ghidra/application.properties' -d "${ghidra_root}"
rm "${ghidra_root}/ghidra.zip"
properties="$(find "${ghidra_root}" -path '*/Ghidra/application.properties' -print -quit)"
test -n "${properties}"
@@ -46,19 +46,3 @@ jobs:
java_home_variable="JAVA_HOME_${GHIDRA_JAVA_MAJOR}_X64"
test -n "${!java_home_variable}"
JAVA_HOME="${!java_home_variable}" npm run verify:ghidra:bridge-sources -- --verified-sdk
- name: Inspect real ARM and Thumb instruction context through the bridge
shell: bash
run: |
java_home_variable="JAVA_HOME_${GHIDRA_JAVA_MAJOR}_X64"
export JAVA_HOME="${!java_home_variable}"
probe_root="${RUNNER_TEMP}/rea-arm-context"
mkdir -p "${probe_root}/project"
printf '\036\377\057\341\160\107\000\000' > "${probe_root}/arm-thumb.bin"
timeout 180s "${GHIDRA_INSTALL_DIR}/support/analyzeHeadless" "${probe_root}/project" ArmContext \
-import "${probe_root}/arm-thumb.bin" -loader BinaryLoader -processor ARM:LE:32:v7 \
-noanalysis -readOnly -deleteProject \
-scriptPath "${GITHUB_WORKSPACE}/tests/conformance/ghidra;${GITHUB_WORKSPACE}/bridge/ghidra" \
-postScript ReaArmContextProbe.java "${GITHUB_WORKSPACE}/bridge/ghidra" \
> "${probe_root}/output.log" 2>&1 || { cat "${probe_root}/output.log"; exit 1; }
cat "${probe_root}/output.log"
grep -F 'REA_ARM_CONTEXT_OK arm=0 thumb=1 unavailable=null (injected provider value)' "${probe_root}/output.log"
@@ -1,55 +0,0 @@
name: Real Android metadata bridge
on:
push:
branches: ["**"]
paths:
- .github/workflows/real-android-metadata.yml
- bridge/android/**
- src/android/JadxRelease.ts
- scripts/fixtures/android/**
- tests/conformance/android/**
- tests/fixtures/android/static-initializers/**
- tests/fixtures/android/direct-invokes/**
- .nvmrc
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: android-metadata-${{ github.ref }}
cancel-in-progress: true
jobs:
android-metadata:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
- name: Fetch checksum-pinned engine and public APK fixture
run: node scripts/fixtures/android/fetch.mjs "$RUNNER_TEMP/rea-android"
- name: Compile and exercise the real metadata bridge
shell: bash
run: |
export JAVA_HOME="$JAVA_HOME_21_X64"
android_root="$RUNNER_TEMP/rea-android"
classes="$android_root/classes"
jar="$android_root/jadx-headless-mcp-0.7.1-all.jar"
mkdir -p "$classes"
"$JAVA_HOME/bin/javac" -J-Xmx512m -J-XX:ActiveProcessorCount=2 \
-classpath "$jar" -d "$classes" \
bridge/android/ReaJadxBridge.java \
tests/conformance/android/ReaJadxMetadataRegression.java
"$JAVA_HOME/bin/java" -Xmx512m -XX:ActiveProcessorCount=2 \
-classpath "$classes:$jar" ReaJadxMetadataRegression \
"$android_root/ApiDemos-debug.apk" \
"$GITHUB_WORKSPACE/tests/fixtures/android/static-initializers" \
"$classes" "$GITHUB_WORKSPACE/tests/fixtures/android/direct-invokes" \
| tee "$android_root/metadata.log"
grep -F 'RAW_CLASS_SELECTOR_REGRESSION_PASS nested=true metadata=true references=true' \
"$android_root/metadata.log"
-97
View File
@@ -1,97 +0,0 @@
name: Real APK signature inspection
on:
workflow_call:
inputs:
build_cache_artifact:
description: Portable build cache artifact from the calling CI run.
required: false
type: string
default: ""
remote_cache_write:
description: Trusted release qualification may publish the remote cache.
required: false
type: boolean
default: false
secrets:
TURBO_TOKEN:
description: Remote cache token. Only trusted callers pass it.
required: false
workflow_dispatch:
permissions:
contents: read
env:
HUSKY: "0"
# A called workflow sees the caller event, so workflow_call is not an event
# name here. Writes when remote_cache_write is set, or on the default branch
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
# and use the local cache only.
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
NODE_OPTIONS: "--max-old-space-size=768"
RAYON_NUM_THREADS: "1"
GOMAXPROCS: "2"
UV_THREADPOOL_SIZE: "2"
JAVA_TOOL_OPTIONS: "-Xmx512m -XX:ActiveProcessorCount=2"
jobs:
real-apk-signature:
runs-on: ubuntu-latest
timeout-minutes: 12
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
- name: Cache npm package downloads
id: npm-cache
uses: ./.github/actions/npm-cache
- run: npm ci
- uses: ./.github/actions/portable-build-cache
with:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
- name: Prepare pinned SDK oracle without installing host software
shell: bash
run: |
python3 - <<'PYTHON'
import hashlib, os, urllib.request, zipfile
from pathlib import Path
root = Path(os.environ["RUNNER_TEMP"]) / "rea-apk-signature-sdk"
packages = [
("build-tools_r36.1_linux.zip", "a7b5889e4a79fcf3b0976bef40d401f4240fb1eed891d9d91169da1111e11d78", 64195204, "build-tools/36.1.0", {"apksigner", "aapt2", "lib/apksigner.jar", "source.properties"}),
("platform-36_r02.zip", "37607369a28c5b640b3a7998868d45898ebcb777565a0e85f9acf36f29631d2e", 65878410, "platforms/android-36", {"android.jar", "source.properties"}),
]
for name, expected, expected_size, directory, selected in packages:
archive = root / name
archive.parent.mkdir(parents=True, exist_ok=True)
digest = hashlib.sha256()
total = 0
with urllib.request.urlopen("https://dl.google.com/android/repository/" + name, timeout=60) as response, archive.open("wb") as output:
while chunk := response.read(1024 * 1024):
total += len(chunk)
if total > expected_size:
raise RuntimeError("SDK archive exceeds the pinned package size budget")
digest.update(chunk)
output.write(chunk)
if total != expected_size or digest.hexdigest() != expected:
raise RuntimeError("SDK oracle archive digest mismatch")
extracted = set()
with zipfile.ZipFile(archive) as source:
for entry in source.infolist():
relative = entry.filename.partition("/")[2]
if relative not in selected:
continue
target = root / directory / relative
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(source.read(entry))
target.chmod((entry.external_attr >> 16) & 0o777 or 0o644)
extracted.add(relative)
if extracted != selected:
raise RuntimeError("Pinned SDK package is missing required oracle files")
with Path(os.environ["GITHUB_ENV"]).open("a") as environment:
environment.write("ANDROID_HOME=" + str(root) + "\n")
PYTHON
- run: npm run verify:apk-signature -- --require-tools
-6
View File
@@ -55,12 +55,6 @@ jobs:
- uses: ./.github/actions/portable-build-cache
with:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
- name: Install ELF32 cross toolchains for source-owned fixtures
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends \
gcc-i686-linux-gnu gcc-arm-linux-gnueabi gcc-mips-linux-gnu gcc-mipsel-linux-gnu \
libc6-dev-i386-cross libc6-dev-armel-cross libc6-dev-mips-cross libc6-dev-mipsel-cross
- name: Acquire exact upstream libraries in an isolated runner environment
shell: bash
run: |
+1 -2
View File
@@ -48,8 +48,7 @@ jobs:
- uses: ./.github/actions/portable-build-cache
with:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
# Own a fresh bus so Chromium never depends on an inherited session address.
- run: dbus-run-session -- npm run verify:browser
- run: npm run verify:browser
env:
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
REA_BROWSER_NO_SANDBOX: "true"
+1 -2
View File
@@ -44,8 +44,7 @@ jobs:
- uses: ./.github/actions/portable-build-cache
with:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
# Own a fresh bus so Chromium never depends on an inherited session address.
- run: dbus-run-session -- npm run verify:browser:runtime
- run: env -u DBUS_SESSION_BUS_ADDRESS npm run verify:browser:runtime
env:
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
REA_BROWSER_NO_SANDBOX: "true"
+1 -5
View File
@@ -46,12 +46,8 @@ jobs:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
- name: Install the pinned compiler into an isolated fixture directory
run: npm install --prefix "$RUNNER_TEMP/rea-web-source-map-compiler" --no-audit --no-fund --package-lock=false esbuild@0.25.10
# Own a fresh bus so Chromium never depends on an inherited session address.
- run: dbus-run-session -- npm run verify:browser:source-maps
- run: npm run verify:browser:source-maps
env:
# Retain browser launch argv/PID/stderr in the verifier's captured
# CLI failure diagnostics without changing its deadline or retrying.
DEBUG: pw:browser
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
REA_BROWSER_NO_SANDBOX: "true"
REA_WEB_SOURCE_MAP_COMPILER: ${{ runner.temp }}/rea-web-source-map-compiler/node_modules/esbuild/lib/main.js
+40 -118
View File
@@ -47,63 +47,19 @@ jobs:
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
# Filter at the repository connection, without a search-index dependency.
pages=$(gh api graphql --paginate --slurp \
-f owner="${GH_REPO%/*}" -f name="${GH_REPO#*/}" \
-f query='query($owner:String!,$name:String!,$endCursor:String) {
repository(owner:$owner,name:$name) {
nameWithOwner
pullRequests(first:100,after:$endCursor,states:OPEN,baseRefName:"main",labels:["rea:release-notes-finalized"]) {
nodes { number headRefName headRepository { nameWithOwner } }
pageInfo { hasNextPage endCursor }
}
}
}')
finalized=$(jq -er '
if type != "array" or length == 0 then error("Missing GitHub pages") else . end |
if .[-1].data.repository.pullRequests.pageInfo.hasNextPage != false
then error("Incomplete GitHub pagination") else . end |
map(
if (.errors // [] | length) != 0 then error("GitHub query failed") else . end |
.data.repository |
if (.nameWithOwner | type) != "string" or (.pullRequests.nodes | type) != "array" or
(.pullRequests.pageInfo.hasNextPage | type) != "boolean"
then error("Invalid GitHub repository connection") else . end |
.nameWithOwner as $repo | .pullRequests.nodes[] |
if (.number | type) != "number" or (.headRefName | type) != "string" or (has("headRepository") | not) or
(.headRepository != null and (.headRepository.nameWithOwner | type) != "string")
then error("Invalid GitHub pull request") else . end |
select(.headRepository.nameWithOwner == $repo) |
select(.headRefName | startswith("release-please--branches--main--")) | .number
) | map(tostring) | join(" ")' <<< "$pages")
finalized=$(gh api --paginate "repos/${GH_REPO}/pulls?state=open&base=main&per_page=100" \
--jq '.[] | select(.head.repo.full_name == .base.repo.full_name) | select(.head.ref | startswith("release-please--branches--main--")) | select(any(.labels[]; .name == "rea:release-notes-finalized")) | .number')
if [[ -n "${finalized}" ]]; then
echo "Finalized release notes on PR ${finalized}; automatic proposal refresh is paused." >> "$GITHUB_STEP_SUMMARY"
echo "frozen=true" >> "$GITHUB_OUTPUT"
fi
- name: Check out proposal controller and complete Git history
if: steps.editorial.outputs.frozen != 'true'
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
if: steps.editorial.outputs.frozen != 'true'
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install proposal controller dependencies
if: steps.editorial.outputs.frozen != 'true'
run: npm ci
- name: Update release proposal
if: steps.editorial.outputs.frozen != 'true'
env:
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
SOURCE_SHA: ${{ github.sha }}
run: node scripts/release-proposal.mjs --target-branch main --source-sha "$SOURCE_SHA"
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
target-branch: main
skip-github-release: true
merged-release:
name: Release reviewed main merge
@@ -276,25 +232,15 @@ jobs:
node scripts/release-notes.mjs publication --repository "$GH_REPO" \
--source "$SOURCE_SHA"
- name: Prepare checkpoint release proposal
if: inputs.phase == 'prepare'
id: proposal
env:
# A dedicated credential starts PR CI automatically; the built-in
# token creates approval-required PR runs for maintainers to approve.
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
RELEASE_BRANCH: ${{ inputs.release_branch }}
SOURCE_SHA: ${{ steps.branch.outputs.sha }}
run: node scripts/release-proposal.mjs --target-branch "$RELEASE_BRANCH" --source-sha "$SOURCE_SHA"
- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
if: inputs.phase == 'publish'
id: release
with:
# A dedicated credential starts PR CI automatically; the built-in
# token creates approval-required PR runs for maintainers to approve.
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
target-branch: ${{ inputs.release_branch }}
skip-github-release: false
skip-github-pull-request: true
skip-github-release: ${{ inputs.phase == 'prepare' }}
skip-github-pull-request: ${{ inputs.phase == 'publish' }}
- name: Require a merged release PR for publication
if: inputs.phase == 'publish' && steps.release.outputs.release_created != 'true'
@@ -314,26 +260,26 @@ jobs:
fi
- name: Check out release pull request
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
ref: ${{ fromJSON(steps.proposal.outputs.pr).headBranchName }}
ref: ${{ fromJSON(steps.release.outputs.pr).headBranchName }}
fetch-depth: 0
persist-credentials: false
- name: Set up Node.js for generated documentation
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install dependencies
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
run: npm ci
- name: Validate prepared release version and migration notes
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
env:
RELEASE_BRANCH: ${{ inputs.release_branch }}
SOURCE_SHA: ${{ steps.branch.outputs.sha }}
@@ -343,11 +289,11 @@ jobs:
--source-sha "$SOURCE_SHA"
- name: Regenerate release documentation
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
run: npm run docs:generate
- name: Validate generated release documentation
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
run: npm run docs:check && git diff --exit-code
qualification:
@@ -369,9 +315,6 @@ jobs:
rehearsal:
name: Rehearse qualified release on a clean runner
env:
TURBO_TOKEN: ""
TURBO_CACHE: local:rw
needs: [merged-release, release-please, qualification]
if: >-
always() && !cancelled() && needs.qualification.result == 'success'
@@ -405,9 +348,6 @@ jobs:
always() && !cancelled() &&
needs.qualification.result == 'success' && needs.rehearsal.result == 'success'
runs-on: ubuntu-latest
env:
TURBO_TOKEN: ""
TURBO_CACHE: local:rw
permissions:
contents: read
id-token: write
@@ -416,56 +356,41 @@ jobs:
with:
ref: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
persist-credentials: false
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version-file: .nvmrc
package-manager-cache: false
registry-url: https://registry.npmjs.org
- name: Download the original qualified package
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: verified-release-package
path: .cache/verified-package/
- name: Bind publication to qualified intent
id: package
run: node scripts/ci/prepare-npm-publication.mjs
- name: Publish
if: steps.package.outputs.published == 'false'
env:
NPM_RELEASE_TAG: ${{ steps.package.outputs.tag }}
run: >-
npm publish .cache/verified-package/package.tgz --ignore-scripts
--registry=https://registry.npmjs.org --access=public --tag="$NPM_RELEASE_TAG"
verify-npm:
name: Verify published npm package
needs: [merged-release, release-please, publish]
if: >-
always() && !cancelled() && needs.publish.result == 'success'
runs-on: ubuntu-latest
env:
TURBO_TOKEN: ""
TURBO_CACHE: local:rw
permissions:
contents: read
actions: read
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
ref: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
persist-credentials: false
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version-file: .nvmrc
package-manager-cache: false
- run: npm ci
- uses: ./.github/actions/prepare-release-package
id: package
- name: Inspect existing npm version
id: registry
env:
VERIFIED_PACKAGE_TARBALL: ${{ steps.package.outputs.tarball }}
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
run: node scripts/ci/npm-release.mjs inspect "$VERIFIED_PACKAGE_TARBALL" "$VERIFIED_PACKAGE_VERSION"
- name: Publish
if: steps.registry.outputs.published == 'false'
shell: bash
env:
VERIFIED_PACKAGE_TARBALL: ${{ steps.package.outputs.tarball }}
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
run: |
tag="$(node scripts/release-npm-tag.mjs "$VERIFIED_PACKAGE_VERSION")"
npm publish "${VERIFIED_PACKAGE_TARBALL}" --access public --tag "${tag}"
- name: Verify published npm bytes and dist-tag
env:
VERIFIED_PACKAGE_TARBALL: ${{ steps.package.outputs.tarball }}
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
run: node scripts/ci/npm-release.mjs verify "$VERIFIED_PACKAGE_TARBALL" "$VERIFIED_PACKAGE_VERSION"
- name: Verify published CLI from npm
env:
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
@@ -473,12 +398,9 @@ jobs:
publish-mcp:
name: Publish to MCP Registry
env:
TURBO_TOKEN: ""
TURBO_CACHE: local:rw
needs: [merged-release, release-please, verify-npm]
needs: [merged-release, release-please, publish]
if: >-
always() && !cancelled() && needs.verify-npm.result == 'success' &&
always() && !cancelled() && needs.publish.result == 'success' &&
((needs.merged-release.result == 'success' && needs.merged-release.outputs.release_created == 'true') ||
(needs.release-please.result == 'success' && inputs.phase == 'publish' && needs.release-please.outputs.release_created == 'true'))
runs-on: ubuntu-latest
+3 -3
View File
@@ -17,10 +17,10 @@ jobs:
strategy:
fail-fast: false
matrix:
# Exercise unavailable-runtime diagnostics and rolling supported releases.
# Security floors prevent fallback to an older hosted-toolcache patch.
# Exercise unavailable-runtime diagnostics, the first supported release,
# and new patches on both supported lines without hand-editing versions.
# The repository-pinned Node 24 version also runs the complete test suite.
node: ["^22.23.4", "24.22.1", "^24.22.1", "^26.12.1"]
node: ["22.x", "24.15.0", "24.x", "26.x"]
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+1 -1
View File
@@ -1,5 +1,5 @@
{
"threshold": 4,
"threshold": 3.5,
"reporters": ["consoleFull"],
"ignore": [
"node_modules/**",
+1 -1
View File
@@ -1 +1 @@
24.22.1
24.18.0
+3 -95
View File
@@ -1,13 +1,7 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "unicorn", "oxc", "vitest"],
"plugins": ["typescript", "import", "unicorn", "oxc"],
"rules": {
"rea/no-buffer-deep-equality": "error",
"rea/cancellation-cause": "error",
"no-async-promise-executor": "error",
"no-unreachable": "error",
"no-unsafe-optional-chaining": "error",
"no-useless-escape": ["warn", { "allowRegexCharacters": ["-"] }],
"complexity": ["warn", 20],
"max-depth": ["warn", 4],
"max-lines": [
@@ -29,38 +23,7 @@
"max-params": ["warn", 4],
"no-var": "warn",
"no-var-requires": "error",
"typescript/no-import-type-side-effects": "error",
"vitest/no-standalone-expect": "off",
"vitest/no-conditional-expect": "off",
"vitest/expect-expect": "off",
"vitest/no-disabled-tests": "off",
"vitest/require-mock-type-parameters": "off",
"vitest/require-to-throw-message": "off",
"import/no-duplicates": [
"warn",
{
"preferInline": false
}
],
"vitest/valid-expect": [
"warn",
{
"maxArgs": 2
}
],
"rea/no-evidence-json-preadmission": "error",
"rea/no-positional-tool-contract": "error",
"rea/no-fixture-root-cleanup": "error",
"typescript/no-floating-promises": "error",
"typescript/no-misused-promises": "warn",
"typescript/return-await": ["error", "error-handling-correctness-only"],
"typescript/switch-exhaustiveness-check": [
"error",
{
"considerDefaultExhaustiveForUnions": true
}
],
"rea/no-shared-module-mock": "error"
"typescript/no-import-type-side-effects": "error"
},
"overrides": [
{
@@ -73,12 +36,6 @@
"no-var-requires": "off"
}
},
{
"files": ["src/**/*.ts"],
"rules": {
"typescript/no-misused-promises": "error"
}
},
{
"files": ["tests/**/*.ts", "src/**/*.test.ts", "src/**/*.fixture.ts"],
"rules": {
@@ -90,8 +47,7 @@
"skipBlankLines": true,
"skipComments": true
}
],
"typescript/no-misused-promises": "warn"
]
}
},
{
@@ -135,45 +91,6 @@
}
]
}
},
{
"files": ["tests/**/*.test.ts", "src/**/*.test.ts"],
"rules": {
"vitest/valid-expect": [
"warn",
{
"maxArgs": 2
}
],
"vitest/valid-expect-in-promise": "error",
"vitest/no-focused-tests": "error"
}
},
{
"files": [
"scripts/lib/http3-capture-assertions.test.mjs",
"scripts/lib/http3-verifier-lifecycle.test.mjs"
],
"rules": {
"typescript/no-floating-promises": [
"error",
{
"allowForKnownSafeCalls": [
{
"from": "package",
"package": "@types/node",
"name": "test"
}
]
}
]
}
},
{
"files": ["scripts/fixtures/javascript-recovery/code-split/entry.mjs"],
"rules": {
"typescript/no-floating-promises": "off"
}
}
],
"env": {
@@ -192,14 +109,5 @@
".devin",
".factory",
"third_party"
],
"options": {
"reportUnusedDisableDirectives": "error"
},
"jsPlugins": [
{
"name": "rea",
"specifier": "./scripts/lint/index.mjs"
}
]
}
+1 -1
View File
@@ -1,3 +1,3 @@
{
".": "6.8.0"
".": "6.7.0"
}
+6 -11
View File
@@ -22,8 +22,6 @@ Keep proof of ownership separate from validity, editability, and readiness. Malf
For lifecycle changes, follow [ownership and completion invariants](docs/tool-design.md#own-the-lifecycle) and select the applicable [lifecycle regressions](docs/testing.md#lifecycle-regressions). Include late handshakes, cancellation during final cleanup, mixed child outcomes, and cleanup retry where those stages exist; active-execution cancellation alone does not cover finalization.
Before adding complexity for a speculative or vanishingly unlikely race or security edge case, explain the concrete failure mode, likelihood, and complexity cost to the user and get their buy-in. Do not silently expand scope for theoretical robustness.
Assess brittleness in touched code during implementation, debugging, review, and maintenance. Fix the producing representation or ownership rule before adding downstream repairs. Normalize once at the owning boundary using format-aware parsers. Remove superseded representations, aliases, wrappers, and redundant tests after verifying affected consumers. Introduce abstractions only when they remove observed complexity.
When fixing a bug, inspect the producing boundary and affected consumers for duplicated interpretation or unclear ownership. Complete any directly related refactor in the same change and remove superseded code. Passing regression tests alone does not establish that the brittleness is resolved. If related cleanup remains, explain what remains and why.
@@ -61,7 +59,7 @@ do not establish fewer tokens or faster analysis.
## Development
Use [.nvmrc](.nvmrc) and `package.json#packageManager` for the pinned toolchain. Cursor Cloud may place older Node.js first on `PATH`; environment setup installs the pinned toolchain under `/usr/local/bin`. Prefix `PATH="/usr/local/bin:$PATH"` before `node` and `npm`.
Use [.nvmrc](.nvmrc) and `package.json#packageManager` for the pinned toolchain. Cursor Cloud may place older Node.js first on `PATH`; environment setup installs the pinned toolchain under `/usr/local/bin`.
- `npm ci`: install locked dependencies.
- `npm run build:cached`: build the runtime and packaged skill.
@@ -72,15 +70,13 @@ Use [.nvmrc](.nvmrc) and `package.json#packageManager` for the pinned toolchain.
Use relevant checks for the change. The complete local gate, `npm run check:pr`, is optional for broad changes or CI diagnosis. See [CONTRIBUTING.md](CONTRIBUTING.md) for development, generated-file ownership, and release conventions.
Coordinate expensive checks across agents. If a test unexpectedly consumes heavy CPU, stop its owned process family and profile a bounded representative case before retrying. Inspect production work, fixture generation, subprocesses, and repeated setup; preserve regression coverage when optimizing. Follow the [test resource guidance](docs/testing.md#developer-commands).
`docs/public/product-catalog.json`, `docs/verification/managed-conformance-*.json`, and `skills/` are ignored outputs. Edit source contracts and `.agents/skills/reverse-engineer-anything/`, then regenerate as needed. `.cache/mcp-tool-catalog.json` is test metadata; runtime builds and source checking do not consume it. Never commit binaries, provider project documents, credentials, `dist/`, `node_modules/`, or local planning artifacts.
## Tool and Test Changes
For tool design, follow [docs/tool-design.md](docs/tool-design.md): start from the analyst question, reuse existing contracts, preserve caller choices, and return useful evidence inline. Limits must follow real format, protocol, authority, or resource constraints. Account for representation expansion before allocation; derive completeness from examined coverage.
For test changes or provider claims, use [docs/testing.md](docs/testing.md). Select applicable [boundary regressions](docs/testing.md#boundary-regressions) for parser, inventory, snapshot, and projection fixes. Prefer real public CLI/MCP workflows, then production-boundary integration, then producer goldens. Keep module tests for distinct semantics stronger workflows cannot reliably reproduce. Preserve malformed-input, cancellation, cleanup, permission, capacity, and target-identity coverage when pruning. Report unverified coverage explicitly; simulated providers do not establish real-provider behavior.
For test changes or provider claims, use [docs/testing.md](docs/testing.md). Prefer real public CLI/MCP workflows, then production-boundary integration, then producer goldens. Keep module tests for distinct semantics stronger workflows cannot reliably reproduce. Preserve malformed-input, cancellation, cleanup, permission, capacity, and target-identity coverage when pruning. Report unverified coverage explicitly; simulated providers do not establish real-provider behavior.
Use Conventional Commit subjects and PR titles. Mark breaking changes with `!` or a `BREAKING CHANGE:` footer. Describe behavior changes, relevant validation, and real-provider verification scope.
@@ -108,11 +104,10 @@ distinguish source-derived conclusions, proposed checks, and unavailable coverag
Address actionable automated review findings and explain disagreements.
Leave the template's **Author readiness** declarations unchecked for the human
owner to complete. Never attest to human review on their behalf. Draft status
is optional; preserve the PR's current draft status unless the user asks to change
it. Later pushes do not invalidate the readiness declaration or require another
human sign-off. Keep the PR body and affected validation current as work changes.
The readiness check validates
owner to complete. Never attest to human review on their behalf. Keep the PR in
draft until the human declares the PR initially ready. Later pushes do not
invalidate that declaration or require another human sign-off. Keep the PR body
and affected validation current as work changes. The readiness check validates
the filled declaration; CI and automated review results do not establish that
human review occurred, maintainer scope approval, or merge permission.
+1 -103
View File
@@ -1,107 +1,5 @@
# Changelog
## [6.8.0](https://github.com/morluto/rea/compare/rea-agents-6.7.0...rea-agents-6.8.0) (2026-10-25)
### Highlights
- Build local agent environments from network captures, browser scenarios, or a prompt, then serve derived reads and evidence-gated writes and compare them with fresh application captures.
- Inspect more shipped formats, including GPU code objects, BEAM, Python bytecode, Unity IL2CPP metadata, Godot PCK, PyInstaller, Node.js SEA, devicetree, and additional PE/ELF tables.
- Keep large investigations focused with retained summaries, selected fact queries, configurable recovery limits, and row-oriented CLI exports. Complete Evidence remains available.
### ⚠ BREAKING CHANGES
- **Function analysis:** `analyze_function` now defaults to a retained summary. Set `detail: "complete"` for the previous inline dossier; the CLI retains its complete default and requires `--evidence_output` when selecting a summary. [#5660](https://github.com/morluto/rea/pull/5660). Thanks @CryptoJones, @gensokyo-dev, @RishTheFish21.
- **Accessibility:** Accept JSON numbers and booleans in node `value`, alongside strings and null. Update consumers that previously assumed text-only values. [cc90771](https://github.com/morluto/rea/commit/cc90771af14f045538651615147d8bf86b576549), [#6759](https://github.com/morluto/rea/pull/6759), [#6467](https://github.com/morluto/rea/issues/6467). Thanks @coygeek, @gensokyo-dev, @mvanhorn.
- **JSON Lines:** `--format jsonl` now emits object-collection rows followed by a `$rea: "jsonl/v1"`, `type: "metadata"` record; scalar selections and unsupported reports are refused. Parse control records separately, select a single supported collection when necessary, and use `--format json` for the previous whole-document output. See [JSON Lines framing](https://github.com/morluto/rea/blob/rea-agents-6.8.0/docs/cli.md#json-lines-record-output). [#5629](https://github.com/morluto/rea/pull/5629), [#5487](https://github.com/morluto/rea/issues/5487). Thanks @gensokyo-dev, @jayykernel, @vijaykiran06.
- **Interface Builder:** Keyed-archive graph identities use decimal table UIDs instead of authored `objectID`/`object-id`. Treat authored IDs as attributes and update persisted graph lookups; ibtool and native NIBArchive identities keep their source semantics. [6fbe67d](https://github.com/morluto/rea/commit/6fbe67d66e3d716db69929503a3a232a7ada0860).
- **Frida:** `instrument-with-frida` JSON returns the canonical Evidence directly. Read `normalized_result.cleanup_error` instead of the former top-level wrapper field. [48f700e](https://github.com/morluto/rea/commit/48f700ee35e0988ed973e5486dc9db33f2a5d944).
- **Reconstruction proof:** ID-array proof records no longer establish verified or ready ledgers. Supply implementation/revision bindings and individual claim outcomes; handle inapplicable, unavailable, and failed proof diagnostics. [f8b65e7](https://github.com/morluto/rea/commit/f8b65e78723b1e2169f94dd1226198ea3139707e).
- **Mach-O imports:** Handle a null import `name` when identity is ambiguous; use `raw_name` and `name_status` to distinguish observed expressions from exact symbols. [5d0d243](https://github.com/morluto/rea/commit/5d0d243a45c9132e1aff75d454ab53d86a74cc31), [169512a](https://github.com/morluto/rea/commit/169512a35bb015ff209168fb8bc1c9b31a2c678b).
- **Node.js:** Use a runtime satisfying `^22.23.4 || ^24.22.1 || >=26.12.1`; Node 23 and 25 are unsupported. Upgrade Node before installing or updating REA; the installer does not upgrade it. [#6315](https://github.com/morluto/rea/pull/6315), [#6283](https://github.com/morluto/rea/issues/6283). Thanks @GamerDubz, @gensokyo-dev, @ShikamaruNaaraa.
- **Browser storage:** Historical hashed storage remains readable, but comparisons stay unknown until both captures are recaptured in the new representation. [#6341](https://github.com/morluto/rea/pull/6341), [#6289](https://github.com/morluto/rea/issues/6289). Thanks @coygeek, @gongfpp.
- **Environment serving:** `environment-serve` serves derived state by default, including write tools whose effects are confirmed by recorded evidence. Use `--replay-only` for stateless replay of manifest tools. Read stop-summary `state` instead of `state_evidence_id`. [#5351](https://github.com/morluto/rea/pull/5351), [#6158](https://github.com/morluto/rea/pull/6158). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
- **MCP schemas:** Input advertisements default to compact. Set `REA_MCP_INPUT_SCHEMA_PROFILE=full` for complete advertised guidance and nested constraints. [#5975](https://github.com/morluto/rea/pull/5975). Thanks @gensokyo-dev.
- **Browser console arguments:** Accept null `argument_types` and `text_capture.status: "unavailable"` when the protocol argument inventory is omitted or malformed; preserve that uncertainty. [#6016](https://github.com/morluto/rea/pull/6016), [#5934](https://github.com/morluto/rea/issues/5934). Thanks @coygeek, @kaoru0822-kitauji.
- **Reconstruction summaries:** `summary.by_evidence_authority` is keyed by Evidence authority; read the former source-state counts from required `summary.by_source_state`. Regenerate ledgers whose closure digests depended on the old projection. [#5875](https://github.com/morluto/rea/pull/5875), [#5838](https://github.com/morluto/rea/issues/5838). Thanks @coygeek, @kaoru0822-kitauji.
- **Swift bindings:** Conformance `type_name` and `protocol_name` may be null; consume typed `type_binding`/`protocol_binding` evidence. Replace the former Objective-C-specific import-binding type/schema with `MachoImportBinding`. [d0067ba](https://github.com/morluto/rea/commit/d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f).
- **WebSocket frames:** Accept null opcodes and preserve unknown frame facts; numeric opcodes are restricted to the four-bit protocol range. [0bc2b42](https://github.com/morluto/rea/commit/0bc2b42001f684beffcf47b70e507443b1a5d320).
- **Resource-only inspection:** Evidence subjects and executable digests may be null. Use `resource_root` and each resource's reported provenance instead of requiring an executable target. [6a6adf7](https://github.com/morluto/rea/commit/6a6adf703fa44100643947d20fbc670f86260b1b).
- **Browser timestamps:** Console and session-timeline timestamps may be null when the protocol supplies no finite/comparable time. Keep unknown time explicit in sorting and comparisons. [690fc51](https://github.com/morluto/rea/commit/690fc518540bae982038a74f47ef7781201785aa), [2a0410f](https://github.com/morluto/rea/commit/2a0410f1717b3126e8a95063d77dea66a597010a).
- **Reference sources:** Read failed root enumeration from `rootFailure` rather than a synthetic failed child at `"."`; preserve the top-level source-graph limitation. [6296fdf](https://github.com/morluto/rea/commit/6296fdf560f1ac4aa55985558edaa98300aacdbc).
- **Filesystem capture:** Accept `entry_unavailable` and `identity_changed` enumeration omission reasons in addition to the previous reason literals. [c85b44e](https://github.com/morluto/rea/commit/c85b44e1b14851c54e65327a4807f79ad5cd5a61). Thanks @rudrakshagarwal4200-ai.
- **APK locales:** Replace numeric legacy selectors such as `es-r419` with `es-419` or `b+es+419`. Two-letter regions remain supported. [29910d1](https://github.com/morluto/rea/commit/29910d16b98fe4b607a7aa15928789a73614b0b9). Thanks @mertkayacs.
- **XML plist scalars:** Recognize `$plist_type: "unknown"` and read `element`, `literal`, and `reason`; unresolved XML scalars use this wrapper instead of the former real/null unknown marker or malformed scalar prefixes. Supported extended UTC years preserve their timestamp. [3537e58](https://github.com/morluto/rea/commit/3537e58c1ab29309f06eabf50bc52adba9817a0c).
### Changes
- **Binary layouts:** Inspect ELF32 i386, little-endian ARM, and little- or big-endian MIPS `EXEC`/`DYN`/`REL` layouts on Linux x64 using a caller-supplied pwntools environment. [#7052](https://github.com/morluto/rea/pull/7052), [#6934](https://github.com/morluto/rea/issues/6934). Thanks @roblennon, @salmanalmutairi1.
- **Agent environments:** Project network captures and Evidence files into manifests, preserve template path pairs and positions, export AgentEnv bundles, and serve manifests as replay environments. [#4954](https://github.com/morluto/rea/pull/4954), [#5293](https://github.com/morluto/rea/pull/5293), [#5664](https://github.com/morluto/rea/pull/5664), [#4999](https://github.com/morluto/rea/pull/4999), [#4975](https://github.com/morluto/rea/pull/4975). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
- **Capture to environment:** Build environments from browser scenarios or a prompt, extract the initial observed state, and compare the environment with application probe captures. [#5182](https://github.com/morluto/rea/pull/5182), [#6946](https://github.com/morluto/rea/pull/6946), [#5319](https://github.com/morluto/rea/pull/5319), [#5699](https://github.com/morluto/rea/pull/5699). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
- **State-backed tools:** Render read filters, search, sorting and paging from state; apply confirmed write effects with broader state targets and recording-based write admission. [#5352](https://github.com/morluto/rea/pull/5352), [293832b](https://github.com/morluto/rea/commit/293832bafc9a054914ec514fe906dd4936c7a372), [#5800](https://github.com/morluto/rea/pull/5800), [#5787](https://github.com/morluto/rea/pull/5787), [#5600](https://github.com/morluto/rea/pull/5600), [5375ff0](https://github.com/morluto/rea/commit/5375ff0f9aadb4fd0d46333815aa0431e3df1010), [#6169](https://github.com/morluto/rea/pull/6169), [#6385](https://github.com/morluto/rea/pull/6385), [#6897](https://github.com/morluto/rea/pull/6897). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
- **State learning:** Model application-assigned values, preserve view-specific clock spellings, learn write clocks from scenario timing, derive fields, and learn creates across sorted arrays and referenced entities. [7ec9b6e](https://github.com/morluto/rea/commit/7ec9b6e56e1b3762285dca881db6d900a389ab9c), [#6136](https://github.com/morluto/rea/pull/6136), [957bcfa](https://github.com/morluto/rea/commit/957bcfa149c1a0593bb0aa2466cc56d1d4f62e7d), [#6138](https://github.com/morluto/rea/pull/6138), [#6967](https://github.com/morluto/rea/pull/6967), [#7025](https://github.com/morluto/rea/pull/7025), [#7034](https://github.com/morluto/rea/pull/7034). Thanks @earakely-scale.
- **Recorded writes:** Serve creates whose compatible read-backs add omitted fields or express timestamps at a different precision, and whole-object updates that regenerate observed slugs or modification times. Conflicting observations still withhold writes. State views and bundles carrying the new read-back data need REA 6.8.0 or later to serve. [#7080](https://github.com/morluto/rea/pull/7080). Thanks @earakely-scale.
- **Native investigations:** Page native inventory summaries, match functions across builds, export pseudocode to resumable directories, and select objdump disassembly by symbol. [#5378](https://github.com/morluto/rea/pull/5378), [#5390](https://github.com/morluto/rea/pull/5390), [#6170](https://github.com/morluto/rea/pull/6170), [#5171](https://github.com/morluto/rea/pull/5171), [#4249](https://github.com/morluto/rea/issues/4249), [#5343](https://github.com/morluto/rea/issues/5343), [#5159](https://github.com/morluto/rea/issues/5159). Thanks @coygeek, @CryptoJones, @gensokyo-dev, @kaoru0822-kitauji.
- **Engine workflows:** Bound a single Ghidra decompilation without closing its session, analyze GameCube/Wii DOL executables, and inspect static CUDA/HIP code objects. [#5662](https://github.com/morluto/rea/pull/5662), [#6240](https://github.com/morluto/rea/pull/6240), [#5813](https://github.com/morluto/rea/pull/5813), [#5340](https://github.com/morluto/rea/issues/5340), [#5345](https://github.com/morluto/rea/issues/5345). Thanks @arielfikru, @coygeek, @gensokyo-dev, @RishTheFish21.
- **PE/ELF:** Inspect export tables without an engine, PE imports/delay-loads, Authenticode certificates, TLS callbacks/load configuration, ELF notes, Linux kernel module metadata, and Zstandard section-name tables. [#6278](https://github.com/morluto/rea/pull/6278), [#6509](https://github.com/morluto/rea/pull/6509), [#4830](https://github.com/morluto/rea/pull/4830), [#7008](https://github.com/morluto/rea/pull/7008), [#4837](https://github.com/morluto/rea/pull/4837), [#7010](https://github.com/morluto/rea/pull/7010), [#6987](https://github.com/morluto/rea/pull/6987), [#4057](https://github.com/morluto/rea/issues/4057). Thanks @gensokyo-dev, @kaoru0822-kitauji, @mvanhorn, @Rinkia, @ShuaixinHuang.
- **Apple/native metadata:** Inspect IPS crash reports, decode Swift nominal declarations, and select universal Mach-O slices in Go/Rust metadata readers. [#6511](https://github.com/morluto/rea/pull/6511), [#6257](https://github.com/morluto/rea/pull/6257), [#6219](https://github.com/morluto/rea/pull/6219), [#5341](https://github.com/morluto/rea/issues/5341), [#4102](https://github.com/morluto/rea/issues/4102). Thanks @coygeek, @gensokyo-dev, @kaoru0822-kitauji, @mvanhorn, @Rinkia.
- **Packaged applications:** Triage desktop installation directories and inspect/extract Godot PCK, PyInstaller archives, and Node.js single-executable application blobs. [#5207](https://github.com/morluto/rea/pull/5207), [#3634](https://github.com/morluto/rea/pull/3634), [afa5da7](https://github.com/morluto/rea/commit/afa5da745e0b139e68029a96b776ae30e48ea2b2), [#2433](https://github.com/morluto/rea/pull/2433), [#7000](https://github.com/morluto/rea/pull/7000), [#4399](https://github.com/morluto/rea/issues/4399). Thanks @0xthearchitect, @coygeek, @gensokyo-dev, @mvanhorn.
- **Unity and managed code:** Inventory IL2CPP images/types/methods and metadata versions 24/27; read member names and literals, link native methods and generic instances, and export Ghidra seeds. Project managed call/field-access edges and page managed member summaries. [#3807](https://github.com/morluto/rea/pull/3807), [#5326](https://github.com/morluto/rea/pull/5326), [52d1a72](https://github.com/morluto/rea/commit/52d1a72af047a73b787b8935b032786a34966036), [#5327](https://github.com/morluto/rea/pull/5327), [#5785](https://github.com/morluto/rea/pull/5785), [#6260](https://github.com/morluto/rea/pull/6260), [#5167](https://github.com/morluto/rea/pull/5167), [#3784](https://github.com/morluto/rea/issues/3784), [#5112](https://github.com/morluto/rea/issues/5112), [#4056](https://github.com/morluto/rea/issues/4056), [#5148](https://github.com/morluto/rea/issues/5148). Thanks @8rulerstar, @alexlanghart, @elricfd, @gensokyo-dev, @jaelliot, @kaoru0822-kitauji, @Rinkia.
- **Android:** Inspect APK signing declarations and structured manifest components; report static-final DEX field values. [e93e3b1](https://github.com/morluto/rea/commit/e93e3b1e7d6e7e4f49c2de24b92452ec29e853f8), [#4158](https://github.com/morluto/rea/pull/4158), [#5025](https://github.com/morluto/rea/pull/5025), [#6981](https://github.com/morluto/rea/pull/6981), [#4537](https://github.com/morluto/rea/issues/4537), [#6397](https://github.com/morluto/rea/issues/6397). Thanks @Abelo9996, @BaaRai, @FerroLx, @gensokyo-dev, @sserrgo12.
- **Bytecode and firmware:** Inspect and query CPython pyc and BEAM facts, inspect flattened devicetrees, and attribute MMIO peripheral regions in native disassembly. [#4826](https://github.com/morluto/rea/pull/4826), [#6928](https://github.com/morluto/rea/pull/6928), [#4833](https://github.com/morluto/rea/pull/4833), [#6943](https://github.com/morluto/rea/pull/6943), [#7009](https://github.com/morluto/rea/pull/7009), [#5348](https://github.com/morluto/rea/pull/5348), [#6922](https://github.com/morluto/rea/issues/6922), [#6938](https://github.com/morluto/rea/issues/6938). Thanks @gensokyo-dev, @liziye627-design, @mvanhorn.
- **JavaScript:** Retain queryable static route inventories, link assignment values to writes, and accept source selections as a JSON array or file. [#6019](https://github.com/morluto/rea/pull/6019), [#6206](https://github.com/morluto/rea/pull/6206), [#5029](https://github.com/morluto/rea/pull/5029), [#4772](https://github.com/morluto/rea/issues/4772), [#6193](https://github.com/morluto/rea/issues/6193). Thanks @gensokyo-dev, @IvenKooLab, @jacobmalibiran, @JoshOsullivan-au, @kaoru0822-kitauji, @KJLavender.
- **Data inspection:** Decode selected protobuf/gRPC bodies, inspect GGUF/safetensors declarations, and produce byte-entropy profiles. [#5031](https://github.com/morluto/rea/pull/5031), [#5603](https://github.com/morluto/rea/pull/5603), [#2331](https://github.com/morluto/rea/pull/2331), [#4986](https://github.com/morluto/rea/issues/4986), [#3323](https://github.com/morluto/rea/issues/3323). Thanks @AayushJB03, @gensokyo-dev, @kaoru0822-kitauji, @KJLavender, @michaelkillgta, @mvanhorn.
- **CLI input/output:** Accept structured request documents on stdin and expose version/runtime output formats. Row-oriented JSONL exports use the migration above. [#7040](https://github.com/morluto/rea/pull/7040), [#5486](https://github.com/morluto/rea/issues/5486), [#6530](https://github.com/morluto/rea/pull/6530), [169512a](https://github.com/morluto/rea/commit/169512a35bb015ff209168fb8bc1c9b31a2c678b), [#5483](https://github.com/morluto/rea/issues/5483). Thanks @Abelo9996, @ahmed-husssain, @CassieFox23, @DeryFerd, @eco-null, @ezrabrilliant, @GamerDubz, @gensokyo-dev, @gongfpp, @hifzabuildsai, @jayykernel, @mhamzasakhi, @pablodiazjorge, @Plastic-time, @raidnouredden-ux, @SandstromPL, @ShuaixinHuang, @Sriram-PR, @XiaoBinGan.
- **MCP and setup:** Add opt-in Streamable HTTP and FreeBuff setup registration. FreeBuff support first appears in 6.8.0; installed native FreeBuff loading and skill discovery remain unverified. [#4955](https://github.com/morluto/rea/pull/4955), [#2736](https://github.com/morluto/rea/pull/2736), [#3243](https://github.com/morluto/rea/issues/3243). Thanks @0xLLVEL, @codebuff-team, @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
- **Recovery controls:** Adjust per-call WASM/SQLite/firmware deadlines, MCP startup budgets, regex matching deadlines/worker heap, capture decoder heap, ASAR header admission, Apple dispatch input, and the bridge request ceiling. MCP runtime limit recovery reports actionable settings. [#5798](https://github.com/morluto/rea/pull/5798), [#5818](https://github.com/morluto/rea/pull/5818), [aa53c06](https://github.com/morluto/rea/commit/aa53c065387c58b4189d82c47f1f3445fc505e30), [#5863](https://github.com/morluto/rea/pull/5863), [#6304](https://github.com/morluto/rea/pull/6304), [#6267](https://github.com/morluto/rea/pull/6267), [#6301](https://github.com/morluto/rea/pull/6301), [#6299](https://github.com/morluto/rea/pull/6299), [#6209](https://github.com/morluto/rea/pull/6209), [#6233](https://github.com/morluto/rea/pull/6233), [#6376](https://github.com/morluto/rea/pull/6376), [#7049](https://github.com/morluto/rea/pull/7049), [#5797](https://github.com/morluto/rea/issues/5797), [#5812](https://github.com/morluto/rea/issues/5812), [#5851](https://github.com/morluto/rea/issues/5851), [#6176](https://github.com/morluto/rea/issues/6176), [#3917](https://github.com/morluto/rea/issues/3917). Thanks @gensokyo-dev, @jaychou813, @kaoru0822-kitauji.
- **APK output:** Set `string_tables_only` or CLI `--string-tables-only` to omit duplicate primary strings and avoid their output-capacity charge. [7c458f0](https://github.com/morluto/rea/commit/7c458f04b9155707c8ba406f8b455b10016f1b99), [#4990](https://github.com/morluto/rea/pull/4990), [#4982](https://github.com/morluto/rea/issues/4982). Thanks @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0.
- **Retained analysis:** Reuse successful validation of authenticated immutable native/imported-JavaScript parents and captures. Query retained Go build identity, Rust dependencies, JavaScript source-to-bundle mappings, and native export facts without repeating full output. [8f7d12e](https://github.com/morluto/rea/commit/8f7d12e30d63f3d3b4cfe2a90336362841438f56), [#4962](https://github.com/morluto/rea/pull/4962), [#5349](https://github.com/morluto/rea/pull/5349), [#5524](https://github.com/morluto/rea/pull/5524), [#5815](https://github.com/morluto/rea/pull/5815), [#6128](https://github.com/morluto/rea/pull/6128), [#6907](https://github.com/morluto/rea/pull/6907), [#4810](https://github.com/morluto/rea/issues/4810), [#5499](https://github.com/morluto/rea/issues/5499), [#5802](https://github.com/morluto/rea/issues/5802), [#6112](https://github.com/morluto/rea/issues/6112), [#6900](https://github.com/morluto/rea/issues/6900). Thanks @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0.
- **Compact results:** Use retained Frida/GDB/objdump summaries, omit redundant Ghidra raw results, and defer the analysis runtime during MCP catalog discovery. [#5661](https://github.com/morluto/rea/pull/5661), [#5614](https://github.com/morluto/rea/pull/5614), [#5355](https://github.com/morluto/rea/pull/5355), [#4424](https://github.com/morluto/rea/pull/4424), [#5640](https://github.com/morluto/rea/issues/5640), [#5584](https://github.com/morluto/rea/issues/5584), [#5337](https://github.com/morluto/rea/issues/5337). Thanks @coygeek, @gensokyo-dev, @kaoru0822-kitauji, @leeguooooo.
- **Analysis work:** Reuse Hopper literal-string records, JavaScript workers and captured prefix selections, Interface Builder validation and plist scalar schemas; bound stable reads to one snapshot buffer and avoid decoding omitted GGUF strings. [#5017](https://github.com/morluto/rea/pull/5017), [#5819](https://github.com/morluto/rea/pull/5819), [31de177](https://github.com/morluto/rea/commit/31de177404461d122761d8243abe4fd24564f4da), [#5885](https://github.com/morluto/rea/pull/5885), [85e38ac](https://github.com/morluto/rea/commit/85e38ac199dff15e8c43343d9170db7e0e46d284), [#6966](https://github.com/morluto/rea/pull/6966), [#5781](https://github.com/morluto/rea/pull/5781), [#5010](https://github.com/morluto/rea/issues/5010), [#5855](https://github.com/morluto/rea/issues/5855), [#6954](https://github.com/morluto/rea/issues/6954), [#5774](https://github.com/morluto/rea/issues/5774). Thanks @gensokyo-dev, @kaoru0822-kitauji.
- **Native scheduling:** Batch modern IDA callee lookups, schedule decompilation reads on demand, index NativeAOT type closure, and retain call-path ancestry once per visited node. [0c6fed9](https://github.com/morluto/rea/commit/0c6fed90b74c75a9fd23d1ec1b510a7274f85887), [06fe7ee](https://github.com/morluto/rea/commit/06fe7eecbc8c387414b4c1aa0d88ad6314f54f0c), [ce61680](https://github.com/morluto/rea/commit/ce61680c04c95a9cdfad5335632a99542af26d37), [5e4ec0f](https://github.com/morluto/rea/commit/5e4ec0fa1b678aa3bf20704ec9110224ba8c1676).
- **Selected processing:** Prepare CLI positional schemas only for the selected command, separate entropy reads from report resolution, prune typed-array reference escapes, index state-write cutoffs, decode only selected replay query values, and skip payload text during WAT framing. [315b234](https://github.com/morluto/rea/commit/315b23403f0819967878cee418c3f9cb4b1a69d9), [#6950](https://github.com/morluto/rea/pull/6950), [#5139](https://github.com/morluto/rea/pull/5139), [#5350](https://github.com/morluto/rea/pull/5350), [96ea32b](https://github.com/morluto/rea/commit/96ea32b7e4e28d3c9cd79a1de4b8e1ba3b4244e9), [70835d3](https://github.com/morluto/rea/commit/70835d3d5f030deadf606e1c79c70a60883e50c9), [#5766](https://github.com/morluto/rea/pull/5766), [#6947](https://github.com/morluto/rea/issues/6947), [#5760](https://github.com/morluto/rea/issues/5760). Thanks @galleonli, @gensokyo-dev, @kaoru0822-kitauji.
### Fixes
- **IPS report fields:** Preserve the recorded terminating process and PID, termination flags, parent PID, and build/platform metadata. [#7079](https://github.com/morluto/rea/pull/7079), [#7078](https://github.com/morluto/rea/issues/7078). Thanks @rodricksz4h5.
- **Ghidra language selection:** Accept stock language IDs containing `LEBE` or slash-separated processor names, and report conflicting DOL language overrides as configuration errors naming `REA_GHIDRA_LANGUAGE_ID`. [#7070](https://github.com/morluto/rea/pull/7070), [#7072](https://github.com/morluto/rea/issues/7072), [#7081](https://github.com/morluto/rea/pull/7081), [#7082](https://github.com/morluto/rea/issues/7082). Thanks @Adolanium.
- **Setup and Hopper:** Preserve the Linux embedding runtime, supported x86_64 host restriction, readiness-based provider choices, orderly demo-helper shutdown, and installed apt dependency versions. Keep cleanup and skill publication ownership through retries. [#5041](https://github.com/morluto/rea/pull/5041), [#3422](https://github.com/morluto/rea/pull/3422), [f5d1012](https://github.com/morluto/rea/commit/f5d1012fabfe99af82523c3078ce9d93eb3780c1), [#4966](https://github.com/morluto/rea/pull/4966), [#6974](https://github.com/morluto/rea/pull/6974), [211e353](https://github.com/morluto/rea/commit/211e3533b34f81ae0910ad4f53f2a3d3c8587d3e), [#6920](https://github.com/morluto/rea/pull/6920), [#5015](https://github.com/morluto/rea/issues/5015), [#3419](https://github.com/morluto/rea/issues/3419), [#4921](https://github.com/morluto/rea/issues/4921), [#6975](https://github.com/morluto/rea/issues/6975). Thanks @Abel-I-Akhigbe, @Adolanium, @DavidFB-creator, @DeryFerd, @gensokyo-dev, @kaoru0822-kitauji, @sx4im.
- **Client setup:** Treat empty APPDATA as unset, distinguish MSIX Claude Desktop process views, preserve aliased Hermes server tables, reuse client dialect/alias bindings, and materialize the pinned package-runner cache before the first start. [#5126](https://github.com/morluto/rea/pull/5126), [#4903](https://github.com/morluto/rea/pull/4903), [#3785](https://github.com/morluto/rea/pull/3785), [b7eeded](https://github.com/morluto/rea/commit/b7eededf5af5061a834f93f57f2c888acf870344), [#7032](https://github.com/morluto/rea/pull/7032), [#5104](https://github.com/morluto/rea/issues/5104), [#4278](https://github.com/morluto/rea/issues/4278), [#3320](https://github.com/morluto/rea/issues/3320), [#7027](https://github.com/morluto/rea/issues/7027). Thanks @CmsChase, @coygeek, @myso-kr, @pablodiazjorge, @vaidehi-snk, @wxj006007.
- **Target admission:** Preserve thin arm64_32 Mach-O support, distinguish JVM identity before FAT admission, validate FAT format/CPU identity, preserve original DOS header padding, and reject NULs in local objdump/rizin paths. [#5788](https://github.com/morluto/rea/pull/5788), [6843dbe](https://github.com/morluto/rea/commit/6843dbea9bce4709ee2e8bf46c38f0bbf640716b), [497037e](https://github.com/morluto/rea/commit/497037e6e3975cbb37cfd89ea0ad300faa2a4769), [#7013](https://github.com/morluto/rea/pull/7013), [#3600](https://github.com/morluto/rea/pull/3600), [#5713](https://github.com/morluto/rea/issues/5713), [#6510](https://github.com/morluto/rea/issues/6510). Thanks @coygeek, @dhruvac29, @kaoru0822-kitauji, @Lew1sWong, @qorvr.
- **Mach-O and dylibs:** Preserve container framing, facts when dyld rejects a missing UUID, verified-signature snapshot identity, unsigned diagnostics, absolute paths inside the selected root, legacy platform rules, lazy-load payloads, and reasons for rejecting present dylibs. [cf3288d](https://github.com/morluto/rea/commit/cf3288d8cf7a09685acf89103b36c32359be56fa), [2b263a7](https://github.com/morluto/rea/commit/2b263a7fe9e9d4c185c7d6af6b4e67b21a73af30), [eb7acc7](https://github.com/morluto/rea/commit/eb7acc70bf06f119e8639e30a78081d5ba82b0bf), [1502cfb](https://github.com/morluto/rea/commit/1502cfbc8ca0d1d1d86eebf334018dfb418bbac0), [90062c0](https://github.com/morluto/rea/commit/90062c092228dea9d33fadc0d7e1b968105b8b2c), [#5650](https://github.com/morluto/rea/pull/5650), [3b67b36](https://github.com/morluto/rea/commit/3b67b362aa2cd79eac5af57d0965e81a7d0b8c2b), [#5551](https://github.com/morluto/rea/issues/5551). Thanks @coygeek.
- **Native facts:** Admit direct dispatch-metadata providers; preserve record-local Objective-C omissions, snapshot profiles, class-import binding identity, stripped class labels, version-like names, and qualified Swift protocol identities. [50b6975](https://github.com/morluto/rea/commit/50b6975d861069e2c6cfa202780621449f863802), [9eec57b](https://github.com/morluto/rea/commit/9eec57b469087fe9bff3d29dcc02f47074159e91), [0ba4d6f](https://github.com/morluto/rea/commit/0ba4d6f43ad610dacc861a1548be1a5c4712cd61), [4d7de00](https://github.com/morluto/rea/commit/4d7de00d4cc9a95ec2b7a3123b04e01db2daafa0), [22c84ce](https://github.com/morluto/rea/commit/22c84ce64aa6c7601aa81af86d4117b5a4c2feac), [4852c83](https://github.com/morluto/rea/commit/4852c830af1e3b9df437235b7a8fa8970a6d1c59), [aa9043f](https://github.com/morluto/rea/commit/aa9043f6b61c453e4735795cfe6a559bf6b6cc16).
- **Apple resources:** Preserve independent plist scalars, processing instructions, nonstandard table widths, unsupported NIB connector coverage, and strict IPS JSON surrogate validation; classify Interface Builder objects by their declared superclass chains. [fd728f2](https://github.com/morluto/rea/commit/fd728f271e7de7bcfa63890b573715720d5f948c), [#5806](https://github.com/morluto/rea/pull/5806), [4526862](https://github.com/morluto/rea/commit/4526862728af01f1135714079c0d8a8c754eb364), [14cd78c](https://github.com/morluto/rea/commit/14cd78c3224edcf79fca152915077112f547b9ed), [#6990](https://github.com/morluto/rea/pull/6990), [#5805](https://github.com/morluto/rea/issues/5805), [#5228](https://github.com/morluto/rea/pull/5228), [#5218](https://github.com/morluto/rea/issues/5218). Thanks @coygeek, @DeryFerd, @kaoru0822-kitauji, @Mangolycheematcha, @mhamzasakhi, @Shreyash021104.
- **Archives and files:** Complete positional ZIP reads, preserve bounded ASAR signatures, report stable symlink loops as resolution failures, retain target applicability, and report unknown traversal coverage. Retain reported integrity contradictions for members present in only one compared artifact. [#4414](https://github.com/morluto/rea/pull/4414), [e8da7e1](https://github.com/morluto/rea/commit/e8da7e1edd075feec0e6beedec93d5645fed8125), [#5571](https://github.com/morluto/rea/pull/5571), [#5763](https://github.com/morluto/rea/pull/5763), [#5867](https://github.com/morluto/rea/pull/5867), [#5521](https://github.com/morluto/rea/issues/5521), [#5731](https://github.com/morluto/rea/issues/5731), [#5837](https://github.com/morluto/rea/issues/5837), [#7076](https://github.com/morluto/rea/pull/7076). Thanks @Afloat16, @coygeek, @jdotc1, @kaoru0822-kitauji, @WenyuChiou.
- **Extraction and file ownership:** Restrict destination-case recovery to owned collisions, retain physical-read failures through late cancellation/cleanup, preserve cleanup capabilities for retry, and retain independent firmware extraction failures. [932fbb7](https://github.com/morluto/rea/commit/932fbb7d84a9a995b9aee3c1be7da05a8c4d7bbb), [9499667](https://github.com/morluto/rea/commit/949966712e68d8857a50a165ecddf04c2d7e6d3b), [56679dd](https://github.com/morluto/rea/commit/56679dd2ae31dda58e181f8bf99775014f7bb82b), [b3c912a](https://github.com/morluto/rea/commit/b3c912ab79ad147a37b6cfbe1552a1a5eecec629), [7298bd0](https://github.com/morluto/rea/commit/7298bd08d134860b84b4073f6703021916a5a4c5).
- **Processes and cancellation:** Retain partial samples and shutdown gaps, unresolved descendants and caller abort reasons; bind cleanup errors to their owners, propagate comparison contradictions, and report no normal exit code for signalled targets. [ba866a2](https://github.com/morluto/rea/commit/ba866a26ecbe179a717e746cbc0d1605ae616ad5), [#5270](https://github.com/morluto/rea/pull/5270), [#6144](https://github.com/morluto/rea/pull/6144), [a721c42](https://github.com/morluto/rea/commit/a721c42c07dc674c32ab939627355b8a1bee97a5), [#6298](https://github.com/morluto/rea/pull/6298), [#6486](https://github.com/morluto/rea/pull/6486), [#5935](https://github.com/morluto/rea/issues/5935), [#5944](https://github.com/morluto/rea/issues/5944), [#6100](https://github.com/morluto/rea/issues/6100), [#6101](https://github.com/morluto/rea/issues/6101), [#6110](https://github.com/morluto/rea/issues/6110), [#6113](https://github.com/morluto/rea/issues/6113), [#6121](https://github.com/morluto/rea/issues/6121), [#6131](https://github.com/morluto/rea/issues/6131), [#6132](https://github.com/morluto/rea/issues/6132), [#6218](https://github.com/morluto/rea/issues/6218), [#6445](https://github.com/morluto/rea/issues/6445). Thanks @agorevski, @coygeek, @gongfpp, @kwakhyun, @mhamzasakhi.
- **Provider cleanup:** Preserve Windows native handles until close is confirmed, native/Inspector/Electron cleanup ownership, Kuna probe identity and cleanup, IDA registration configuration, and Hopper preparation ownership. [#5772](https://github.com/morluto/rea/pull/5772), [87ab054](https://github.com/morluto/rea/commit/87ab054ea384b3fcbae2998b1f94685edcd5f624), [ff78165](https://github.com/morluto/rea/commit/ff78165978de9dbcf9fa16c2acf74bf85a69dc44), [04ae07a](https://github.com/morluto/rea/commit/04ae07ab8c9ae1a6697b6ad15489bbde515da8b5), [dd0015a](https://github.com/morluto/rea/commit/dd0015aaf156c5701ef9598bcc6c885e6b8da803), [b231c28](https://github.com/morluto/rea/commit/b231c28cf64c2f799ada99343f8f21e0e6acb15e), [#4505](https://github.com/morluto/rea/issues/4505), [#5616](https://github.com/morluto/rea/issues/5616).
- **Provider diagnostics:** Give Ghidra restart recovery, stable Homebrew aliases, unsupported-host results for Linux-only integrations, correct IDA regex/readiness handling, and clear diagnostics when Windows refuses a verified native addon. [#5356](https://github.com/morluto/rea/pull/5356), [7c6142e](https://github.com/morluto/rea/commit/7c6142efdc19a42c148d0e8c96b69d18e63c7f87), [#5793](https://github.com/morluto/rea/pull/5793), [#4862](https://github.com/morluto/rea/pull/4862), [99ccfb6](https://github.com/morluto/rea/commit/99ccfb6e709ac4bc0afe176315280abde9ebcb9f), [#6832](https://github.com/morluto/rea/pull/6832), [#5730](https://github.com/morluto/rea/issues/5730), [#4860](https://github.com/morluto/rea/issues/4860), [#5723](https://github.com/morluto/rea/issues/5723). Thanks @coygeek, @dark13b, @gensokyo-dev, @jgt87, @jpmorris41488, @Lew1sWong.
- **MCP results:** Preserve protocol envelopes on oversized errors, enforce and advertise absolute Evidence paths, retain typed output-filter errors and selected JSON paths, and give client/provider timeout recovery. Compact advertisements now normalize reported enum and union shapes for strict gateway translation. [562dab0](https://github.com/morluto/rea/commit/562dab00c5cbf76a9b2695bad93792eccdc9a49d), [3cf6206](https://github.com/morluto/rea/commit/3cf6206614e65b5ebb6284a5709090dfb8f9aaee), [0e83d92](https://github.com/morluto/rea/commit/0e83d92c583ae36afe00b2c23dd2a0f1d025cbca), [4714c7e](https://github.com/morluto/rea/commit/4714c7eaa3d1d8b6a5df0aff586e844b9d9ab06d), [1bfa60a](https://github.com/morluto/rea/commit/1bfa60ac20e1a344bcf3132c08d15b6860b05c61), [#5608](https://github.com/morluto/rea/pull/5608), [#3806](https://github.com/morluto/rea/pull/3806), [#5598](https://github.com/morluto/rea/issues/5598), [#3800](https://github.com/morluto/rea/issues/3800). Thanks @gensokyo-dev, @Hahaa909, @wang1xiang.
- **Browser lifecycles:** Retain historical outcomes through cleanup, stop late navigation before session release, stop expired work, observe aborted fetch settlement, and mark later scenario actions skipped after failure or cancellation. [2e5171e](https://github.com/morluto/rea/commit/2e5171e391b83f8a467d0861fd452c1f78621fd8), [599fdac](https://github.com/morluto/rea/commit/599fdac029912f96c511f859660483405d2de26f), [b2fad36](https://github.com/morluto/rea/commit/b2fad362f2b37f81dc9a6f784f4521e4ca24d41e), [13a8640](https://github.com/morluto/rea/commit/13a864020414577006dce58d7fdaa877ddd0cb52), [#6023](https://github.com/morluto/rea/pull/6023), [#5943](https://github.com/morluto/rea/issues/5943). Thanks @coygeek, @kaoru0822-kitauji.
- **Browser comparisons:** Preserve frame identity, default origin scope, unknown body/shape coverage, correspondence and uncertainty, and independent retained-observation budgets. [#4040](https://github.com/morluto/rea/pull/4040), [#4413](https://github.com/morluto/rea/pull/4413), [#4476](https://github.com/morluto/rea/pull/4476), [a0c22ff](https://github.com/morluto/rea/commit/a0c22ff0125b8af7612db54d3708413e2bfecb25), [a0fcc60](https://github.com/morluto/rea/commit/a0fcc60d89eb0cc09b08181eb9f961acfd07d8e9), [#3878](https://github.com/morluto/rea/issues/3878), [#3910](https://github.com/morluto/rea/issues/3910), [#3941](https://github.com/morluto/rea/issues/3941), [#4385](https://github.com/morluto/rea/issues/4385), [#4477](https://github.com/morluto/rea/issues/4477). Thanks @Adolanium, @coygeek, @eco-null, @gensokyo-dev, @kaoru0822-kitauji, @XY-code1.
- **Electron observations:** Preserve listener callback ownership and count registrations by source callsite; block trash calls in the active hook, retain window handles through cancelled selection, and preserve cancellation during finalization. [dfe8378](https://github.com/morluto/rea/commit/dfe8378cf5a727e3d93d47a5986233175c6ef898), [5064007](https://github.com/morluto/rea/commit/506400748eb1d6c674f0cf51330073929c45f939), [816a9dc](https://github.com/morluto/rea/commit/816a9dc98886e6d824cd8ae88b68731b161d5dda), [1707d17](https://github.com/morluto/rea/commit/1707d171d750955bf6387e028e378183b360ffe3), [4e2a566](https://github.com/morluto/rea/commit/4e2a566d6c7e053ecbbee1b684311ed31ea163bf).
- **JavaScript source maps:** Keep unmapped and missing mappings unknown, preserve partial indexed-map facts and section paths, propagate preparation capability errors, and retain observed worker failures through cancellation. [a4dbb36](https://github.com/morluto/rea/commit/a4dbb36d8ea83e9f94dd289ac1a2d220d2c72c9b), [2b2e362](https://github.com/morluto/rea/commit/2b2e362a00fc5ba2f93fa6c62e2d8780072d1b4b), [498a667](https://github.com/morluto/rea/commit/498a667234b0140c1cfa43c819b35454ab11d14e), [#6149](https://github.com/morluto/rea/pull/6149), [#6214](https://github.com/morluto/rea/pull/6214), [f01dcca](https://github.com/morluto/rea/commit/f01dccab77f712fb4ac936d0a6e92a70d12f0789), [#6082](https://github.com/morluto/rea/issues/6082), [#6061](https://github.com/morluto/rea/issues/6061). Thanks @coygeek, @diyasharma05, @kaoru0822-kitauji.
- **JavaScript semantics:** Preserve overflowing timer uncertainty, unavailable-scope comparisons, directory-form URL/ESM path intent, and artifact confinement. Replaced globals lose built-in effects; erased imports no longer become runtime bindings. Treat a known truthy `fork()` `silent` option as piped stdio unless a string/array `stdio` override takes precedence; unresolved options stay dynamic. [#3456](https://github.com/morluto/rea/pull/3456), [8d198d6](https://github.com/morluto/rea/commit/8d198d6ec707402d75276f516e0090d71767b7e4), [#4646](https://github.com/morluto/rea/pull/4646), [#2700](https://github.com/morluto/rea/pull/2700), [#4565](https://github.com/morluto/rea/pull/4565), [#6994](https://github.com/morluto/rea/pull/6994), [#3400](https://github.com/morluto/rea/issues/3400), [#3882](https://github.com/morluto/rea/issues/3882), [#3367](https://github.com/morluto/rea/issues/3367), [#7083](https://github.com/morluto/rea/pull/7083), [#7084](https://github.com/morluto/rea/issues/7084). Thanks @Adolanium, @amyanger, @coygeek, @gensokyo-dev, @HawkForge, @rohitg00, @viniciusdsandrade.
- **JavaScript routes and imports:** Treat path data as route candidates, preserve route flag and lexical RPC identities, package-main trailing-slash selection, catch aliases, and factory parameter ownership. [#6360](https://github.com/morluto/rea/pull/6360), [38ecc02](https://github.com/morluto/rea/commit/38ecc0248e1993d25a2f7dd350024484cce797bb), [#4219](https://github.com/morluto/rea/pull/4219), [21097b1](https://github.com/morluto/rea/commit/21097b192b1ab94647e3446b1a2a4dd00e924d5e), [f774dc4](https://github.com/morluto/rea/commit/f774dc420b3befe2b8e68971b4116da2d62fc800), [#6349](https://github.com/morluto/rea/issues/6349), [#4189](https://github.com/morluto/rea/issues/4189). Thanks @coygeek, @hemv-857, @kaoru0822-kitauji, @Nikhi00718.
- **Managed/IL2CPP:** Preserve extension-only scope uncertainty and type discovery after member limits, nested names, ReadyToRun/declaration facts, selected type scope, valid shared-suffix strings, and truncated IL2CPP metadata prefixes. [#5674](https://github.com/morluto/rea/pull/5674), [#5312](https://github.com/morluto/rea/pull/5312), [87fa1b6](https://github.com/morluto/rea/commit/87fa1b613d2b6bda1722855a00f1390f27731b64), [bc2d494](https://github.com/morluto/rea/commit/bc2d494030efd1ccabaeaa0bd49226aa87c378a2), [1f0eb22](https://github.com/morluto/rea/commit/1f0eb228e50908ee2a2aaa72d329178ae1a2cdbd), [4c5fae5](https://github.com/morluto/rea/commit/4c5fae5179dd54c4a0d427cd2a115b9c887e3841), [b3aadaf](https://github.com/morluto/rea/commit/b3aadaf474e59b33e5ff465236008a67eccefb87), [#5467](https://github.com/morluto/rea/issues/5467), [#5146](https://github.com/morluto/rea/issues/5146). Thanks @coygeek, @eco-null, @elricfd, @umi008.
- **Format boundaries:** Preserve independent facts across malformed inputs in model, GPU, WASM, Rust, Android, and SQLite inspection; report optional metadata warnings without discarding core results. [eb41f35](https://github.com/morluto/rea/commit/eb41f358cf83d5626f09de68332f9718f9aa6482), [#5789](https://github.com/morluto/rea/pull/5789), [00a9e95](https://github.com/morluto/rea/commit/00a9e95de91a4bd4d2a41f8aee704a53fa575750), [fc63fe8](https://github.com/morluto/rea/commit/fc63fe8682654086261540dcf3c59afc36c7d58f), [39a91d4](https://github.com/morluto/rea/commit/39a91d4db96ca13224c25936330ea84fe78970e5), [6c6e2ad](https://github.com/morluto/rea/commit/6c6e2ad62b20f810b54f5a567e66fc55241514b2), [929a0a7](https://github.com/morluto/rea/commit/929a0a76c870d9991e015dde58308fd9df0280b8), [d137a4c](https://github.com/morluto/rea/commit/d137a4cdde0049de967f1d5cd429ed9e1f7d0a10), [ef475fc](https://github.com/morluto/rea/commit/ef475fcf8b0601c61052b7091568ba574546417d), [#5749](https://github.com/morluto/rea/issues/5749). Thanks @coygeek, @gensokyo-dev.
- **Capture decoding:** Preserve malformed protobuf trailer facts and wire/schema identity, HTTP/2 URI and UTF-8 failure facts, WebMCP property coverage, and offline capture availability without a browser provider. Report invalid record ordinals as selection errors with actual bounds where available, and trim surrounding SP/HTAB from gRPC-web trailer values while preserving internal whitespace. [9cf7a7c](https://github.com/morluto/rea/commit/9cf7a7cb0d3c72a1121fde280cb11bf911af8055), [b3c3b21](https://github.com/morluto/rea/commit/b3c3b21e43cab4e71b460d08829d8c6e4e9b9c21), [4cb1f1d](https://github.com/morluto/rea/commit/4cb1f1d820df4498ee464bcf72a5f47d40cf8dab), [51e5938](https://github.com/morluto/rea/commit/51e593877642ab42fb5bf25812f4a4110ee7ac26), [d23be5c](https://github.com/morluto/rea/commit/d23be5c0898c39c9f0c54e2e0b43ee746d92d316), [#6725](https://github.com/morluto/rea/pull/6725), [#3947](https://github.com/morluto/rea/issues/3947), [#7067](https://github.com/morluto/rea/pull/7067), [#7068](https://github.com/morluto/rea/issues/7068), [#7069](https://github.com/morluto/rea/pull/7069), [#7071](https://github.com/morluto/rea/issues/7071). Thanks @Adolanium, @coygeek, @Sriram-PR.
- **Evidence and errors:** Authenticate ledger Evidence before committing; preserve parent-schema diagnostics, canonical correction details and owned cleanup outcomes; identify the rejected function-Evidence side/kind. Fixed allowances now name their actual limits. [0407bbf](https://github.com/morluto/rea/commit/0407bbfbe65e8fa475e8bb0c78c30170e2c2f55e), [#6361](https://github.com/morluto/rea/pull/6361), [1629266](https://github.com/morluto/rea/commit/1629266e3c08c90fed6bba5383528af7986a12b7), [#6969](https://github.com/morluto/rea/pull/6969), [#5860](https://github.com/morluto/rea/pull/5860), [#6034](https://github.com/morluto/rea/issues/6034), [#5932](https://github.com/morluto/rea/issues/5932), [#5847](https://github.com/morluto/rea/issues/5847). Thanks @AyoubElleuch, @coygeek, @dhruvac29, @kaoru0822-kitauji.
- **ADB and firmware recovery:** Report an unparsed push checksum as unverified, normalize absent installer-package sentinels, describe extraction-relative Unblob links, and avoid recommending a budget increase at its fixed ceiling. [#6010](https://github.com/morluto/rea/pull/6010), [#4073](https://github.com/morluto/rea/pull/4073), [#4573](https://github.com/morluto/rea/pull/4573), [#6971](https://github.com/morluto/rea/pull/6971), [#5924](https://github.com/morluto/rea/issues/5924), [#4175](https://github.com/morluto/rea/issues/4175). Thanks @coygeek, @Ganesh2325, @kaoru0822-kitauji, @Rinkia, @XiaoBinGan, @yankikucuk.
- **CLI formatting:** Share Markdown report formatting, exclude inherited row values, and preserve arrays of empty objects as JSON. Render Markdown table headers and cells literally while keeping intraword underscores readable. [b784912](https://github.com/morluto/rea/commit/b784912811fd10ac9dc425696dc0e9af632afa34), [#6984](https://github.com/morluto/rea/pull/6984), [#7058](https://github.com/morluto/rea/pull/7058), [#7057](https://github.com/morluto/rea/issues/7057), [#7073](https://github.com/morluto/rea/pull/7073). Thanks @Plastic-time, @ShuaixinHuang, @viniciusdsandrade.
- **Format identity:** Preserve Python marshal scalar/decode limits, empty optional BEAM chunks, PE resource branch identities and certificate alignment, unsupported compressed ELF notes, and codec-framed GPU bundle extents. [0603223](https://github.com/morluto/rea/commit/06032237c6755e3dd5f9a8db732c89958b238cc7), [c46064c](https://github.com/morluto/rea/commit/c46064c3f6eaba121c5de7b17390bfdecdd132ef), [66671e8](https://github.com/morluto/rea/commit/66671e874888e98f7e2b27e68a7c6fb320bbb827), [03077d5](https://github.com/morluto/rea/commit/03077d54afc49613db4e38f70c506bc60f5223aa), [#6721](https://github.com/morluto/rea/pull/6721), [9fb8c11](https://github.com/morluto/rea/commit/9fb8c114a03ad60e57c11586967c6df845a3f97e), [f6a1e2c](https://github.com/morluto/rea/commit/f6a1e2c892dc23c7583c3d03af9edb01632ff5a5), [#6694](https://github.com/morluto/rea/issues/6694). Thanks @coygeek, @Deepak-Kambala.
- **WASM and APK metadata:** Preserve ambiguous WASM identity and partial facts, glue URL query/fragment text across line terminators, APK signer identity, and raw issuer identity when comparison rules are unknown. [af8f0ba](https://github.com/morluto/rea/commit/af8f0badf64fd3546392b6aa4625777904b2f051), [#6802](https://github.com/morluto/rea/pull/6802), [f783f0f](https://github.com/morluto/rea/commit/f783f0f1e82472cdddd4de2156d974f729e332a6), [115a78f](https://github.com/morluto/rea/commit/115a78fe241276c34917b496621954c5f29bfc15), [#6639](https://github.com/morluto/rea/issues/6639). Thanks @coygeek, @gensokyo-dev, @gongfpp.
- **Source identity:** Preserve Next route spelling, module grammar and loader request identity, directory package-main behavior, accessor effects, exact source targets, and resolver-discovery versus module-loading distinctions. [ddd7ac7](https://github.com/morluto/rea/commit/ddd7ac7b846b48191ead1f7dcd0e3bd735468fa8), [9974512](https://github.com/morluto/rea/commit/99745126009b15060d71c38809418858a07d5afa), [f2e5fd1](https://github.com/morluto/rea/commit/f2e5fd148a85fa737302dd194a33172c4561093e), [baf1678](https://github.com/morluto/rea/commit/baf16784af3592534687052431c145daef55cfd1), [6e324b2](https://github.com/morluto/rea/commit/6e324b224405f2befd953234f6dc210cf72ad50a), [75daf0c](https://github.com/morluto/rea/commit/75daf0c7df108f04ab41f34aec1a31d4a7ceb322), [f91b7f8](https://github.com/morluto/rea/commit/f91b7f8009c9af27a5e0148e3b07d20bfe3b7a2a).
- **Graphs and selected facts:** Preserve JavaScript graph omissions/version facts, Electron native-loader facts, web JSON identity/coverage, module-path selection, feature-trace qualifications, selected native paths, endpoint context, and mixed Swift demangler outcomes. [30db499](https://github.com/morluto/rea/commit/30db499db310f073d9bf3d3acfd77985e1cb7e2e), [5a7bddb](https://github.com/morluto/rea/commit/5a7bddb3aeb0345854e76b79d529aed1c40e2054), [79805f2](https://github.com/morluto/rea/commit/79805f244a1ec0850f3109929168a9c8ebb1b908), [f719dc4](https://github.com/morluto/rea/commit/f719dc46c51a55b3926eec9659d06e7dfb95132b), [7784fdb](https://github.com/morluto/rea/commit/7784fdbad78f8b384d513f0e7dae8e983cd48ef8), [76500f1](https://github.com/morluto/rea/commit/76500f12b54e07b1ea700a403b2593890d63b42a), [676d29f](https://github.com/morluto/rea/commit/676d29f9eef794cea668f5f09f0ff510c848a513), [edf7b3d](https://github.com/morluto/rea/commit/edf7b3d32c3248f221e27f7ce5a3d34e8590285b), [93de7bc](https://github.com/morluto/rea/commit/93de7bc7e324a761162a31625b838ba0d4e3824d).
- **Managed boundaries:** Honor native platform symbol lookup, consume resolved IL2CPP ELF declarations, and attribute malformed comparison inputs to the supplied argument. [64fa1db](https://github.com/morluto/rea/commit/64fa1db1309218283df19f3ee4dba61f5fbd2a67), [87745af](https://github.com/morluto/rea/commit/87745af52ab0ccd3a172c2534c516d41b0095035), [96ad94f](https://github.com/morluto/rea/commit/96ad94f69eda5d68c7335ff775b6927b1376fd2b).
- **Provider and worker recovery:** Preserve output loss after successful command exit, physical refusal diagnostics, bounded worker control framing, JavaScript worker-phase/budget attribution, admitted ambiguity choices, and selected EVM limiter availability. Scope objdump debug-link limitations to DWARF requests and keep process-capture preparation nonexecuting. [e639edf](https://github.com/morluto/rea/commit/e639edf6204c05285d96840478926592daf95876), [171b33b](https://github.com/morluto/rea/commit/171b33b2c4defc6cbea49545bedf4272b72d1ea2), [34563fc](https://github.com/morluto/rea/commit/34563fcc9bd92af30668820dcac5635e27181094), [3f9357b](https://github.com/morluto/rea/commit/3f9357b5990d166a38440f56f439452266650093), [cec8f1e](https://github.com/morluto/rea/commit/cec8f1e8f305d6332272457a6ee419d3c5fb6359), [a74d859](https://github.com/morluto/rea/commit/a74d859a19122fb8f1282b3632d320997b1caae0), [c284168](https://github.com/morluto/rea/commit/c284168c4b1cd214ce7f504d43da28528d82d603), [4fb936f](https://github.com/morluto/rea/commit/4fb936fe86bb7a07f94f819ae8510301f2ef69c9).
- **Setup and limits:** Retain unknown environment-budget facts, selected-skill/maintenance uncertainty, Hopper URL admission and bookmark verification, and listener failure recovery that matches the system cause. [a718037](https://github.com/morluto/rea/commit/a7180370332350bc0f998fbd643888221759570e), [d1fbebb](https://github.com/morluto/rea/commit/d1fbebb578538dc94fd5e376e6e1959218efa835), [60accad](https://github.com/morluto/rea/commit/60accad2de8f2862fe91901502fe26cc5390fb36), [fd7da77](https://github.com/morluto/rea/commit/fd7da77fc02dcfaf7d1bc9f2fb240d71cdd27c59), [0c440bf](https://github.com/morluto/rea/commit/0c440bfc07f3cb343e2e836d27cccaed539cfe9c).
- **State and Evidence:** Retain export tarball ownership and SQLite primary outcomes through cleanup retry, count emitted SQLite schema names across encodings, scope display refresh to matching snapshot bindings, and report retained-result validation failures consistently. Reconstruction uses one effective completion scope and kind-based labels when names are blank. [ab58edc](https://github.com/morluto/rea/commit/ab58edcdf50b4bd687900ee2dc362ecc5c3dce15), [1006714](https://github.com/morluto/rea/commit/100671427105791a5c5913a918b63a394f6bc83e), [a47788a](https://github.com/morluto/rea/commit/a47788a6a4abb067273f954275a9d0a269adf1e7), [8aa40e9](https://github.com/morluto/rea/commit/8aa40e9a61155cff8b6f3c138c601bf805b20653), [b8c1b45](https://github.com/morluto/rea/commit/b8c1b45316882beeab4a9de49381d94e131fab0f), [a35b933](https://github.com/morluto/rea/commit/a35b9335a0124674ee5e3abe8c075f1fff6b4fa6), [#6137](https://github.com/morluto/rea/pull/6137), [#6126](https://github.com/morluto/rea/issues/6126). Thanks @carlogiovannineri-rgb, @mohithhhh.
- **Capture coverage:** Name browser bundle budgets and deadline recovery, separate domain readiness from worker attribution, attribute rendered-frame omissions to their producing stage, share offline-capture host admission with discovery, preserve selected signal-file semantics, and retain short Flutter GNU build IDs. [#6036](https://github.com/morluto/rea/pull/6036), [ba78133](https://github.com/morluto/rea/commit/ba781330d6d28b22d15498e63c144fb40e086156), [5934387](https://github.com/morluto/rea/commit/5934387f4185129efe9dc8b7338d8bc3fa5b25bd), [ad629b2](https://github.com/morluto/rea/commit/ad629b2e31c3a9495b27179360b508fa021fe86a), [928ba76](https://github.com/morluto/rea/commit/928ba76882bfd216dde8ed79118789b2c3aad4f9), [678c4cf](https://github.com/morluto/rea/commit/678c4cfa68e2fee12f620075e12dc87d9b1195b0).
- **Additional boundary facts:** Retain parser format/query occurrence identity and independent malformed-input facts, unresolved nested Swift identifiers, and completed signing-capture coverage; describe crash documents without assuming a provider. [5f702dd](https://github.com/morluto/rea/commit/5f702dd6ccd9e8e505e224854b91678439c3b41f), [99d2d7a](https://github.com/morluto/rea/commit/99d2d7ad22bcbd780919760008aba6a88e7d1077), [90e433d](https://github.com/morluto/rea/commit/90e433df06b3f83417176b8db1b0a905f9df1d3a), [#5548](https://github.com/morluto/rea/pull/5548), [ee12d51](https://github.com/morluto/rea/commit/ee12d517a25d34853d40e31fc039c7d1576c60e1), [#5538](https://github.com/morluto/rea/issues/5538). Thanks @coygeek, @ShuaixinHuang.
- **Documentation:** Add the Romanian README. [#7026](https://github.com/morluto/rea/pull/7026). Thanks @An3treiu.
Full contribution record: [1740 commits and 497 merged PRs](https://github.com/morluto/rea/blob/rea-agents-6.8.0/docs/releases/6.8.0.contributions.json).
## [6.7.0](https://github.com/morluto/rea/compare/rea-agents-6.6.0...rea-agents-6.7.0) (2026-10-18)
### Highlights
@@ -180,7 +78,7 @@ Full contribution record: [4 commits and 1 merged PR](https://github.com/morluto
### ⚠ BREAKING CHANGES
- **Saved Evidence:** Evidence and analysis snapshots now require explicit nullable `relationship_coverage` in their identity commitments. Earlier records whose digest omitted null coverage are incompatible even when the serialized field is explicitly null. Artifact inventories require scoped coverage and explicit `integrity_contradictions`; obsolete `coverage_status` and prose-based coverage inference are removed. Regenerate old Evidence/snapshots and inventory observations with this release: saved files are not migrated and omitted facts are not filled with guessed defaults. ([db8128d](https://github.com/morluto/rea/commit/db8128dc0d1c4f731f3d19877fa00d25cf17f657), [0405c9d](https://github.com/morluto/rea/commit/0405c9d361de6d904dfa16cc0b6d8350407f2c9b), [a9588be](https://github.com/morluto/rea/commit/a9588be4ebdd20eeda975cb63ffcdb1cd868b340)).
- **Saved Evidence:** Evidence and analysis snapshots now require explicit nullable `relationship_coverage` in their identity commitments. Artifact inventories require scoped coverage and explicit `integrity_contradictions`; obsolete `coverage_status` and prose-based coverage inference are removed. Regenerate old Evidence/snapshots and inventory observations with this release: saved files are not migrated and omitted facts are not filled with guessed defaults. ([db8128d](https://github.com/morluto/rea/commit/db8128dc0d1c4f731f3d19877fa00d25cf17f657), [0405c9d](https://github.com/morluto/rea/commit/0405c9d361de6d904dfa16cc0b6d8350407f2c9b), [a9588be](https://github.com/morluto/rea/commit/a9588be4ebdd20eeda975cb63ffcdb1cd868b340)).
- **Browser capture results:** Consumers must retain resolved capture limits, event families and omission facts. Set desired budgets through the supported capture options; omitted caller inputs still select defaults, but stored observations must report the resolved values. Admitted content reads queue behind bounded concurrency. ([57a23f0](https://github.com/morluto/rea/commit/57a23f0a2da0fdc3835e203718ded8acea5a34e6)).
- **Android version codes:** `inspect_adb_package.version_code` is a decimal string rather than a JSON number. Read it as a string, or use `BigInt` for numeric comparison. ([#3485](https://github.com/morluto/rea/pull/3485), [morluto/rea#3368](https://github.com/morluto/rea/issues/3368), [7666a48](https://github.com/morluto/rea/commit/7666a482b69dc7cabdbac3bbaf89a43e32300192)). Thanks @coygeek, @kaoru0822-kitauji.
- **Native UI failures:** Failed or cancelled native UI scenarios now return typed errors with the collected report in `partial_observation`. Handle the error path and read that report instead of expecting a successful result containing failed steps. ([d909919](https://github.com/morluto/rea/commit/d9099190ab3a819bd3a9fd62a8714cef398016d5)).
+7 -56
View File
@@ -10,33 +10,13 @@ and maintenance cost. Keep cosmetic polish with useful fixes; confusing or
incorrect user instructions warrant standalone corrections. Collect other
cosmetic-only suggestions in an issue rather than separate PRs.
For selected architectural owners and dependency paths, use the
[architecture map](docs/architecture.mermaid). It is not a complete directory or
import inventory. For current provider wiring, start with
[`src/composition/binary.ts`](src/composition/binary.ts) for deep candidates and
the other `src/composition/` factories for tool services, including
[`gpu.ts`](src/composition/gpu.ts) and [`model.ts`](src/composition/model.ts).
Live Frida and Cutter adapters are constructed directly in
[`src/cli/fridaCommands.ts`](src/cli/fridaCommands.ts),
[`src/cli/cutterCommands.ts`](src/cli/cutterCommands.ts), and
[`src/server/createServer.ts`](src/server/createServer.ts).
Production factories construct fresh providers without starting engines or
acquiring targets; optional adapter failures must preserve successful peers. Run
For capability organization and provider composition, use the
[architecture map](docs/architecture.mermaid). Production factories in
`src/composition/` construct fresh providers without starting engines or acquiring
targets; optional adapter failures must preserve successful peers. Run
`npm run verify:test-discovery` after adding or moving tests. Resolved import
boundaries are checked by `npm run verify:module-boundaries`, included in lint.
Before retiring an alias or facade, trace runtime registration, dynamic imports,
JavaScript verifiers, subprocess scripts, and packaged entrypoints as well as
TypeScript references. Tests may be the last consumers of a superseded internal
path; migrate their distinct assertions to the current production path before
removing it. Preserve validation, lazy loading, caller policy, and resource
acquisition and cleanup supplied by the old boundary. Published APIs, persisted
formats, and independently versioned bridges require an explicit compatibility
decision; an internal representation alone does not establish that obligation.
See [interpreting boundary facts once](docs/tool-design.md#interpret-boundary-facts-once)
for admission ownership and the distinction between repeated validation and
independent checks.
For analysis workflows and agent interfaces, follow the
[performance priorities](AGENTS.md#performance). Explain the selected output
scope, reuse/cache decisions, and relevant before/after measurements when these
@@ -46,10 +26,6 @@ to locate provider and shared-workflow bottlenecks, preserve Evidence, and
compare equivalent real tasks before claiming an improvement.
When adding or changing an MCP tool, follow the [tool design guide](docs/tool-design.md) and preserve the canonical contracts and generated catalog.
When adding or changing an admission ceiling, budget, or deadline, follow
[analysis settings, limits, and recovery](docs/design/limits-and-recovery.md)
for ownership, operation snapshots, provider integration, and the steps for
adding a runtime setting.
## Reporting issues
@@ -103,11 +79,7 @@ Human ownership and review remain required when CI or an automated reviewer
reports success. Readiness requests maintainer review; it does not grant scope
approval or permission to merge.
Draft status is optional. Draft PRs may request early feedback with open questions
and incomplete checks. Agents preserve the current draft status unless asked to
change it; missing readiness declarations or later updates do not require a PR
to become a draft.
Draft PRs may request early feedback with open questions and incomplete checks.
Leave the readiness declaration unchecked until the human owner has completed
it. The declaration records initial readiness; later pushes do not invalidate
it or require another human sign-off. Keep the PR description and affected
@@ -267,19 +239,6 @@ MinGW-w64, and `tar`; the build script does not run on Windows. See that guide
for the build and artifact-verification commands and the separate real Windows
verification prerequisites. Keep the generated addon and manifest uncommitted.
The general source suite (`npm run test:fast`, `npm run test:changed`, and
`npm run test:local`) runs on Linux in CI, and Windows CI runs a curated lane.
On a Windows checkout, some source tests fail because fixtures use POSIX paths,
`#!/bin/sh` tool stubs, or POSIX rename semantics. Compare failures against the
base revision, or run the suite on Linux or WSL, before attributing them to a
change. See [testing](docs/testing.md#developer-commands) for command scopes
and lane selection.
`git submodule update --init --recursive` fails at the pinned Unblob submodule
on Windows and can leave later submodules unchecked out. Apply the
[Unblob checkout workaround](docs/firmware-analysis.md#windows-submodule-checkout),
then rerun the command.
## Documentation website
The VitePress site uses the Markdown files in `docs/`. Run `npm run docs:dev`
@@ -386,17 +345,9 @@ Real-provider execution is uncached.
### Temporary files and diagnostics
Tests use `createTestTempDirectory` from `tests/fixtures/temporaryDirectory.ts`
for exact-path, awaited cleanup, including failures and timeouts. It registers
deletion with Vitest's `onTestFinished`; do not also add its result to a roots
array or an `afterEach` removal. `createTestWorkspace` from
`tests/support/workspace/workspaceFixture.ts` leaves cleanup with its caller,
which must await `removeTestWorkspace`. Keep explicit resource teardown before
directory deletion, using the case's `finally` or `afterEach`; Vitest runs
`afterEach` before `onTestFinished` and awaits finish hooks in reverse
registration order.
for exact-path, awaited cleanup, including failures and timeouts.
`npm run verify:test-temp-hygiene` runs the suite under a fresh `TMPDIR` and
rejects remaining REA-owned paths. It cannot detect duplicate deletion owners or
prove resource-close ordering. Never glob-clean shared `/tmp/rea-*` content.
rejects remaining REA-owned paths. Never glob-clean shared `/tmp/rea-*` content.
`REA_LOG_LEVEL` selects `trace`, `debug`, `info`, `warn`, `error`, `fatal`, or
`silent`. Logs go to stderr: MCP defaults to `info`, and one-shot CLI logging
+33 -53
View File
@@ -1,7 +1,7 @@
<div align="center">
**English** · [简体中文](docs/readme/README_zh.md) · [繁體中文](docs/readme/README_zh-TW.md) · [日本語](docs/readme/README_ja.md) · [한국어](docs/readme/README_ko.md) · [Türkçe](docs/readme/README_tr.md) · [Русский](docs/readme/README_ru.md) · [Tiếng Việt](docs/readme/README_vi.md) · [ไทย](docs/readme/README_th.md) · [Deutsch](docs/readme/README_de.md) · [Español](docs/readme/README_es.md) · [Français](docs/readme/README_fr.md) · [Українська](docs/readme/README_uk.md) · [Polski](docs/readme/README_pl.md) · [Português (Brasil)](docs/readme/README_pt-BR.md) · [العربية](docs/readme/README_ar.md) · [فارسی](docs/readme/README_fa.md) · [Bahasa Indonesia](docs/readme/README_id.md) · [Italiano](docs/readme/README_it.md)
· [Ελληνικά](docs/readme/README_el.md) · [മലയാളം](docs/readme/README_ml.md) · [Roman Urdu](docs/readme/README_ur-Latn.md) · [Română](docs/readme/README_ro.md)
· [Ελληνικά](docs/readme/README_el.md) · [മലയാളം](docs/readme/README_ml.md)
# REA: Reverse Engineer Anything
@@ -90,12 +90,6 @@ Setup supports Claude Code, Codex, Cursor, Gemini CLI, Grok Build and
[installation and setup](docs/installation.md) for provider configuration and
manual MCP registration.
MCP uses compact input schemas by default to reduce context overhead. Set
`REA_MCP_INPUT_SCHEMA_PROFILE=full` in your MCP registration when you need the
complete advertised field guidance and nested validation details. Both profiles
keep the full tool set and canonical server validation. See
"Compact input schema profile" in the MCP contracts guide.
### Ask your agent
```text
@@ -177,47 +171,38 @@ CLI commands use the same workflows.
## What you can analyze
REA requires Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), or 26.12.1+, plus npm.
REA requires Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+, plus npm.
Additional tools and host support depend on the target:
| Target | What REA returns | Requirements and guide |
| ----------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Desktop installations | Runtime candidates, supporting files, component markers and next probes | Built-in bounded directory/header inspection; [desktop applications](docs/desktop-applications.md) |
| Native binaries | Pseudocode, assembly, strings, symbols, calls and references | Hopper, Ghidra or IDA; [native analysis](docs/native-investigation.md) |
| Offline ELF layout | Sections, segments, original symbols/relocations and static mitigation candidates | Caller-supplied pwntools on Linux x64; [binary diagnostics](docs/binary-diagnostics.md) |
| Go binaries | Embedded compiler version, modules, replacements and build settings | Built-in static ELF/PE/thin Mach-O inspection; no Go installation required |
| GPU code objects | CUDA/HIP targets, selected kernel parameters and resources, retained PTX text | Built-in static ELF/PE/fatbin/CUBIN/PTX inspection; [GPU code objects](docs/gpu-code-objects.md) |
| Model artifacts | GGUF / safetensors metadata, tensor declarations and derived totals | Built-in header inspection; optional full-file hash; [model files](docs/model-artifacts.md) |
| IL2CPP metadata | Unity global-metadata.dat assembly images, type and method names and index ranges | Built-in static decoding of versions 24, 27, 29, 31 and 39; no Unity required |
| Godot PCK | Archive inventory and extraction, original header/directory facts and selected content identities | Built-in static formats 2, 3 and 4; no Godot required |
| Rust binaries | Embedded cargo-auditable crate names, versions, sources and dependency edges | Built-in static ELF/PE/thin Mach-O inspection; [Rust binaries](docs/rust-binaries.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](docs/pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](docs/pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](docs/elf-notes.md) |
| Apple IPS crashes | .ips metadata, threads, images, registers and documented crash classes | Built-in static JSON inspection; [Apple IPS](docs/apple-ips.md) |
| Linux kernel modules | .modinfo keys, __versions CRCs, this_module presence, and signature trailer | Built-in static ELF inspection; [kernel modules](docs/kernel-modules.md) |
| Erlang BEAM | IFF chunks, atoms, exports, imports, lambdas and the Code header | Built-in static BEAM inspection; [Erlang BEAM](docs/erlang-beam.md) |
| WASM artifact | Validation, sections, exact import/export WAT forms and decoded WAT | Caller-supplied WABT 1.0.42; `inspect_wasm_artifact` |
| EVM bytecode | Dispatch selectors, byte offsets, inferred arguments and mutability | Local raw/hex carrier on Linux x64; [offline EVM guide](docs/evm-bytecode.md) |
| SQLite databases | Tables, columns, indexes, views, triggers and selected ordinary-table rows | Local database/WAL snapshot; Node.js 24.x >=24.15 or 26+ with native SQLite limits |
| Recorded Linux crashes | Raw notes, every recorded thread's registers/signals and optional mapping candidates | Caller-supplied pwntools on Linux x64; optional GDB/pwndbg; [recorded crashes](docs/recorded-crashes.md) |
| JavaScript / Electron | Modules, imports, source maps, routes, IPC and native add-on relationships | Node.js and npm; Linux: procps-compatible `ps`; [application analysis](docs/javascript-application-workflows.md) |
| Websites | Page structure, scripts, network observations and requested screenshots | A Chrome-family browser; [browser analysis](docs/browser-observation.md) |
| Saved network captures | Requests, responses, exposed payloads and source locations | HAR or Fiddler Everywhere SAZ; mitmdump on Linux for native mitmproxy; [capture guide](docs/web-network-captures.md) |
| .NET assemblies | Metadata, CIL instructions, declared native dependencies and build comparisons | None (static only); [managed-code guide](docs/managed-code-analysis.md) |
| Android APKs | Manifest declarations, classes, decompiled methods and references | Headless JADX and a full JDK on Linux/macOS/Windows x64; [Android guide](docs/android-analysis.md) |
| APK signatures | v1/v2/v3/v3.1 signers, certificate fingerprints, key rotation and digest self-consistency | None (static only); `inspect_apk_signature` |
| Android devices | Devices, packages, processes, logs, dumps, screen captures, and APK/file transfers | Caller-supplied adb (emulator or hardware); [ADB guide](docs/adb-device-analysis.md) |
| JEB projects | Engine-backed project units, artifact digests and type/method pseudo-code | A caller-started JEB client serving MCP, selected with REA_JEB_MCP_URL; [JEB guide](docs/jeb-analysis.md) |
| Android resources | Decoded manifest, string tables, locales and version facts | Caller-supplied Apktool launcher; [resource guide](docs/apktool-resource-analysis.md) |
| Flutter apps | Dart snapshot hash, per-ABI library digests and engine build provenance | None (static APK parsing); [Flutter guide](docs/flutter-analysis.md) |
| Firmware | Regions, extraction results and native-analysis handoffs | Binwalk / Unblob on Linux; [firmware guide](docs/firmware-analysis.md) |
| Recorded packets | Selected fields, repeated values, filters and decode-as rules | Linux: TShark 4.x + prlimit; [capture guide](docs/packet-captures.md) |
| Packet summaries | Endpoints, DNS, TLS SNI, protocol distribution and HTTP requests | Caller-supplied tshark and capinfos (Linux-verified); [pcap guide](docs/pcap-analysis.md) |
| Recorded logic signals | Decoder annotations, sample ranges and diagnostics | Linux: sigrok-cli 0.7.2 + prlimit; [signal recordings](docs/signal-recordings.md) |
| Packages and resources | File inventories, digests, plists, Apple bundle anatomy and extracted resources | [Artifact and JavaScript guide](docs/javascript-artifact-reconstruction.md), [Apple applications](docs/apple-application-analysis.md) |
| Process behavior | Terminal output, interactions, exit and filesystem observations, and run comparisons | Linux/macOS with a native PTY; [process capture](docs/process-capture.md) |
| PyInstaller executables | CArchive members, Python version, entry scripts and extracted bytecode | None (static only); [Python packaged applications](docs/python-packaged-applications.md) |
| Target | What REA returns | Requirements and guide |
| ---------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Desktop installations | Runtime candidates, supporting files, component markers and next probes | Built-in bounded directory/header inspection; [desktop applications](docs/desktop-applications.md) |
| Native binaries | Pseudocode, assembly, strings, symbols, calls and references | Hopper, Ghidra or IDA; [native analysis](docs/native-investigation.md) |
| Offline ELF layout | Sections, segments, original symbols/relocations and static mitigation candidates | Caller-supplied pwntools on Linux x64; [binary diagnostics](docs/binary-diagnostics.md) |
| Go binaries | Embedded compiler version, modules, replacements and build settings | Built-in static ELF/PE/thin Mach-O inspection; no Go installation required |
| Model artifacts | GGUF / safetensors metadata, tensor declarations and derived totals | Built-in header inspection; optional full-file hash; [model files](docs/model-artifacts.md) |
| IL2CPP metadata | Unity global-metadata.dat assembly images, type and method names and index ranges | Built-in static decoding of versions 24, 27, 29, 31 and 39; no Unity required |
| Godot PCK | Archive inventory and extraction, original header/directory facts and selected content identities | Built-in static formats 2, 3 and 4; no Godot required |
| Rust binaries | Embedded cargo-auditable crate names, versions, sources and dependency edges | Built-in static ELF/PE/thin Mach-O inspection; [Rust binaries](docs/rust-binaries.md) |
| WASM artifact | Validation, sections, exact import/export WAT forms and decoded WAT | Caller-supplied WABT 1.0.42; `inspect_wasm_artifact` |
| EVM bytecode | Dispatch selectors, byte offsets, inferred arguments and mutability | Local raw/hex carrier on Linux x64; [offline EVM guide](docs/evm-bytecode.md) |
| SQLite databases | Tables, columns, indexes, views, triggers and selected ordinary-table rows | Local database/WAL snapshot; Node.js 24.x >=24.15 or 26+ with native SQLite limits |
| Recorded Linux crashes | Raw notes, every recorded thread's registers/signals and optional mapping candidates | Caller-supplied pwntools on Linux x64; optional GDB/pwndbg; [recorded crashes](docs/recorded-crashes.md) |
| JavaScript / Electron | Modules, imports, source maps, routes, IPC and native add-on relationships | Node.js and npm; Linux: procps-compatible `ps`; [application analysis](docs/javascript-application-workflows.md) |
| Websites | Page structure, scripts, network observations and requested screenshots | A Chrome-family browser; [browser analysis](docs/browser-observation.md) |
| Saved network captures | Requests, responses, exposed payloads and source locations | HAR or Fiddler Everywhere SAZ; mitmdump on Linux for native mitmproxy; [capture guide](docs/web-network-captures.md) |
| .NET assemblies | Metadata, CIL instructions, declared native dependencies and build comparisons | None (static only); [managed-code guide](docs/managed-code-analysis.md) |
| Android APKs | Manifest declarations, classes, decompiled methods and references | Headless JADX and a full JDK on Linux/macOS/Windows x64; [Android guide](docs/android-analysis.md) |
| Android devices | Devices, packages, processes, logs, dumps, screen captures, and APK/file transfers | Caller-supplied adb (emulator or hardware); [ADB guide](docs/adb-device-analysis.md) |
| JEB projects | Engine-backed project units, artifact digests and type/method pseudo-code | A caller-started JEB client serving MCP, selected with REA_JEB_MCP_URL; [JEB guide](docs/jeb-analysis.md) |
| Android resources | Decoded manifest, string tables, locales and version facts | Caller-supplied Apktool launcher; [resource guide](docs/apktool-resource-analysis.md) |
| Flutter apps | Dart snapshot hash, per-ABI library digests and engine build provenance | None (static APK parsing); [Flutter guide](docs/flutter-analysis.md) |
| Firmware | Regions, extraction results and native-analysis handoffs | Binwalk / Unblob on Linux; [firmware guide](docs/firmware-analysis.md) |
| Recorded packets | Selected fields, repeated values, filters and decode-as rules | Linux: TShark 4.x + prlimit; [capture guide](docs/packet-captures.md) |
| Packet summaries | Endpoints, DNS, TLS SNI, protocol distribution and HTTP requests | Caller-supplied tshark and capinfos (Linux-verified); [pcap guide](docs/pcap-analysis.md) |
| Recorded logic signals | Decoder annotations, sample ranges and diagnostics | Linux: sigrok-cli 0.7.2 + prlimit; [signal recordings](docs/signal-recordings.md) |
| Packages and resources | File inventories, digests, plists, Apple bundle anatomy and extracted resources | [Artifact and JavaScript guide](docs/javascript-artifact-reconstruction.md), [Apple applications](docs/apple-application-analysis.md) |
| Process behavior | Terminal output, interactions, exit and filesystem observations, and run comparisons | Linux/macOS with a native PTY; [process capture](docs/process-capture.md) |
Static JavaScript and .NET inspection read the supplied files without running
the application. Runtime capture runs or interacts with the selected target
@@ -244,12 +229,7 @@ for features added since the latest npm release.
## Showcases
<a href="https://rea.tools/showcase/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/rea-showcases-dark.svg" />
<img alt="Illustrations of the DX-Ball sound-pan, Notion clipboard-bridge, and TH04 bullet-ring showcases" src="docs/assets/rea-showcases.svg" />
</picture>
</a>
[![Illustrations of the DX-Ball sound-pan, Notion clipboard-bridge, and TH04 bullet-ring showcases](docs/assets/rea-showcases.png)](https://rea.tools/showcase/)
### DX-Ball: reconstruct a sound-pan calculation
@@ -407,7 +387,7 @@ to improve the code or docs.
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and checks,
[testing](docs/testing.md) for verification lanes, and the
[architecture map](docs/architecture.mermaid) for selected layers and dependency paths.
[architecture map](docs/architecture.mermaid) for the project structure.
## License
+21 -22
View File
@@ -16,7 +16,6 @@ notices supplied with the software you actually obtain or redistribute.
| Material | Use and licensing records |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| npm dependencies | [Complete inventory](licenses/npm-dependencies.txt) of every entry in `package-lock.json`, including transitive, optional platform, development, and website dependencies. [Runtime notices](licenses/npm-runtime-notices.txt) preserve the copyright, license, and NOTICE documents from integrity-verified release archives, plus the MIT notice for Rolldown's generated runtime helpers. Development dependencies are inventoried but are not all distributed in REA's npm package. |
| pefile ordinal tables | Import-hash ordinal names are derived from [pefile 2024.8.26](https://github.com/erocarrera/pefile/tree/4b3b1e2e568a88d4f1897d694d684f23d9e270c4), copyright 2004–2024 Ero Carrera. Preserve the [MIT license](licenses/pefile-2024.8.26-LICENSE.txt). The Python package is not a runtime dependency. |
| NativeAOT parser | REA's derived parser uses the MIT-licensed [ghidra-nativeaot](https://github.com/Washi1337/ghidra-nativeaot/tree/effeb734fc570c32650f88b159608979dc7b423e) work of Washi and the NativeAOT layout definitions in [dotnet/runtime](https://github.com/dotnet/runtime/tree/a3b2d40328be0be3f8dd43f0e5cc925b8827b044). Preserve [Washi's MIT notice](licenses/nativeaot-parser-MIT.txt) and the [.NET Foundation MIT notice](licenses/dotnet-runtime-MIT.txt). |
| EVM analysis | `evmole@0.9.3`, MIT, copyright 2023 Maxim Andreev. The published npm archive omits its license document; REA retains the [original upstream license](third_party/evmole/LICENSE) and [provenance](third_party/evmole/README.md). It is also included in the runtime notices. |
| Windows native addon | The release addon is compiled with MinGW-w64 and statically links GCC runtime libraries. Preserve the [MinGW-w64 runtime notices](licenses/MinGW-w64-runtime.txt), [GPLv3 text](licenses/GPL-3.0.txt), and [GCC Runtime Library Exception 3.1](licenses/GCC-Runtime-Library-Exception-3.1.txt). The exception governs eligible compiled combinations; it does not relicense the runtime itself under MIT. The Node-API build headers are from Node 22.19.0; its [original license and component notices](licenses/Node-22.19.0-LICENSE.txt) are retained. Node itself is supplied by the user. Snapshot provenance and release-toolchain review requirements are in [licenses/README.md](licenses/README.md). |
@@ -53,27 +52,27 @@ REA's adapters do not grant, replace, or waive these licenses. The upstream
executables and their dependency graphs are supplied separately. Git submodules
pin inspection sources; they are not included in the npm package.
| Project | License and source | REA relationship |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Ghidra | [Apache-2.0](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/LICENSE), with the government-work and component qualifications in its [NOTICE](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/NOTICE) | Bring your own Ghidra; see [supported versions](docs/installation.md#ghidra). Copies of the 12.1.4 [LICENSE](licenses/ghidra-12.1.4-LICENSE.txt), [NOTICE](licenses/ghidra-12.1.4-NOTICE.txt), and [DISCLAIMER](licenses/ghidra-12.1.4-DISCLAIMER.txt) are retained for reference. |
| ghidra-nativeaot | [MIT](https://github.com/Washi1337/ghidra-nativeaot/blob/effeb734fc570c32650f88b159608979dc7b423e/LICENSE.md) | Pinned inspection source; derived parser attribution above. |
| JADX | [Apache-2.0](https://github.com/skylot/jadx/blob/v1.5.6/LICENSE) | The audited jadx-headless-mcp source uses JADX 1.5.6 for Android decompilation; the engine JAR is separately supplied. |
| jadx-headless-mcp | [Apache-2.0](https://github.com/1013503897/jadx-headless-mcp/blob/5844800a486d2248fa949b74c9896285f5b54de2/LICENSE) | v0.7.1; pinned submodule and user-supplied JAR. |
| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.833/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.833/NOTICE) | Bring your own `kuna`; REA verified 1.833 and never downloads, installs or bundles it. REA invokes it as a separate process. |
| Apktool | [Apache-2.0](https://github.com/iBotPeaches/Apktool/blob/master/LICENSE.md) | User-supplied Android resource decoder. |
| Binwalk | [MIT](https://github.com/ReFirmLabs/binwalk/blob/4fdab3d464d97b68e0af9088df3f9e2e1545b21c/LICENSE) | v3.1.0; pinned submodule and user-supplied firmware engine. |
| Unblob | [MIT](https://github.com/onekey-sec/unblob/blob/1fcc7a0a584a70a96c31f5a276c20944d199a089/LICENSE) | 26.6.4; pinned submodule and user-supplied firmware engine. |
| Wakaru | [Apache-2.0](https://github.com/pionxzh/wakaru/blob/8219bf5016a063d7fb215101a8c45d5959ec733f/LICENSE) | v1.14.0; pinned source and separately obtained JavaScript reconstruction tooling. |
| WABT | [Apache-2.0](https://github.com/WebAssembly/wabt/blob/ff0ef7e0009402740c805a9744c09b05be063e48/LICENSE) | v1.0.42; external WebAssembly tools; [integration provenance](third_party/wabt/README.md). |
| pwntools | [MIT, with GPL/BSD component qualifications](https://github.com/Gallopsled/pwntools/blob/3ad86ec5adab1abf9781ca7552a6f5d7cff7a33c/LICENSE-pwntools.txt) | 4.15.0; caller-supplied offline ELF analysis package. |
| pyelftools | [Public-domain dedication and component notices](https://github.com/eliben/pyelftools/blob/v0.33/LICENSE) | 0.33; includes separately licensed bundled material, including Construct. |
| Unicorn | [GPL-2.0 and component notices](https://github.com/unicorn-engine/unicorn/blob/2.1.2/COPYING) | 2.1.2; used by the caller-supplied ELF analysis profile for instruction emulation. |
| pwndbg | [MIT](third_party/pwndbg/LICENSE.md) | 2026.09.15, commit `55596aa676a2756efb8d67034f31675170eabe48`; separately obtained debugger extension. |
| GNU GDB | [GPL-3.0-or-later](https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/COPYING) | Caller-supplied debugger for the optional pwndbg core-file workflow. |
| LLDB / LLVM | [Apache-2.0 with LLVM exceptions](https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT) | Caller-supplied debugger; the installed distribution's component and historical notices also apply. |
| mitmproxy | [MIT](https://github.com/mitmproxy/mitmproxy/blob/6c09d56e4c29a92f5ad01b03199977584b8ea14f/LICENSE) | 12.2.3; separately supplied network capture proxy. |
| Android Debug Bridge (adb) | [Apache-2.0](https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/NOTICE) | User-supplied Android device tool. A complete Android SDK download has its own [SDK license terms](https://developer.android.com/studio/terms); adb's source license does not replace them. |
| ILSpy / ICSharpCode.Decompiler | [MIT](https://github.com/icsharpcode/ILSpy/blob/master/ICSharpCode.Decompiler/Properties/AssemblyInfo.cs) | Optional separately installed .NET decompiler; not a redistributed REA dependency. |
| Project | License and source | REA relationship |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Ghidra | [Apache-2.0](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/LICENSE), with the government-work and component qualifications in its [NOTICE](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/NOTICE) | Bring your own Ghidra 12.1.4. Copies of its [LICENSE](licenses/ghidra-12.1.4-LICENSE.txt), [NOTICE](licenses/ghidra-12.1.4-NOTICE.txt), and [DISCLAIMER](licenses/ghidra-12.1.4-DISCLAIMER.txt) are retained for reference. |
| ghidra-nativeaot | [MIT](https://github.com/Washi1337/ghidra-nativeaot/blob/effeb734fc570c32650f88b159608979dc7b423e/LICENSE.md) | Pinned inspection source; derived parser attribution above. |
| JADX | [Apache-2.0](https://github.com/skylot/jadx/blob/v1.5.6/LICENSE) | The audited jadx-headless-mcp source uses JADX 1.5.6 for Android decompilation; the engine JAR is separately supplied. |
| jadx-headless-mcp | [Apache-2.0](https://github.com/1013503897/jadx-headless-mcp/blob/5844800a486d2248fa949b74c9896285f5b54de2/LICENSE) | v0.7.1; pinned submodule and user-supplied JAR. |
| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.833/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.833/NOTICE) | Bring your own `kuna`; REA verified 1.833 and never downloads, installs or bundles it. REA invokes it as a separate process. |
| Apktool | [Apache-2.0](https://github.com/iBotPeaches/Apktool/blob/master/LICENSE.md) | User-supplied Android resource decoder. |
| Binwalk | [MIT](https://github.com/ReFirmLabs/binwalk/blob/4fdab3d464d97b68e0af9088df3f9e2e1545b21c/LICENSE) | v3.1.0; pinned submodule and user-supplied firmware engine. |
| Unblob | [MIT](https://github.com/onekey-sec/unblob/blob/1fcc7a0a584a70a96c31f5a276c20944d199a089/LICENSE) | 26.6.4; pinned submodule and user-supplied firmware engine. |
| Wakaru | [Apache-2.0](https://github.com/pionxzh/wakaru/blob/8219bf5016a063d7fb215101a8c45d5959ec733f/LICENSE) | v1.14.0; pinned source and separately obtained JavaScript reconstruction tooling. |
| WABT | [Apache-2.0](https://github.com/WebAssembly/wabt/blob/ff0ef7e0009402740c805a9744c09b05be063e48/LICENSE) | v1.0.42; external WebAssembly tools; [integration provenance](third_party/wabt/README.md). |
| pwntools | [MIT, with GPL/BSD component qualifications](https://github.com/Gallopsled/pwntools/blob/3ad86ec5adab1abf9781ca7552a6f5d7cff7a33c/LICENSE-pwntools.txt) | 4.15.0; caller-supplied offline ELF analysis package. |
| pyelftools | [Public-domain dedication and component notices](https://github.com/eliben/pyelftools/blob/v0.33/LICENSE) | 0.33; includes separately licensed bundled material, including Construct. |
| Unicorn | [GPL-2.0 and component notices](https://github.com/unicorn-engine/unicorn/blob/2.1.2/COPYING) | 2.1.2; used by the caller-supplied ELF analysis profile for instruction emulation. |
| pwndbg | [MIT](third_party/pwndbg/LICENSE.md) | 2026.09.15, commit `55596aa676a2756efb8d67034f31675170eabe48`; separately obtained debugger extension. |
| GNU GDB | [GPL-3.0-or-later](https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/COPYING) | Caller-supplied debugger for the optional pwndbg core-file workflow. |
| LLDB / LLVM | [Apache-2.0 with LLVM exceptions](https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT) | Caller-supplied debugger; the installed distribution's component and historical notices also apply. |
| mitmproxy | [MIT](https://github.com/mitmproxy/mitmproxy/blob/6c09d56e4c29a92f5ad01b03199977584b8ea14f/LICENSE) | 12.2.3; separately supplied network capture proxy. |
| Android Debug Bridge (adb) | [Apache-2.0](https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/NOTICE) | User-supplied Android device tool. A complete Android SDK download has its own [SDK license terms](https://developer.android.com/studio/terms); adb's source license does not replace them. |
| ILSpy / ICSharpCode.Decompiler | [MIT](https://github.com/icsharpcode/ILSpy/blob/master/ICSharpCode.Decompiler/Properties/AssemblyInfo.cs) | Optional separately installed .NET decompiler; not a redistributed REA dependency. |
Ghidra's original notice credits software developed at the National Security
Agency. It explains that US-government-created portions are not subject to US
+23 -181
View File
@@ -3,8 +3,6 @@ import com.google.gson.*;
import io.modelcontextprotocol.kotlin.sdk.server.*;
import io.modelcontextprotocol.kotlin.sdk.types.*;
import jadx.api.*;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.insns.InsnIndexType;
@@ -12,7 +10,6 @@ import jadx.api.plugins.input.insns.Opcode;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.*;
import java.io.PrintStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CountDownLatch;
@@ -26,10 +23,6 @@ import kotlin.coroutines.intrinsics.IntrinsicsKt;
import kotlinx.coroutines.BuildersKt;
import kotlinx.io.CoreKt;
import kotlinx.io.JvmCoreKt;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
/** REA-owned metadata boundary for the unmodified jadx-headless-mcp 0.7.1 JAR. */
class ReaJadxBridge {
@@ -37,14 +30,10 @@ class ReaJadxBridge {
private final SessionHolder holder;
private JadxSession indexedSession;
private Map<String, List<ClassSnapshot>> classes = Map.of();
private JadxSession manifestSession;
private ManifestSnapshot manifestSnapshot;
private record MethodSnapshot(String name, String rawName, String shortId, List<String> bodyAbsenceMarkers,
Map<String, Object> summary) {}
private record ClassSnapshot(JavaClass node, List<MethodSnapshot> methods, Map<String, Object> summary,
Map<String, Object> summaryWithValues) {}
private record ManifestSnapshot(String text, Map<String, Object> summary) {}
private record ClassSnapshot(JavaClass node, List<MethodSnapshot> methods, Map<String, Object> summary) {}
private static final class InputFailure extends IllegalArgumentException {
final String field;
@@ -104,7 +93,6 @@ class ReaJadxBridge {
args.has("threads") ? integer(args, "threads", 1) : null,
args.has("code_cache_size") ? integer(args, "code_cache_size", 64) : null,
optionalString(args, "resources"));
clearManifest();
return loadCall(next -> holder.load(path, options, next), continuation);
}
@@ -145,14 +133,6 @@ class ReaJadxBridge {
return value.getAsString();
}
private static boolean flag(JsonObject args, String name) {
JsonElement value = args.get(name);
if (value == null) return false;
if (!value.isJsonPrimitive() || !value.getAsJsonPrimitive().isBoolean())
throw new IllegalArgumentException(name + " must be a boolean");
return value.getAsBoolean();
}
private static int integer(JsonObject args, String name, int fallback) {
JsonElement value = args.get(name);
if (value == null) return fallback;
@@ -173,77 +153,6 @@ class ReaJadxBridge {
return current;
}
private void clearManifest() {
manifestSession = null;
manifestSnapshot = null;
}
/** Original archive identity, never a deobfuscated basename or name suffix. */
private ManifestSnapshot manifest(JadxSession current) {
if (manifestSession != current || manifestSnapshot == null) {
ResourceFile selected = null;
for (ResourceFile resource : current.getResources()) {
if (resource.getType() != ResourceType.MANIFEST
|| !resource.getOriginalName().equals("AndroidManifest.xml")) continue;
if (selected != null)
throw new IllegalArgumentException("Root AndroidManifest.xml resource identity is ambiguous");
selected = resource;
}
if (selected == null) throw new IllegalArgumentException("Root AndroidManifest.xml not found");
var content = selected.loadContent();
if (content == null || content.getDataType() != jadx.core.xmlgen.ResContainer.DataType.TEXT)
throw new IllegalArgumentException("Root AndroidManifest.xml did not decode as text");
String xml = content.getText().getCodeStr();
ManifestSnapshot snapshot = new ManifestSnapshot(xml, manifestSummary(xml));
manifestSnapshot = snapshot;
manifestSession = current;
}
return manifestSnapshot;
}
/** One parsed XML owner supplies namespace-aware values alongside the exact decoded text. */
static Map<String, Object> manifestSummary(String xml) {
Element root;
try {
// Match REA's existing JDK XML setup: document types are unsupported.
// Parse an in-memory string; XInclude and external DTD/schema I/O stay disabled.
DocumentBuilderFactory factory = DocumentBuilderFactory.newDefaultNSInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setXIncludeAware(false);
factory.setAttribute(javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
root = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)))
.getDocumentElement();
} catch (javax.xml.parsers.ParserConfigurationException | org.xml.sax.SAXException
| java.io.IOException failure) {
throw new IllegalArgumentException("Root AndroidManifest.xml is not supported well-formed XML", failure);
}
if (!root.getTagName().equals("manifest") || root.getNamespaceURI() != null)
throw new IllegalArgumentException("AndroidManifest.xml root must be an unnamespaced <manifest>");
String android = "http://schemas.android.com/apk/res/android";
String minSdk = "";
String targetSdk = "";
List<String> permissions = new ArrayList<>();
for (Node child = root.getFirstChild(); child != null; child = child.getNextSibling()) {
if (!(child instanceof Element element) || element.getNamespaceURI() != null) continue;
if (element.getTagName().equals("uses-sdk")) {
minSdk = element.getAttributeNS(android, "minSdkVersion");
targetSdk = element.getAttributeNS(android, "targetSdkVersion");
} else if (element.getTagName().equals("uses-permission")) {
String permission = element.getAttributeNS(android, "name");
if (!permission.isEmpty()) permissions.add(permission);
}
}
Map<String, Object> summary = new LinkedHashMap<>();
summary.put("package", root.getAttribute("package"));
summary.put("version_name", root.getAttributeNS(android, "versionName"));
summary.put("version_code", root.getAttributeNS(android, "versionCode"));
summary.put("min_sdk", minSdk);
summary.put("target_sdk", targetSdk);
summary.put("permissions", List.copyOf(permissions));
return Collections.unmodifiableMap(summary);
}
private ClassSnapshot selected(JsonObject args) {
JadxSession current = session();
if (indexedSession != current) {
@@ -274,43 +183,18 @@ class ReaJadxBridge {
"dex_descriptor", descriptor,
"is_constructor", method.isConstructor(), "def_pos", 0));
}).toList();
List<FieldNode> orderedFields = new ArrayList<>(node.getFields());
orderedFields.sort(Comparator.comparing(FieldNode::getAlias));
List<Map<String, Object>> fields = new ArrayList<>();
List<Map<String, Object>> valuedFields = new ArrayList<>();
boolean anyValue = false;
for (FieldNode field : orderedFields) {
Map<String, Object> entry = Map.of("name", field.getAlias(),
"type", field.getType().toString(), "def_pos", 0);
fields.add(entry);
Map<String, Object> value = dexStaticValue(field);
if (value == null) valuedFields.add(entry);
else {
anyValue = true;
Map<String, Object> valued = new LinkedHashMap<>(entry);
valued.put("dex_static_value", value);
valuedFields.add(Collections.unmodifiableMap(valued));
}
}
List<Map<String, Object>> fields = node.getFields().stream()
.sorted(Comparator.comparing(FieldNode::getAlias))
.map(field -> Map.<String, Object>of("name", field.getAlias(),
"type", field.getType().toString(), "def_pos", 0)).toList();
List<String> inner = node.getInnerClasses().stream().map(ClassNode::getFullName).toList();
Map<String, Object> summary = Map.of(
"full_name", cls.getFullName(), "raw_name", cls.getRawName(), "name", cls.getName(),
"full_name", cls.getFullName(), "name", cls.getName(),
"method_count", methods.size(), "field_count", fields.size(),
"inner_class_count", inner.size(), "inner_classes", inner,
"methods", methods.stream().map(MethodSnapshot::summary).toList(), "fields", List.copyOf(fields));
// DEX static values are opt-in, so method and reference lookups keep the member-only reply.
Map<String, Object> summaryWithValues = summary;
if (anyValue) {
Map<String, Object> copy = new LinkedHashMap<>(summary);
copy.put("fields", List.copyOf(valuedFields));
summaryWithValues = Collections.unmodifiableMap(copy);
}
ClassSnapshot snapshot = new ClassSnapshot(cls, methods, summary, summaryWithValues);
index.computeIfAbsent(cls.getFullName(), ignored -> new ArrayList<>()).add(snapshot);
// Keep the binary identity supplied by JADX; '$' can also be a literal
// name character, so replacing separators would invent lookup aliases.
if (!cls.getRawName().equals(cls.getFullName()))
index.computeIfAbsent(cls.getRawName(), ignored -> new ArrayList<>()).add(snapshot);
"methods", methods.stream().map(MethodSnapshot::summary).toList(), "fields", fields);
index.computeIfAbsent(cls.getFullName(), ignored -> new ArrayList<>())
.add(new ClassSnapshot(cls, methods, summary));
}
}
classes = Collections.unmodifiableMap(index);
@@ -325,28 +209,6 @@ class ReaJadxBridge {
return candidates.get(0);
}
/** A static final field's class-definition static value; a static initializer can still assign it. */
private static Map<String, Object> dexStaticValue(FieldNode field) {
if (!field.getAccessFlags().isStatic() || !field.getAccessFlags().isFinal()) return null;
EncodedValue encoded = field.get(JadxAttrType.CONSTANT_VALUE);
if (encoded == null) return null;
Object value = encoded.getValue();
// JADX also attaches the shared null to static finals without a value, so null is never reported.
// Wide and floating values keep their Java text instead of a lossy JSON number.
return switch (encoded.getType()) {
case ENCODED_BOOLEAN -> Map.of("type", "boolean", "value", value);
case ENCODED_BYTE -> Map.of("type", "byte", "value", value);
case ENCODED_SHORT -> Map.of("type", "short", "value", value);
case ENCODED_CHAR -> Map.of("type", "char", "value", (int) (Character) value);
case ENCODED_INT -> Map.of("type", "int", "value", value);
case ENCODED_LONG -> Map.of("type", "long", "value", value.toString());
case ENCODED_FLOAT -> Map.of("type", "float", "value", value.toString());
case ENCODED_DOUBLE -> Map.of("type", "double", "value", value.toString());
case ENCODED_STRING -> Map.of("type", "string", "value", value);
default -> null;
};
}
private List<MethodSnapshot> methods(ClassSnapshot cls, JsonObject args) {
String name = string(args, "method_name");
List<MethodSnapshot> result = cls.methods().stream().filter(method -> method.name().equals(name)).toList();
@@ -471,9 +333,6 @@ class ReaJadxBridge {
"java_version", System.getProperty("java.version"),
"metadata_scope", "parsed_members"));
JadxSession current = session();
if (name.equals("get_app_info")) return json(manifest(current).summary());
if (name.equals("get_android_manifest"))
return text(TextUtilKt.truncateToBytes(manifest(current).text(), current.getMaxSourceBytes()), false);
if (name.equals("list_classes")) {
List<String> names = current.getClassFqns();
String query = args.has("query") ? string(args, "query") : "";
@@ -497,8 +356,7 @@ class ReaJadxBridge {
"items", items));
}
ClassSnapshot cls = selected(args);
if (name.equals("get_class_summary"))
return json(flag(args, "dex_static_values") ? cls.summaryWithValues() : cls.summary());
if (name.equals("get_class_summary")) return json(cls.summary());
if (name.equals("get_method_body")) {
List<MethodSnapshot> overloads = methods(cls, args);
int index = integer(args, "overload_index", 0);
@@ -513,7 +371,7 @@ class ReaJadxBridge {
"markers", code.getMarkers(), "body", code.getText()));
}
List<JavaNode> uses;
String target = (String) cls.summary().get("full_name");
String target = string(args, "class_name");
MethodSnapshot selectedMethod = null;
int overloadIndex = 0;
if (name.equals("get_xrefs_to_class")) uses = cls.node().getUseIn();
@@ -562,17 +420,6 @@ class ReaJadxBridge {
return json(result);
}
private static void registerBridgeTool(Server server, ReaJadxBridge bridge, String name,
ToolSchema schema, String description) {
Tool tool = new Tool(name, schema, description, null, null, null, null, null, null);
server.addTool(tool, (connection, request, continuation) -> {
try { return bridge.handle(name, request.getArguments()); }
catch (InputFailure error) { return text(JSON.toJson(Map.of(
"kind", "invalid-input", "field", error.field, "message", error.getMessage())), true); }
catch (IllegalArgumentException error) { return text(error.getMessage(), true); }
});
}
public static void main(String[] args) throws Exception {
PrintStream protocolOut = System.out;
System.setOut(System.err);
@@ -581,9 +428,10 @@ class ReaJadxBridge {
SessionHolder holder = new SessionHolder(defaults);
ServerCapabilities capabilities = new ServerCapabilities(
new ServerCapabilities.Tools(null), null, null, null, null, null, null, Map.of());
Server server = new Server(new Implementation("rea-jadx-bridge", "7", null, null, null),
Server server = new Server(new Implementation("rea-jadx-bridge", "4", null, null, null),
new ServerOptions(capabilities), "", ignored -> Unit.INSTANCE);
SessionToolsKt.registerSessionTools(server, holder);
ManifestToolsKt.registerManifestTools(server, holder);
ClassToolsKt.registerClassTools(server, holder);
MethodToolsKt.registerMethodTools(server, holder);
XrefToolsKt.registerXrefTools(server, holder);
@@ -592,7 +440,7 @@ class ReaJadxBridge {
server.removeTool("load_apk");
server.addTool(loadTool, (connection, request, continuation) ->
bridge.load(request.getArguments(), defaults, continuation));
Set<String> consumed = Set.of("load_apk",
Set<String> consumed = Set.of("load_apk", "get_app_info", "get_android_manifest",
"list_classes", "get_class_summary", "get_method_body",
"get_xrefs_to_class", "get_xrefs_to_method");
for (String name : new ArrayList<>(server.getTools().keySet()))
@@ -601,13 +449,6 @@ class ReaJadxBridge {
new ToolSchema(null, new kotlinx.serialization.json.JsonObject(Map.of()), List.of(), null),
"Observed worker JVM and metadata scope", null, null, null, null, null, null);
server.addTool(diagnostics, (connection, request, continuation) -> bridge.handle("rea_jvm_status", request.getArguments()));
for (String name : List.of("get_app_info", "get_android_manifest")) {
String description = name.equals("get_app_info")
? "Namespace-aware summary of the exact root AndroidManifest.xml resource."
: "Exact decoded root AndroidManifest.xml text, bounded by the session source byte limit.";
registerBridgeTool(server, bridge, name,
new ToolSchema(null, new kotlinx.serialization.json.JsonObject(Map.of()), List.of(), null), description);
}
for (String name : List.of("list_classes", "get_class_summary",
"get_method_body", "get_xrefs_to_class", "get_xrefs_to_method")) {
Tool tool = server.getTools().get(name).getTool();
@@ -623,18 +464,20 @@ class ReaJadxBridge {
if (name.equals("list_classes")) properties.put("query",
new kotlinx.serialization.json.JsonObject(Map.of(
"type", kotlinx.serialization.json.JsonElementKt.JsonPrimitive("string"))));
if (name.equals("get_class_summary")) properties.put("dex_static_values",
new kotlinx.serialization.json.JsonObject(Map.of(
"type", kotlinx.serialization.json.JsonElementKt.JsonPrimitive("boolean"))));
String description = switch (name) {
case "list_classes" -> "Pages of case-sensitive matching top-level class names; offset and next_offset are inventory positions, limit bounds matching rows. Omitted or empty query inventories all names.";
case "get_class_summary" -> "Parsed member metadata for one exact class name; no source generation. Set dex_static_values to include encoded static final field values.";
case "get_class_summary" -> "Parsed member metadata for one exact class name; no source generation.";
case "get_method_body" -> "Source for an exact class and stable same-name overload index; static initializers use smali to preserve folded field initialization.";
default -> "Complete static using-node inventory for an exact class or selected method overload; one node may contain multiple invoke instructions, and source lines are unresolved.";
};
registerBridgeTool(server, bridge, name,
new ToolSchema(null, new kotlinx.serialization.json.JsonObject(properties),
tool.getInputSchema().getRequired(), null), description);
Tool narrowed = new Tool(name, new ToolSchema(null, new kotlinx.serialization.json.JsonObject(properties),
tool.getInputSchema().getRequired(), null), description, null, null, null, null, null, null);
server.addTool(narrowed, (connection, request, continuation) -> {
try { return bridge.handle(name, request.getArguments()); }
catch (InputFailure error) { return text(JSON.toJson(Map.of(
"kind", "invalid-input", "field", error.field, "message", error.getMessage())), true); }
catch (IllegalArgumentException error) { return text(error.getMessage(), true); }
});
}
CountDownLatch done = new CountDownLatch(1);
server.onClose(() -> { done.countDown(); return Unit.INSTANCE; });
@@ -660,7 +503,6 @@ class ReaJadxBridge {
BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE,
(scope, continuation) -> server.close(continuation));
} finally {
bridge.clearManifest();
BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE,
(scope, continuation) -> holder.unload(continuation));
}
+96 -601
View File
@@ -11,7 +11,6 @@ import json
import hashlib
import hmac
import os
import re
import secrets
import socket
import stat
@@ -34,151 +33,9 @@ except ImportError:
from PySide2.QtCore import QCoreApplication, QObject, Signal
# Request and response frame budget in bytes, excluding the trailing LF.
# REA_BRIDGE_MAX_REQUEST_BYTES selects it. Unset stays 16777216.
# 7464 is the authenticated frame of the largest bounded symbol name.
# The 64-receipt recovery status is smaller with current_file null. A path
# that fits is kept. A path that does not fit is null, not the descriptor path.
# 536870887 is Node's 536870888-character string ceiling minus the framing LF.
# The ceiling is the encoded payload; JSON expansion is measured before it.
# A fitting response is signed from bounded JSON pieces and written in 64 KiB
# chunks, so this maximum does not retain several copies of the frame.
_BRIDGE_MAX_REQUEST_BYTES_SETTING = "REA_BRIDGE_MAX_REQUEST_BYTES"
DEFAULT_BRIDGE_MAX_REQUEST_BYTES = 16 * 1024 * 1024
MINIMUM_BRIDGE_MAX_REQUEST_BYTES = 7464
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES = 536870887
# The outer authenticated frame around the escaped JSON payload, excluding LF.
_AUTHENTICATED_PREFIX = b'{"response":"'
_AUTHENTICATED_PROOF_MARK = b'","proof":"'
_PROOF_HEX_LENGTH = 64
_AUTHENTICATED_FRAME_OVERHEAD = (
len(_AUTHENTICATED_PREFIX)
+ len(_AUTHENTICATED_PROOF_MARK)
+ _PROOF_HEX_LENGTH
+ len(b'"}')
)
_AUTHENTICATED_WRITE_BUFFER_BYTES = 64 * 1024
# Raw JSON is produced in pieces no larger than this many characters.
# One character expands to at most six JSON bytes (\u00XX) before the outer
# string escape, so a piece stays far below the frame ceiling.
_JSON_CHUNK_CHARS = 64 * 1024
# Fixed admission cap. It does not follow REA_BRIDGE_MAX_REQUEST_BYTES.
# A further accept is closed. Starting a worker must not stop the listener.
MAX_CONNECTION_WORKERS = 8
def _close_socket(connection: socket.socket) -> None:
try:
connection.close()
except OSError:
pass
def _bridge_max_request_bytes_constraint():
return (
"REA_BRIDGE_MAX_REQUEST_BYTES must be a positive decimal integer "
"from %d to %d bytes; the default is %d bytes (16 MiB). "
"A value below %d bytes cannot carry the largest admitted "
"symbol-name command, and a larger value is refused so the encoded "
"payload plus its framing LF stays one string under Node's "
"single-string ceiling."
% (
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
DEFAULT_BRIDGE_MAX_REQUEST_BYTES,
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
)
)
def _parse_bridge_max_request_bytes(raw):
"""Refuse a non-decimal or out-of-range ceiling. Never fall back."""
constraint = _bridge_max_request_bytes_constraint()
if isinstance(raw, bool):
raise RuntimeError(constraint)
if isinstance(raw, int):
value = raw
elif isinstance(raw, str):
# ASCII digits only. str.isdigit() accepts numerals such as U+0668
# that the TypeScript setting rejects.
if re.fullmatch(r"[1-9][0-9]*", raw) is None:
raise RuntimeError(constraint)
value = int(raw)
else:
raise RuntimeError(constraint)
if (
value < MINIMUM_BRIDGE_MAX_REQUEST_BYTES
or value > MAXIMUM_BRIDGE_MAX_REQUEST_BYTES
):
raise RuntimeError(constraint)
return value
def _resolve_bridge_max_request_bytes():
"""Prefer an injected global, then the process environment, then 16 MiB."""
injected = globals().get(_BRIDGE_MAX_REQUEST_BYTES_SETTING)
if injected is not None:
return _parse_bridge_max_request_bytes(injected), "setting"
raw = os.environ.get(_BRIDGE_MAX_REQUEST_BYTES_SETTING)
if raw is None:
return DEFAULT_BRIDGE_MAX_REQUEST_BYTES, "default"
return _parse_bridge_max_request_bytes(raw), "setting"
MAX_FRAME_BYTES, BRIDGE_MAX_REQUEST_BYTES_SOURCE = (
_resolve_bridge_max_request_bytes()
)
def _bridge_max_request_bytes_recovery():
return (
"The effective REA_BRIDGE_MAX_REQUEST_BYTES value is %d bytes "
"(source %s; default %d, floor %d, maximum %d). "
"Set REA_BRIDGE_MAX_REQUEST_BYTES in the environment that starts REA "
"to a decimal integer from %d to %d, then restart REA. "
"Restart Hopper or Cutter from that same environment so the bridge "
"uses this ceiling."
% (
MAX_FRAME_BYTES,
BRIDGE_MAX_REQUEST_BYTES_SOURCE,
DEFAULT_BRIDGE_MAX_REQUEST_BYTES,
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
)
)
def _frame_limit_exceeded():
return ValueError(
"frame-limit-exceeded: Bridge request exceeds the %d-byte payload limit. %s"
% (MAX_FRAME_BYTES, _bridge_max_request_bytes_recovery())
)
MAX_FRAME_BYTES = 16 * 1024 * 1024
SOCKET_TIMEOUT_SECONDS = 20
AUTH_FRAME_TIMEOUT_SECONDS = 1
# The client request deadline. The authenticated frame read may use up to
# this long; it does not grow past it when the byte ceiling is raised.
REQUEST_DEADLINE_SECONDS = 25
def _authenticated_frame_timeout_seconds():
"""Time to hash and transfer one maximum-sized authenticated frame.
The hello frame stays at one second. The following command frame is
built only after that challenge, so its read budget follows the
configured payload size, at one second per mebibyte, and never exceeds
the 25 second client deadline.
"""
mebibytes = (MAX_FRAME_BYTES + (1024 * 1024) - 1) // (1024 * 1024)
return min(
REQUEST_DEADLINE_SECONDS,
max(AUTH_FRAME_TIMEOUT_SECONDS, mebibytes),
)
MAX_DESCRIPTOR_BYTES = 64 * 1024
MAX_COMPLETION_RECEIPTS = 64
@@ -623,17 +480,6 @@ class _BridgeServer(threading.Thread):
self.token = secrets.token_urlsafe(32)
self.poisoned = False
self.connection_ordinal = 0
# The client proof is inside the authenticated frame, so that read
# cannot start after proof. It runs off the accept loop. One
# authenticated command still runs at a time. completed_connection
# is the longest prefix of these ordinals whose handlers have
# terminated, not "this status ordinal minus one".
self._command_lock = threading.Lock()
self._connections_lock = threading.Lock()
self._connections: set[socket.socket] = set()
self._workers: list[threading.Thread] = []
self._finished_through = 0
self._finished_ordinals: set[int] = set()
self.directory = _bridge_directory()
self.descriptor = self.directory / f"cutter-{os.getpid()}-{self.session_id}.json"
@@ -654,7 +500,6 @@ class _BridgeServer(threading.Thread):
"current_file": self.dispatcher.current_file,
"cutter_version": self.dispatcher.cutter_version,
"identity_status": "partial",
"max_request_bytes": MAX_FRAME_BYTES,
}
if os.name == "nt":
_write_windows_descriptor(self.directory, self.descriptor, json.dumps(payload).encode("utf-8"))
@@ -696,46 +541,8 @@ class _BridgeServer(threading.Thread):
continue
except OSError:
break
with self._connections_lock:
self._workers = [
worker for worker in self._workers if worker.is_alive()
]
at_capacity = len(self._workers) >= MAX_CONNECTION_WORKERS
if at_capacity:
_close_socket(connection)
continue
self.connection_ordinal += 1
connection_ordinal = self.connection_ordinal
worker = threading.Thread(
target=self._serve_connection,
args=(connection, connection_ordinal),
name="rea-cutter-bridge-connection",
daemon=True,
)
with self._connections_lock:
self._connections.add(connection)
self._workers.append(worker)
try:
worker.start()
except RuntimeError:
with self._connections_lock:
self._connections.discard(connection)
if worker in self._workers:
self._workers.remove(worker)
self._note_connection_finished(connection_ordinal)
_close_socket(connection)
with self._connections_lock:
workers = list(self._workers)
for worker in workers:
worker.join(timeout=1)
self.server.close()
try:
self.descriptor.unlink(missing_ok=True)
except OSError:
pass
def _serve_connection(self, connection: socket.socket, connection_ordinal: int) -> None:
try:
with connection:
connection.settimeout(AUTH_FRAME_TIMEOUT_SECONDS)
authenticated = False
@@ -743,73 +550,60 @@ class _BridgeServer(threading.Thread):
hello = self._receive_frame(connection)
if not isinstance(hello, dict) or hello.get("kind") != "rea-auth-v2" or not isinstance(hello.get("nonce"), str) or len(hello["nonce"]) != 64:
self._send(connection, {"ok": False, "error": "authentication-failed"})
return
continue
client_nonce = hello["nonce"]
server_nonce = f"{connection_ordinal:016x}{secrets.token_hex(24)}"
server_proof = hmac.new(self.token.encode(), f"rea-cutter-server-v2:{self.session_id}:{client_nonce}:{server_nonce}".encode(), hashlib.sha256).hexdigest()
self._send(connection, {"session_id": self.session_id, "nonce": server_nonce, "proof": server_proof})
authenticated_frame = self._receive_frame(
connection, _authenticated_frame_timeout_seconds()
)
authenticated_frame = self._receive_frame(connection)
request_bytes = authenticated_frame.get("request") if isinstance(authenticated_frame, dict) else None
presented_proof = authenticated_frame.get("proof") if isinstance(authenticated_frame, dict) else None
if not isinstance(request_bytes, str) or not isinstance(presented_proof, str):
expected_proof = hmac.new(self.token.encode(), f"rea-cutter-client-v2:{self.session_id}:{client_nonce}:{server_nonce}:{request_bytes}".encode(), hashlib.sha256).hexdigest()
if not isinstance(request_bytes, str) or not isinstance(authenticated_frame.get("proof"), str) or not hmac.compare_digest(authenticated_frame["proof"], expected_proof):
self._send(connection, {"ok": False, "error": "authentication-failed"})
return
# Update the prefix and the request separately. One joined
# preimage would retain another copy of a maximum-sized command.
expected_proof = hmac.new(self.token.encode(), digestmod=hashlib.sha256)
expected_proof.update(
f"rea-cutter-client-v2:{self.session_id}:{client_nonce}:{server_nonce}:".encode()
)
expected_proof.update(request_bytes.encode())
if not hmac.compare_digest(presented_proof, expected_proof.hexdigest()):
self._send(connection, {"ok": False, "error": "authentication-failed"})
return
continue
request = json.loads(request_bytes)
if not isinstance(request, dict) or request.get("session_id") != self.session_id:
self._send(connection, {"ok": False, "error": "authentication-failed"})
return
continue
authenticated = True
with self._command_lock:
connection.settimeout(SOCKET_TIMEOUT_SECONDS)
if self.poisoned and request.get("kind") not in ("status", "ack"):
self._send_authenticated(connection, {
"ok": False,
"error": "session-poisoned-after-ui-timeout",
"execution_state": "not_started",
}, client_nonce, server_nonce)
return
if (
request.get("kind") == "command"
and not self.dispatcher.can_accept_command()
):
self._send_authenticated(connection, {
"ok": False,
"error": "completion-receipt-capacity-exceeded",
"execution_state": "not_started",
}, client_nonce, server_nonce)
return
request["session_id"] = self.session_id
event = threading.Event()
cancelled = threading.Event()
reply: dict[str, Any] = {}
request["_event"] = event
request["_cancelled"] = cancelled
self.dispatcher.dispatch.emit(request, reply)
if not event.wait(SOCKET_TIMEOUT_SECONDS):
cancelled.set()
self.poisoned = True
self._send_authenticated(connection, {
"ok": False,
"error": "ui-thread-request-timeout",
"execution_state": "unknown",
"message": "The UI operation may still complete after this timeout; restart Cutter before issuing further commands.",
}, client_nonce, server_nonce)
else:
if request.get("kind") == "status" and reply.get("ok") is True:
reply["completed_connection"] = self._completion_watermark()
self._send_authenticated(connection, reply, client_nonce, server_nonce)
connection.settimeout(SOCKET_TIMEOUT_SECONDS)
if self.poisoned and request.get("kind") not in ("status", "ack"):
self._send_authenticated(connection, {
"ok": False,
"error": "session-poisoned-after-ui-timeout",
"execution_state": "not_started",
}, client_nonce, server_nonce)
continue
if (
request.get("kind") == "command"
and not self.dispatcher.can_accept_command()
):
self._send_authenticated(connection, {
"ok": False,
"error": "completion-receipt-capacity-exceeded",
"execution_state": "not_started",
}, client_nonce, server_nonce)
continue
request["session_id"] = self.session_id
event = threading.Event()
cancelled = threading.Event()
reply: dict[str, Any] = {}
request["_event"] = event
request["_cancelled"] = cancelled
self.dispatcher.dispatch.emit(request, reply)
if not event.wait(SOCKET_TIMEOUT_SECONDS):
cancelled.set()
self.poisoned = True
self._send_authenticated(connection, {
"ok": False,
"error": "ui-thread-request-timeout",
"execution_state": "unknown",
"message": "The UI operation may still complete after this timeout; restart Cutter before issuing further commands.",
}, client_nonce, server_nonce)
else:
if request.get("kind") == "status" and reply.get("ok") is True:
reply["completed_connection"] = f"{connection_ordinal - 1:016x}"
self._send_authenticated(connection, reply, client_nonce, server_nonce)
except socket.timeout:
if authenticated:
self._send_authenticated(connection, {
@@ -820,217 +614,77 @@ class _BridgeServer(threading.Thread):
}, client_nonce, server_nonce)
except Exception as error:
self._send(connection, {"ok": False, "error": str(error)})
finally:
self._note_connection_finished(connection_ordinal)
with self._connections_lock:
self._connections.discard(connection)
def _note_connection_finished(self, ordinal: int) -> None:
"""Record one accepted connection as terminated.
The watermark is the longest prefix 1..W that has terminated. A later
status cannot report an earlier connection that is still reading or
still waiting to run.
"""
with self._connections_lock:
if ordinal <= self._finished_through or ordinal in self._finished_ordinals:
return
self._finished_ordinals.add(ordinal)
while self._finished_through + 1 in self._finished_ordinals:
self._finished_through += 1
self._finished_ordinals.discard(self._finished_through)
def _completion_watermark(self) -> str:
with self._connections_lock:
return f"{self._finished_through:016x}"
self.server.close()
try:
self.descriptor.unlink(missing_ok=True)
except OSError:
pass
@staticmethod
def _receive_frame(
connection: socket.socket,
timeout_seconds: float = AUTH_FRAME_TIMEOUT_SECONDS,
) -> dict[str, Any]:
def _receive_frame(connection: socket.socket) -> dict[str, Any]:
data = bytearray()
deadline = time.monotonic() + timeout_seconds
newline = -1
while newline < 0:
if len(data) > MAX_FRAME_BYTES:
raise _frame_limit_exceeded()
deadline = time.monotonic() + AUTH_FRAME_TIMEOUT_SECONDS
while b"\n" not in data:
if len(data) >= MAX_FRAME_BYTES:
raise ValueError("frame-limit-exceeded")
remaining = deadline - time.monotonic()
if remaining <= 0:
raise socket.timeout("authentication frame timed out")
connection.settimeout(remaining)
# MAX_FRAME_BYTES limits the JSON payload; the terminating LF is
# framing and may be the one byte beyond that payload budget.
part = connection.recv(min(65536, MAX_FRAME_BYTES + 1 - len(data)))
if not part:
raise ValueError("incomplete authentication frame")
scanned = len(data)
data.extend(part)
# The delimiter is one byte, so it cannot straddle a chunk.
# Search only the bytes just received.
newline = data.find(b"\n", scanned)
if newline > MAX_FRAME_BYTES:
raise _frame_limit_exceeded()
if newline != len(data) - 1:
line, remainder = bytes(data).split(b"\n", 1)
if len(line) > MAX_FRAME_BYTES or remainder:
raise ValueError("invalid framed request")
value = json.loads(bytes(data[:newline]))
value = json.loads(line)
return value if isinstance(value, dict) else {}
def _send_authenticated(self, connection: socket.socket, value: dict[str, Any], client_nonce: str, server_nonce: str) -> None:
if self._deliver_authenticated(connection, value, client_nonce, server_nonce):
encoded = self._encode_authenticated_response(value, client_nonce, server_nonce)
if encoded is None:
value = self._truncated_response(value)
encoded = self._encode_authenticated_response(value, client_nonce, server_nonce)
if encoded is None:
# This fixed-size fallback is checked through the same signed framing path.
value = {
"ok": False,
"error": "response-limit-exceeded",
"execution_state": value.get("execution_state", "unknown"),
"output": None,
"output_truncated": True,
"message": "The result exceeded the bridge frame limit; command completion state is preserved.",
}
encoded = self._encode_authenticated_response(value, client_nonce, server_nonce)
if encoded is None:
return
# Keep the recovery payload. Include the observed path when it
# fits. An explicit null path means unknown, so the client does
# not pair this generation with the descriptor's older path.
bounded = self._bounded_status_response(value)
if bounded is not None:
observed_path = value.get("current_file")
if isinstance(observed_path, str) and observed_path:
with_path = dict(bounded)
with_path["current_file"] = observed_path
with_path.pop("message", None)
if self._deliver_authenticated(
connection, with_path, client_nonce, server_nonce
):
return
if self._deliver_authenticated(
connection, bounded, client_nonce, server_nonce
):
return
command = self._bounded_command_response(value)
if command is not None:
observed_path = value.get("current_file")
if isinstance(observed_path, str) and observed_path:
with_path = dict(command)
with_path["current_file"] = observed_path
with_path.pop("message", None)
if self._deliver_authenticated(
connection, with_path, client_nonce, server_nonce
):
return
if self._deliver_authenticated(
connection, command, client_nonce, server_nonce
):
return
value = self._truncated_response(value)
if self._deliver_authenticated(connection, value, client_nonce, server_nonce):
return
# This fixed-size fallback is checked through the same signed framing path.
self._deliver_authenticated(connection, {
"ok": False,
"error": "response-limit-exceeded",
"execution_state": value.get("execution_state", "unknown"),
"output": None,
"output_truncated": True,
"message": "The result exceeded the bridge frame limit; command completion state is preserved.",
}, client_nonce, server_nonce)
def _deliver_authenticated(
self, connection: socket.socket, value: dict[str, Any], client_nonce: str, server_nonce: str
) -> bool:
proof = self._authenticated_proof(value, client_nonce, server_nonce)
if proof is None:
return False
try:
self._write_authenticated_frame(connection, value, proof)
connection.sendall(encoded)
except OSError:
# A disconnected caller must not fall through to another payload.
pass
return True
def _authenticated_proof(
self, value: dict[str, Any], client_nonce: str, server_nonce: str
) -> str | None:
def _encode_authenticated_response(self, value: dict[str, Any], client_nonce: str, server_nonce: str) -> bytes | None:
if not _within_response_limit(value):
return None
# The payload budget excludes the terminating LF. Fragments are
# measured and signed here, then discarded. The write pass encodes
# them again so a maximum-sized reply is not held in several copies.
frame_size = _AUTHENTICATED_FRAME_OVERHEAD
proof = hmac.new(self.token.encode(), digestmod=hashlib.sha256)
proof.update(
f"rea-cutter-response-v2:{self.session_id}:{client_nonce}:{server_nonce}:".encode()
)
for fragment in _response_fragments(value):
frame_size += len(_escape_json_fragment(fragment))
prefix = b'{"response":"'
suffix = b'","proof":"' + (b"0" * 64) + b'"}\n'
frame_size = len(prefix) + len(suffix)
response_parts: list[bytes] = []
escaped_parts: list[bytes] = []
for fragment in json.JSONEncoder(ensure_ascii=False, separators=(",", ":")).iterencode(value):
raw = fragment.encode("utf-8")
escaped = json.dumps(fragment, ensure_ascii=False)[1:-1].encode("utf-8")
frame_size += len(escaped)
if frame_size > MAX_FRAME_BYTES:
return None
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):
yield _escape_json_fragment(fragment)
yield _AUTHENTICATED_PROOF_MARK + proof.encode("ascii") + b'"}\n'
def _write_authenticated_frame(
self, connection: socket.socket, value: dict[str, Any], proof: str
) -> None:
pending = bytearray()
for chunk in self._iter_authenticated_frame(value, proof):
pending.extend(chunk)
if len(pending) >= _AUTHENTICATED_WRITE_BUFFER_BYTES:
connection.sendall(pending)
pending.clear()
if pending:
connection.sendall(pending)
@staticmethod
def _bounded_status_response(value: dict[str, Any]) -> dict[str, Any] | None:
if "completed_commands" not in value and "completed_connection" not in value:
return None
bounded: dict[str, Any] = {
"ok": value.get("ok") is True,
"session_id": value.get("session_id"),
"document_generation": value.get("document_generation"),
"identity_status": value.get("identity_status", "partial"),
"completed_commands": value.get("completed_commands"),
"current_file": None,
"message": (
"current_file is unknown because the status frame exceeded "
"REA_BRIDGE_MAX_REQUEST_BYTES. Do not reuse an older descriptor "
"path with this document_generation."
),
}
if "completed_connection" in value:
bounded["completed_connection"] = value["completed_connection"]
return bounded
@staticmethod
def _bounded_command_response(value: dict[str, Any]) -> dict[str, Any] | None:
if "execution_state" not in value and "document_generation" not in value:
return None
bounded: dict[str, Any] = {
"ok": value.get("ok") is True,
"output": None,
"execution_state": value.get("execution_state", "unknown"),
"output_truncated": True,
"current_file": None,
"message": (
"current_file is unknown because the result frame exceeded "
"REA_BRIDGE_MAX_REQUEST_BYTES. The command completion state and "
"document_generation are preserved. Do not reuse an older "
"descriptor path with this document_generation."
),
}
generation = value.get("document_generation")
if isinstance(generation, int) and not isinstance(generation, bool):
bounded["document_generation"] = generation
if value.get("identity_status") == "partial":
bounded["identity_status"] = "partial"
if value.get("ok") is not True and isinstance(value.get("error"), str):
bounded["error"] = value["error"]
return bounded
response_parts.append(raw)
escaped_parts.append(escaped)
proof = hmac.new(self.token.encode(), digestmod=hashlib.sha256)
proof.update(f"rea-cutter-response-v2:{self.session_id}:{client_nonce}:{server_nonce}:".encode())
for part in response_parts:
proof.update(part)
return prefix + b"".join(escaped_parts) + b'","proof":"' + proof.hexdigest().encode() + b'"}\n'
@staticmethod
def _truncated_response(value: dict[str, Any]) -> dict[str, Any]:
@@ -1088,17 +742,6 @@ class _BridgeServer(threading.Thread):
self.server.close()
except OSError:
pass
with self._connections_lock:
connections = list(self._connections)
for connection in connections:
try:
connection.shutdown(socket.SHUT_RDWR)
except OSError:
pass
try:
connection.close()
except OSError:
pass
class ReaCutterPlugin(cutter.CutterPlugin):
@@ -1132,128 +775,6 @@ def create_cutter_plugin() -> ReaCutterPlugin:
return ReaCutterPlugin()
def _json_object_key(key: Any) -> str:
"""Match CPython's JSON object-key text before quoting."""
if isinstance(key, str):
return key
if key is True:
return "true"
if key is False:
return "false"
if key is None:
return "null"
if isinstance(key, int):
return str(key)
if isinstance(key, float):
return json.dumps(key, separators=(",", ":"))
raise TypeError("keys must be str, int, float, bool or None")
def _response_fragments(value: Any):
"""Yield compact JSON pieces whose concatenation is the encoded value.
String contents are sliced before encoding so a large output never becomes
one frame-sized fragment. Each piece is at most ``_JSON_CHUNK_CHARS``
characters, or the six-byte ``\\u00XX`` expansion of one such slice.
"""
if isinstance(value, str):
yield '"'
for start in range(0, len(value), _JSON_CHUNK_CHARS):
yield json.dumps(
value[start:start + _JSON_CHUNK_CHARS], ensure_ascii=False
)[1:-1]
yield '"'
return
if isinstance(value, dict):
yield "{"
first = True
for key, item in value.items():
if not first:
yield ","
first = False
yield from _response_fragments(_json_object_key(key))
yield ":"
yield from _response_fragments(item)
yield "}"
return
if isinstance(value, (list, tuple)):
yield from _list_fragments(value)
return
token = _scalar_token(value)
if token is None:
raise TypeError("response value is not JSON")
yield token
def _scalar_token(value: Any) -> str | None:
"""Compact JSON for one scalar. None means the value needs structure."""
if type(value) is int:
return str(value)
if value is None:
return "null"
if value is True:
return "true"
if value is False:
return "false"
if isinstance(value, float):
return json.dumps(value, separators=(",", ":"))
return None
def _list_fragments(value: list[Any] | tuple[Any, ...]):
"""Encode a list, batching scalars into bounded pieces.
One ``json.dumps`` per number makes a dense array miss the client
deadline. A batch is at most ``_JSON_CHUNK_CHARS`` of compact JSON.
"""
yield "["
first = True
batch: list[str] = []
batch_len = 0
def flush_batch() -> str | None:
nonlocal first, batch, batch_len
if not batch:
return None
piece = ",".join(batch)
batch = []
batch_len = 0
if first:
first = False
return piece
return "," + piece
for item in value:
token = _scalar_token(item)
if token is None:
emitted = flush_batch()
if emitted is not None:
yield emitted
if first:
first = False
else:
yield ","
yield from _response_fragments(item)
continue
extra = len(token) if not batch else len(token) + 1
if batch and batch_len + extra > _JSON_CHUNK_CHARS:
emitted = flush_batch()
if emitted is not None:
yield emitted
extra = len(token)
batch.append(token)
batch_len += extra
emitted = flush_batch()
if emitted is not None:
yield emitted
yield "]"
def _escape_json_fragment(fragment: str) -> bytes:
"""Escape one raw JSON piece for the outer response string."""
return json.dumps(fragment, ensure_ascii=False)[1:-1].encode("utf-8")
def _within_response_limit(value: Any) -> bool:
pending = [value]
seen: set[int] = set()
@@ -1277,22 +798,13 @@ def _within_response_limit(value: Any) -> bool:
# Unencodable output also needs the signed omission fallback.
return False
size += current.count('"') + current.count("\\")
# A short escape is two JSON bytes and was already counted as one.
# Other controls expand to a six-byte \\u00XX sequence.
size += sum(
1 if ord(character) in (0x08, 0x09, 0x0A, 0x0C, 0x0D) else 5
for character in current
if ord(character) < 0x20
)
size += sum(5 for character in current if ord(character) < 0x20)
elif isinstance(current, dict):
identity = id(current)
if identity in seen:
return False
seen.add(identity)
count = len(current)
# Braces, one colon per entry, and commas between entries.
# Keys and values are counted when they are visited.
size += 2 + count + max(0, count - 1)
size += 2 + 4 * len(current)
if size > MAX_FRAME_BYTES:
return False
pending.extend(current.keys())
@@ -1302,29 +814,12 @@ def _within_response_limit(value: Any) -> bool:
if identity in seen:
return False
seen.add(identity)
count = len(current)
# Brackets and commas between items. Each item is counted below.
size += 2 + max(0, count - 1)
size += 2 + 2 * len(current)
if size > MAX_FRAME_BYTES:
return False
pending.extend(current)
elif current is None:
size += 4
elif current is True:
size += 4
elif current is False:
size += 5
elif isinstance(current, int):
size += len(str(current))
elif isinstance(current, float):
size += len(json.dumps(current, separators=(",", ":")))
else:
try:
size += len(
json.dumps(current, ensure_ascii=False, separators=(",", ":"))
)
except (TypeError, ValueError):
return False
size += 16
if size > MAX_FRAME_BYTES:
return False
return True
+12 -65
View File
@@ -1,91 +1,38 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.services.Analyzer;
import ghidra.app.util.headless.HeadlessScript;
import ghidra.program.model.listing.Program;
import ghidra.util.classfinder.ClassSearcher;
/**
* Disable the named auto-analyzers before default auto-analysis.
*
* REA_GHIDRA_DISABLED_ANALYZERS is process-wide, but Ghidra registers only the
* installed analyzers whose canAnalyze(program) accepts this program. A name an
* installed analyzer provides without being registered here is not applicable
* and is recorded instead of rejected; a name no installed analyzer provides
* aborts the import. The outcome is stored as a program property that the
* bridge reports during its handshake.
*/
/** Disable the named auto-analyzers before default auto-analysis. */
public final class ReaGhidraAnalysisOptions extends HeadlessScript {
static final String REPORT_OPTION = "REA disabled analyzers";
private static final String SETTING = "REA_GHIDRA_DISABLED_ANALYZERS";
@Override
public void run() throws Exception {
String[] arguments = getScriptArgs();
if (arguments.length != 1 || arguments[0].isBlank()) {
throw new IllegalArgumentException(
"ReaGhidraAnalysisOptions requires one comma-separated " + SETTING + " argument");
"ReaGhidraAnalysisOptions requires one comma-separated analyzer-name argument");
}
String[] names = arguments[0].split(",");
boolean[] registered = new boolean[names.length];
AutoAnalysisManager analysis = AutoAnalysisManager.getAnalysisManager(currentProgram);
Map<String, String> options = getCurrentAnalysisOptionsAndValues(currentProgram);
Set<String> installed = null;
for (int index = 0; index < names.length; index++) {
for (String requestedName : names) {
monitor.checkCancelled();
String name = names[index].trim();
names[index] = name;
if (analysis.getAnalyzer(name) != null) {
if (!options.containsKey(name)) {
throw new IllegalStateException(SETTING + " names " + name +
", which Ghidra registered for this program without an enablement option");
}
registered[index] = true;
continue;
}
if (installed == null) installed = installedAnalyzerNames();
if (!installed.contains(name)) {
throw new IllegalArgumentException(SETTING + " names " + name +
", which is not an analyzer in this Ghidra installation");
String name = requestedName.trim();
if (analysis.getAnalyzer(name) == null || !options.containsKey(name)) {
throw new IllegalArgumentException("Unknown Ghidra analyzer for this program: " + name);
}
}
for (int index = 0; index < names.length; index++) {
for (String requestedName : names) {
monitor.checkCancelled();
if (registered[index]) setAnalysisOption(currentProgram, names[index], "false");
setAnalysisOption(currentProgram, requestedName.trim(), "false");
}
options = getCurrentAnalysisOptionsAndValues(currentProgram);
JsonArray analyzers = new JsonArray();
for (int index = 0; index < names.length; index++) {
for (String requestedName : names) {
monitor.checkCancelled();
String name = names[index];
if (registered[index] && !"false".equals(options.get(name))) {
throw new IllegalStateException(SETTING + " names " + name +
", which remained enabled for this program");
String name = requestedName.trim();
if (!"false".equals(options.get(name))) {
throw new IllegalStateException("Ghidra analyzer remained enabled: " + name);
}
JsonObject analyzer = new JsonObject();
analyzer.addProperty("name", name);
analyzer.addProperty("status", registered[index] ? "disabled" : "not_applicable");
analyzers.add(analyzer);
}
JsonObject report = new JsonObject();
report.addProperty("format", "rea-ghidra-disabled-analyzers-v1");
report.add("analyzers", analyzers);
currentProgram.getOptions(Program.PROGRAM_INFO).setString(REPORT_OPTION, report.toString());
println("REA disabled analyzers: " + report);
}
/** The same analyzer universe AutoAnalysisManager filters by canAnalyze(program). */
private static Set<String> installedAnalyzerNames() {
Set<String> names = new HashSet<>();
for (Analyzer analyzer : ClassSearcher.getInstances(Analyzer.class)) {
names.add(analyzer.getName());
}
return names;
}
}
+20 -83
View File
@@ -178,10 +178,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
private List<FunctionEntry> procedureInventory;
private List<InventoryItem> stringInventory;
private DecompInterface decompiler;
/** Per-function native decompiler limit from the session; 0 leaves it unbounded. */
private int decompileTimeoutSeconds;
// DecompileProcess converts seconds to an int millisecond timer delay.
private static final int MAX_DECOMPILE_TIMEOUT_SECONDS = Integer.MAX_VALUE / 1000;
private final ReaGhidraExtensions analysisExtensions = new ReaGhidraExtensions();
private static AddressSpace sessionDefaultAddressSpace;
@@ -193,7 +189,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
}
Path descriptorPath = Path.of(arguments[0]);
SessionDescriptor descriptor = readDescriptor(descriptorPath);
decompileTimeoutSeconds = descriptor.decompileTimeoutSeconds();
// Windows retains a native immutable bearer descriptor lease until
// runtime_close; POSIX removes it after this read. Normal cleanup removes
// the Windows file, while abrupt owner death can leave private residue.
@@ -404,8 +399,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
writeSuccess(writer, request.id, result);
return;
}
decompileTimeoutSeconds = request.decompileTimeoutSeconds() == null
? descriptor.decompileTimeoutSeconds() : request.decompileTimeoutSeconds();
writeSuccess(writer, request.id, handleRequest(request, descriptor));
}
catch (RequestFailure failure) {
@@ -530,11 +523,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
String seeds = currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
.getString("REA analysis seeds", null);
if (seeds != null) result.add("analysis_seeds", JsonParser.parseString(seeds));
// Written by ReaGhidraAnalysisOptions before auto-analysis; REA checks it
// against the committed profile.
String disabledAnalyzers = currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
.getString("REA disabled analyzers", null);
if (disabledAnalyzers != null) result.add("disabled_analyzers", JsonParser.parseString(disabledAnalyzers));
// Written by ReaGhidraMipsSoftFloat after analysis; REA requires it
// exactly when the committed profile declares soft-float storage.
String softFloat = currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
@@ -645,9 +633,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
mapping.addProperty("initialized", block.isInitialized());
mapping.addProperty("loaded", block.isLoaded());
mapping.addProperty("overlay", block.isOverlay());
mapping.addProperty("readable", block.isRead());
mapping.addProperty("writable", block.isWrite());
mapping.addProperty("executable", block.isExecute());
mapping.addProperty("length", source.getLength());
FileBytes backing = source.getFileBytes().orElse(null);
int sourceIndex = backing == null ? -1 : files.indexOf(backing);
@@ -1107,8 +1092,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
Function function = resolveProcedure(requireString(params, "procedure"));
JsonObject result = new JsonObject();
result.addProperty("name", procedureName(function));
Symbol symbol = function.getSymbol();
result.add("symbol", symbol == null ? JsonNull.INSTANCE : symbolMetadata(symbol));
result.addProperty("entrypoint", canonicalAddress(function.getEntryPoint()));
result.addProperty("basicblock_count", basicBlocks(function).size());
JsonObject body = functionBody(function);
@@ -1280,11 +1263,9 @@ public final class ReaGhidraBridge extends HeadlessScript {
result.add("procedure", function == null ? JsonNull.INSTANCE : GSON.toJsonTree(canonicalAddress(function.getEntryPoint())));
result.addProperty("architecture", currentProgram.getLanguageID().getIdAsString());
result.addProperty("mode", currentProgram.getLanguage().getLanguageDescription().getVariant());
if (currentProgram.getLanguageID().getIdAsString().startsWith("MIPS:") ||
currentProgram.getLanguageID().getIdAsString().startsWith("ARM:")) {
if (currentProgram.getLanguageID().getIdAsString().startsWith("MIPS:")) {
JsonArray decoderContext = new JsonArray();
String regName = currentProgram.getLanguageID().getIdAsString().startsWith("MIPS:") ? "ISA_MODE" : "TMode";
var modeRegister = currentProgram.getRegister(regName);
var modeRegister = currentProgram.getRegister("ISA_MODE");
if (modeRegister != null) {
var modeValue = currentProgram.getProgramContext().getValue(modeRegister, address, false);
JsonObject context = new JsonObject();
@@ -1567,8 +1548,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
} catch (RequestFailure failure) {
// The listing, reference and CFG facts below do not depend on the
// native decompiler. Cancellation still ends the request.
if (!failure.code.equals("decompile_failed") &&
!failure.code.equals("decompile_timed_out")) throw failure;
if (!failure.code.equals("decompile_failed")) throw failure;
decompilation = null;
decompilerFailure = failure.getMessage().strip();
}
@@ -2151,8 +2131,13 @@ public final class ReaGhidraBridge extends HeadlessScript {
if (!address.isMemoryAddress() && !address.isExternalAddress()) {
continue;
}
JsonObject facts = symbolMetadata(symbol);
result.add(new InventoryItem(address, symbol.getName(true), facts, null));
JsonObject facts = new JsonObject();
facts.addProperty("primary", symbol.isPrimary());
facts.addProperty("dynamic", symbol.isDynamic());
facts.addProperty("external", symbol.isExternal());
facts.addProperty("type", normalizedSymbolType(symbol));
facts.addProperty("source", normalizedSource(symbol.getSource()));
result.add(new InventoryItem(address, symbol.getName(true), facts));
}
result.sort(INVENTORY_ORDER);
nameInventory = List.copyOf(result);
@@ -2196,8 +2181,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
InventoryItem item = new InventoryItem(
function.getEntryPoint(),
symbol == null ? function.getName() : symbol.getName(true),
facts,
symbol == null ? null : symbolMetadata(symbol)
facts
);
destination.add(new FunctionEntry(function, item));
}
@@ -2228,7 +2212,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
instance.isMissingNullTerminator() ? "missing" : "present_or_not_required"
);
facts.addProperty("byte_length", Math.max(0, data.getLength()));
result.add(new InventoryItem(data.getAddress(), value, facts, null));
result.add(new InventoryItem(data.getAddress(), value, facts));
}
result.sort(INVENTORY_ORDER);
stringInventory = List.copyOf(result);
@@ -3370,22 +3354,12 @@ public final class ReaGhidraBridge extends HeadlessScript {
}
DecompileResults results = decompiler.decompileFunction(
function,
decompileTimeoutSeconds,
0,
monitor
);
if (results.isCancelled()) {
throw new RequestFailure("decompile_cancelled", "Ghidra decompilation was cancelled");
}
// DecompInterface disposes the native process at the limit and starts a
// fresh one for the next request, so the session stays usable.
if (results.isTimedOut()) {
throw new RequestFailure(
"decompile_timed_out",
"Ghidra decompilation of " + canonicalAddress(function.getEntryPoint()) +
" exceeded the " + decompileTimeoutSeconds +
"-second configured decompilation limit (ghidra.decompile_timeout_seconds; launch seed REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS)"
);
}
if (!results.decompileCompleted()) {
throw new RequestFailure(
"decompile_failed",
@@ -4425,9 +4399,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
if (factsName != null) {
result.add(factsName, item.facts.deepCopy());
}
if (item.symbol != null) {
result.add("symbol", item.symbol.deepCopy());
}
return result;
}
@@ -4455,8 +4426,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
JsonObject result = new JsonObject();
result.addProperty("address", canonicalAddress(function.getEntryPoint()));
result.addProperty("name", procedureName(function));
Symbol symbol = function.getSymbol();
result.add("symbol", symbol == null ? JsonNull.INSTANCE : symbolMetadata(symbol));
result.add("classification", functionClassification(function));
result.add("body", functionBody(function));
return result;
@@ -4685,16 +4654,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
return value.isEmpty() ? "unknown" : value;
}
private static JsonObject symbolMetadata(Symbol symbol) {
JsonObject facts = new JsonObject();
facts.addProperty("primary", symbol.isPrimary());
facts.addProperty("dynamic", symbol.isDynamic());
facts.addProperty("external", symbol.isExternal());
facts.addProperty("type", normalizedSymbolType(symbol));
facts.addProperty("source", normalizedSource(symbol.getSource()));
return facts;
}
private static String normalizedSource(SourceType source) {
return source.name().toLowerCase(Locale.ROOT);
}
@@ -4735,11 +4694,10 @@ public final class ReaGhidraBridge extends HeadlessScript {
};
}
// The host queue owns session lifetime, including concurrent cancellation.
private static RequestFailure regexStackExhausted(String expression, String stage) {
return new RequestFailure("regex_stack_exhausted",
"Ghidra's Java regex engine exhausted its stack while " + stage +
" pattern " + expression + ".");
" pattern " + expression + ". Use literal mode or simplify the regex; the session remains usable.");
}
private static SessionDescriptor readDescriptor(Path path) throws IOException {
@@ -4750,15 +4708,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
Set<String> keys = new HashSet<>(DESCRIPTOR_KEYS);
if (parsed.isJsonObject() && parsed.getAsJsonObject().has("analysis_extensions"))
keys.add("analysis_extensions");
if (parsed.isJsonObject() && parsed.getAsJsonObject().has("decompile_timeout_seconds"))
keys.add("decompile_timeout_seconds");
JsonObject object = requireObject(parsed, keys);
int decompileTimeout = 0;
if (object.has("decompile_timeout_seconds")) {
decompileTimeout = requireInteger(object, "decompile_timeout_seconds");
if (decompileTimeout < 1 || decompileTimeout > MAX_DECOMPILE_TIMEOUT_SECONDS)
throw new IllegalArgumentException("REA session descriptor is invalid");
}
return new SessionDescriptor(
requireString(object, "transport"),
requireString(object, "endpoint_path"),
@@ -4767,24 +4717,12 @@ public final class ReaGhidraBridge extends HeadlessScript {
requireSha256(object, "target_sha256"),
requireString(object, "provider_version"),
requireString(object, "profile_digest"),
object.has("analysis_extensions") ? object.getAsJsonArray("analysis_extensions") : new JsonArray(),
decompileTimeout
object.has("analysis_extensions") ? object.getAsJsonArray("analysis_extensions") : new JsonArray()
);
}
private static Request parseRequest(String line, String expectedToken) {
JsonElement parsed = JsonParser.parseString(line);
Set<String> keys = new HashSet<>(REQUEST_KEYS);
if (parsed.isJsonObject() && parsed.getAsJsonObject().has("decompile_timeout_seconds"))
keys.add("decompile_timeout_seconds");
JsonObject object = requireObject(parsed, keys);
Integer timeout = null;
if (object.has("decompile_timeout_seconds")) {
JsonElement value = object.get("decompile_timeout_seconds");
timeout = value.isJsonNull() ? 0 : requireInteger(object, "decompile_timeout_seconds");
if (!value.isJsonNull() && (timeout < 1 || timeout > MAX_DECOMPILE_TIMEOUT_SECONDS))
throw new IllegalArgumentException("Invalid per-request decompile deadline");
}
JsonObject object = requireObject(JsonParser.parseString(line), REQUEST_KEYS);
int id = requireInteger(object, "id");
if (id <= 0 || !constantTimeEquals(requireString(object, "token"), expectedToken)) {
throw new IllegalArgumentException("Bridge request authentication failed");
@@ -4793,7 +4731,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
if (params == null || !params.isJsonObject()) {
throw new IllegalArgumentException("Bridge request parameters are invalid");
}
return new Request(id, requireString(object, "method"), params.getAsJsonObject(), timeout);
return new Request(id, requireString(object, "method"), params.getAsJsonObject());
}
private static JsonObject requireObject(JsonElement element, Set<String> keys) {
@@ -4962,7 +4900,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
.thenComparingInt(Reference::getOperandIndex)
.thenComparing(Reference::isPrimary);
private record InventoryItem(Address address, String value, JsonObject facts, JsonObject symbol) {}
private record InventoryItem(Address address, String value, JsonObject facts) {}
private record AddressNameRequest(String inputAddress, Address address, String name) {}
private record FunctionEntry(Function function, InventoryItem item) {}
private record FunctionAnalysisObservation(JsonObject dossier, List<String> limitations, JsonObject relationshipCoverage, boolean decompilerFailed) {}
@@ -4984,10 +4922,9 @@ public final class ReaGhidraBridge extends HeadlessScript {
String targetSha256,
String providerVersion,
String profileDigest,
JsonArray analysisExtensions,
int decompileTimeoutSeconds
JsonArray analysisExtensions
) {}
private record Request(int id, String method, JsonObject params, Integer decompileTimeoutSeconds) {}
private record Request(int id, String method, JsonObject params) {}
private static final class RequestFailure extends RuntimeException {
private final String code;
-193
View File
@@ -1,193 +0,0 @@
import ghidra.app.script.GhidraScript;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.mem.MemoryBlockSourceInfo;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
/**
* Rebuild a GameCube/Wii DOL memory map from the validated load ranges REA's TypeScript
* admission wrote. BinaryLoader maps the whole file once at address 0; this
* script replaces that block with text, data, and BSS blocks at their load addresses.
*/
public final class ReaGhidraPrepareDol extends GhidraScript {
// Canonical v2 header, 18 section rows, 12 gap rows and entry, using uint32 hex.
private static final int MAX_PLAN_BYTES = 13 + 18 * 49 + 12 * 30 + 17;
private static final class Section {
final String kind;
final int index;
final long fileOffset;
final long address;
final long size;
Section(String kind, int index, long fileOffset, long address, long size) {
this.kind = kind;
this.index = index;
this.fileOffset = fileOffset;
this.address = address;
this.size = size;
}
}
private record BssGap(long address, long size) {}
@Override
public void run() throws Exception {
String[] arguments = getScriptArgs();
if (arguments.length != 1) {
throw new IllegalArgumentException(
"GameCube DOL preparation requires exactly one section table path"
);
}
if (!currentProgram.getExecutableFormat().equals("Raw Binary") ||
!currentProgram.getLanguageID().getIdAsString().startsWith("PowerPC:BE:32:")) {
throw new IllegalArgumentException(
"GameCube DOL preparation requires a BinaryLoader import in big-endian 32-bit PowerPC"
);
}
Path tablePath = Path.of(arguments[0]);
if (!Files.isRegularFile(tablePath, LinkOption.NOFOLLOW_LINKS)) {
throw new IllegalArgumentException("GameCube DOL section table is not a regular file");
}
if (Files.size(tablePath) > MAX_PLAN_BYTES) {
throw new IllegalArgumentException("GameCube DOL plan exceeds the canonical format's byte bound");
}
List<Section> sections = new ArrayList<>();
List<BssGap> bssGaps = new ArrayList<>();
int expectedSections = -1;
int expectedGaps = -1;
long entry = -1;
for (String line : Files.readAllLines(tablePath)) {
String[] fields = line.trim().split("\\s+");
switch (fields[0]) {
case "plan" -> {
requireFields(fields, 4, line);
if (expectedSections >= 0 || !fields[1].equals("2")) {
throw new IllegalArgumentException("Duplicate or unsupported GameCube DOL plan header");
}
expectedSections = Integer.parseInt(fields[2]);
expectedGaps = Integer.parseInt(fields[3]);
// A DOL has 18 slots, including at most 11 data slots and 12 BSS gaps.
if (expectedSections < 1 || expectedSections > 18 || expectedGaps < 0 || expectedGaps > 12) {
throw new IllegalArgumentException("GameCube DOL plan counts exceed the format's slot bounds");
}
}
case "section" -> {
requireFields(fields, 6, line);
if (expectedSections < 0 || sections.size() >= expectedSections) {
throw new IllegalArgumentException("GameCube DOL section exceeds its declared count");
}
sections.add(new Section(
fields[1],
Integer.parseInt(fields[2]),
Long.decode(fields[3]),
Long.decode(fields[4]),
Long.decode(fields[5])
));
}
case "bss-gap" -> {
requireFields(fields, 3, line);
if (expectedGaps < 0 || bssGaps.size() >= expectedGaps) {
throw new IllegalArgumentException("GameCube DOL BSS gap exceeds its declared count");
}
long address = Long.decode(fields[1]);
long size = Long.decode(fields[2]);
if (address < 0 || size <= 0) {
throw new IllegalArgumentException("GameCube DOL BSS gap is not a positive range");
}
bssGaps.add(new BssGap(address, size));
}
case "entry" -> {
requireFields(fields, 2, line);
entry = Long.decode(fields[1]);
}
default -> throw new IllegalArgumentException("Unknown GameCube DOL table line: " + line);
}
}
if (expectedSections != sections.size() || expectedGaps != bssGaps.size() || entry < 0) {
throw new IllegalArgumentException("GameCube DOL section table is incomplete");
}
Memory memory = currentProgram.getMemory();
List<FileBytes> files = memory.getAllFileBytes();
if (files.size() != 1) {
throw new IllegalStateException("GameCube DOL preparation requires exactly one imported file image");
}
FileBytes fileBytes = files.get(0);
AddressSpace space = currentProgram.getAddressFactory().getDefaultAddressSpace();
// Remove the whole-file block before creating section blocks, so no two blocks claim one address.
for (MemoryBlock block : memory.getBlocks()) {
for (MemoryBlockSourceInfo source : block.getSourceInfos()) {
if (source.getFileBytes().isPresent() && source.getFileBytes().get().equals(fileBytes)) {
memory.removeBlock(block, monitor);
break;
}
}
}
for (Section section : sections) {
if (section.fileOffset + section.size > fileBytes.getSize()) {
throw new IllegalArgumentException(
"GameCube DOL " + section.kind + " " + section.index + " exceeds the imported file"
);
}
boolean text = section.kind.equals("text");
if (!text && !section.kind.equals("data")) {
throw new IllegalArgumentException("Unknown GameCube DOL section kind: " + section.kind);
}
MemoryBlock block = memory.createInitializedBlock(
"." + section.kind + section.index,
space.getAddress(section.address),
fileBytes,
section.fileOffset,
section.size,
false
);
block.setRead(true);
block.setWrite(!text);
block.setExecute(text);
}
// Apply the domain owner's exact uninitialized ranges; do not reinterpret BSS here.
int gapIndex = 0;
for (BssGap gap : bssGaps) {
MemoryBlock block = memory.createUninitializedBlock(
".bss" + gapIndex++,
space.getAddress(gap.address()),
gap.size(),
false
);
block.setRead(true);
block.setWrite(true);
block.setExecute(false);
}
Address entryAddress = space.getAddress(entry);
MemoryBlock entryBlock = memory.getBlock(entryAddress);
if (entryBlock == null || !entryBlock.isExecute()) {
throw new IllegalArgumentException("GameCube DOL entry must lie in an executable section");
}
addEntryPoint(entryAddress);
if (!disassemble(entryAddress) || createFunction(entryAddress, "__start") == null) {
throw new IllegalStateException("GameCube DOL entry cannot be decoded as a function");
}
currentProgram.getOptions(Program.PROGRAM_INFO).setBoolean("REA raw binary prepared", true);
currentProgram.getOptions(Program.PROGRAM_INFO).setBoolean("REA GameCube DOL prepared", true);
}
private static void requireFields(String[] fields, int count, String line) {
if (fields.length != count) {
throw new IllegalArgumentException("Malformed GameCube DOL table line: " + line);
}
}
}
+35 -145
View File
@@ -8,100 +8,12 @@ API access on this thread: moving dispatch to a worker can deadlock Hopper.
import json
import hmac
import os
import re
import socket
from typing import Any, Optional, Protocol, Sequence
BAD_ADDRESSES = (-1, 0xFFFFFFFFFFFFFFFF, None)
# The native label setter truncates NSString names at this UTF-16 extent.
HOPPER_SYMBOL_NAME_UTF16_UNITS = 1024
# Request JSON payload budget in bytes, excluding the trailing LF.
# REA_BRIDGE_MAX_REQUEST_BYTES selects it. Unset stays 16777216.
# 7464 is the authenticated Cutter frame of the largest bounded symbol name.
# Cutter's 64-receipt recovery status is smaller with a null path. A path
# that does not fit is null, not an older descriptor path.
# 536870887 is Node's 536870888-character string ceiling minus the framing LF.
# The ceiling is the encoded payload; JSON expansion is measured before it.
_BRIDGE_MAX_REQUEST_BYTES_SETTING = "REA_BRIDGE_MAX_REQUEST_BYTES"
DEFAULT_BRIDGE_MAX_REQUEST_BYTES = 16 * 1024 * 1024
MINIMUM_BRIDGE_MAX_REQUEST_BYTES = 7464
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES = 536870887
def _bridge_max_request_bytes_constraint():
return (
"REA_BRIDGE_MAX_REQUEST_BYTES must be a positive decimal integer "
"from %d to %d bytes; the default is %d bytes (16 MiB). "
"A value below %d bytes cannot carry the largest admitted "
"symbol-name command, and a larger value is refused so the encoded "
"payload plus its framing LF stays one string under Node's "
"single-string ceiling."
% (
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
DEFAULT_BRIDGE_MAX_REQUEST_BYTES,
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
)
)
def _parse_bridge_max_request_bytes(raw):
"""Refuse a non-decimal or out-of-range ceiling. Never fall back."""
constraint = _bridge_max_request_bytes_constraint()
if isinstance(raw, bool):
raise RuntimeError(constraint)
if isinstance(raw, int):
value = raw
elif isinstance(raw, str):
# ASCII digits only. str.isdigit() accepts numerals such as U+0668
# that the TypeScript setting rejects.
if re.fullmatch(r"[1-9][0-9]*", raw) is None:
raise RuntimeError(constraint)
value = int(raw)
else:
raise RuntimeError(constraint)
if (
value < MINIMUM_BRIDGE_MAX_REQUEST_BYTES
or value > MAXIMUM_BRIDGE_MAX_REQUEST_BYTES
):
raise RuntimeError(constraint)
return value
def _resolve_bridge_max_request_bytes():
"""Prefer the bootstrap injection, then the process environment, then 16 MiB."""
injected = globals().get(_BRIDGE_MAX_REQUEST_BYTES_SETTING)
if injected is not None:
return _parse_bridge_max_request_bytes(injected), "setting"
raw = os.environ.get(_BRIDGE_MAX_REQUEST_BYTES_SETTING)
if raw is None:
return DEFAULT_BRIDGE_MAX_REQUEST_BYTES, "default"
return _parse_bridge_max_request_bytes(raw), "setting"
MAX_REQUEST_BYTES, BRIDGE_MAX_REQUEST_BYTES_SOURCE = (
_resolve_bridge_max_request_bytes()
)
def _bridge_max_request_bytes_recovery():
return (
"The effective REA_BRIDGE_MAX_REQUEST_BYTES value is %d bytes "
"(source %s; default %d, floor %d, maximum %d). "
"Set REA_BRIDGE_MAX_REQUEST_BYTES in the environment that starts REA "
"to a decimal integer from %d to %d, then restart REA. "
"Restart Hopper or Cutter from that same environment so the bridge "
"uses this ceiling."
% (
MAX_REQUEST_BYTES,
BRIDGE_MAX_REQUEST_BYTES_SOURCE,
DEFAULT_BRIDGE_MAX_REQUEST_BYTES,
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
)
)
_rea_session_document = None
_search_inventory_cache = {}
_pseudocode_cache = {}
@@ -324,27 +236,20 @@ def _name_matches(document, address, name):
return observed == name or (not name and observed is None)
def _bookmark_api(document, name):
method = getattr(document, name, None)
if not callable(method):
raise CapabilityUnavailableError(
"Hopper's public Python API does not expose %s" % name
)
return method
def _bookmark_name(document, address):
return _bookmark_api(document, "getBookmarkName")(address)
getter = getattr(document, "getBookmarkName", None)
if not callable(getter):
raise CapabilityUnavailableError(
"Hopper's public Python API does not expose getBookmarkName"
)
return getter(address)
def _address_selection(document, value=None):
"""Own the coordinate and whether it was selected by symbol or address."""
def _address(document, value=None):
"""Keep explicit hexadecimal coordinates distinct from symbol names."""
if value is None:
return document.getCurrentAddress(), "cursor"
return document.getCurrentAddress()
_api_text(value, "Address")
if value.startswith("file-offset:"):
raise InvalidRequestError("Hopper does not support file-offset: selectors; select Ghidra for source-file coordinates or use a Hopper analysis address")
source = "address"
if value.lower().startswith("0x"):
try:
address = int(value, 16)
@@ -355,8 +260,6 @@ def _address_selection(document, value=None):
# Prefer an observed symbol, retaining bare-hex compatibility only
# when Hopper has no symbol with the supplied name.
address = document.getAddressForName(value)
if address not in BAD_ADDRESSES:
source = "symbol"
if address in BAD_ADDRESSES:
try:
address = int(value, 16)
@@ -364,11 +267,7 @@ def _address_selection(document, value=None):
raise InvalidRequestError("Unknown Hopper address or name: %s" % value) from error
if not 0 <= address < 0xFFFFFFFFFFFFFFFF:
raise InvalidRequestError("Address is outside Hopper's usable unsigned 64-bit range: %s" % value)
return address, source
def _address(document, value=None):
return _address_selection(document, value)[0]
return address
def _segment(document, address):
@@ -379,15 +278,10 @@ def _segment(document, address):
def _procedure(document, value=None):
address, source = _address_selection(document, value)
address = document.getCurrentAddress() if value is None else _address(document, value)
result = _segment(document, address).getProcedureAtAddress(address)
if result is None:
raise InvalidRequestError("No procedure exists at the requested address")
if source == "symbol" and address != result.getEntryPoint():
raise InvalidRequestError(
"Symbol %s at %s is inside procedure %s at %s; select its entry name/address or use resolve_containing_procedure for explicit containment"
% (value, _hex(address), _procedure_name(result), _hex(result.getEntryPoint()))
)
return result
@@ -400,7 +294,6 @@ def _procedure_identity(procedure):
return {
"address": _hex(procedure.getEntryPoint()),
"name": _procedure_name(procedure),
"symbol": None,
"classification": None,
"body": {
"available": False,
@@ -847,7 +740,6 @@ def _dispatch(method, params):
"version": "1.0.0",
"run_id": REA_RUN_ID,
"process_id": os.getpid(),
"max_request_bytes": MAX_REQUEST_BYTES,
}
if method in ("shutdown", "shutdown_document"):
document = _session_document()
@@ -1071,7 +963,6 @@ def _dispatch(method, params):
length = sum(max(0, _basic_block_end(procedure, block) - block.getStartingAddress()) for block in blocks)
return {
"name": _procedure_name(procedure),
"symbol": None,
"entrypoint": _hex(procedure.getEntryPoint()),
"basicblock_count": procedure.getBasicBlockCount(),
"length": length,
@@ -1112,24 +1003,39 @@ def _dispatch(method, params):
observed = getter(address)
return observed == comment or (comment == "" and observed is None)
if method == "list_bookmarks":
return [{"address": _hex(item), "name": _bookmark_name(document, item)} for item in _bookmark_api(document, "getBookmarks")()]
return [{"address": _hex(item), "name": _bookmark_name(document, item)} for item in document.getBookmarks()]
if method == "set_bookmark":
address = _address(document, params.get("address"))
_segment(document, address)
name = params.get("name")
if name is not None:
_api_text(name, "Bookmark name")
has_bookmark = _bookmark_api(document, "hasBookmarkAtAddress")
has_bookmark = getattr(document, "hasBookmarkAtAddress", None)
if not callable(has_bookmark):
raise CapabilityUnavailableError(
"Hopper's public Python API does not expose hasBookmarkAtAddress"
)
exists = has_bookmark(address)
if name is None and exists:
# Omission means preserve the existing bookmark and its name.
return True
_bookmark_api(document, "getBookmarkName")
if not callable(getattr(document, "getBookmarkName", None)):
raise CapabilityUnavailableError(
"Hopper's public Python API does not expose getBookmarkName"
)
if name == "" and exists:
rename = _bookmark_api(document, "renameBookmarkAtAddress")
rename = getattr(document, "renameBookmarkAtAddress", None)
if not callable(rename):
raise CapabilityUnavailableError(
"Hopper's public Python API does not expose renameBookmarkAtAddress"
)
rename(address, name)
else:
setter = _bookmark_api(document, "setBookmarkAtAddress")
setter = getattr(document, "setBookmarkAtAddress", None)
if not callable(setter):
raise CapabilityUnavailableError(
"Hopper's public Python API does not expose setBookmarkAtAddress"
)
setter(address, name)
if not has_bookmark(address):
return False
@@ -1141,12 +1047,10 @@ def _dispatch(method, params):
address = _address(document, params.get("address"))
# Permit removing a legacy orphan bookmark, but do not report success
# for an unmapped address with no bookmark to remove.
has_bookmark = _bookmark_api(document, "hasBookmarkAtAddress")
remove_bookmark = _bookmark_api(document, "removeBookmarkAtAddress")
if not has_bookmark(address):
if not document.hasBookmarkAtAddress(address):
_segment(document, address)
remove_bookmark(address)
return not has_bookmark(address)
document.removeBookmarkAtAddress(address)
return not document.hasBookmarkAtAddress(address)
raise InvalidRequestError("Unknown bridge method")
@@ -1156,23 +1060,9 @@ def _serve_connection(connection):
should_stop = False
try:
while True:
# Bound the read itself. The payload budget excludes LF, so permit
# one additional byte for the delimiter while still refusing an
# oversized unterminated peer without waiting for EOF.
line = file.readline(MAX_REQUEST_BYTES + 1)
line = file.readline()
if not line:
break
if not line.endswith(b"\n"):
message = (
"Bridge request exceeds the %d-byte payload limit. %s"
% (MAX_REQUEST_BYTES, _bridge_max_request_bytes_recovery())
if len(line) > MAX_REQUEST_BYTES
else "Incomplete bridge request frame"
)
_write_message(file, {"id": 0, "error": _safe_diagnostic(
InvalidRequestError(message)
)})
break
request_id = None
authenticated = False
should_stop = False
-26
View File
@@ -1,26 +0,0 @@
import Foundation
import CoreFoundation
struct AccessibilityValue {
let value: Any
let gap: String?
}
/** Preserve JSON scalars; report successful reads that JSON cannot represent faithfully. */
func captureAccessibilityValue(_ source: Any?) -> AccessibilityValue {
guard let source else { return AccessibilityValue(value: NSNull(), gap: nil) }
if let text = source as? String { return AccessibilityValue(value: text, gap: nil) }
if let number = source as? NSNumber {
if CFGetTypeID(number) == CFBooleanGetTypeID() {
return AccessibilityValue(value: number.boolValue, gap: nil)
}
let value = number.doubleValue
let encoding = String(cString: number.objCType)
let floating = encoding == "f" || encoding == "d"
if number is NSDecimalNumber || !value.isFinite || (value == 0 && value.sign == .minus) || (!floating && abs(value) > 9_007_199_254_740_991) {
return AccessibilityValue(value: NSNull(), gap: "AXValue number \(number.stringValue) cannot be represented faithfully as a JSON number")
}
return AccessibilityValue(value: number, gap: nil)
}
return AccessibilityValue(value: NSNull(), gap: "AXValue type \(type(of: source)) is not a supported JSON scalar")
}
+4 -9
View File
@@ -43,13 +43,10 @@ func windowBounds(_ element: AXUIElement) -> CGRect? {
return CGRect(origin: point, size: size)
}
func observe(_ request: Request) async throws -> [String: Any] {
guard let app = NSRunningApplication(processIdentifier: request.pid) else {
try fail("pid-not-application", "LaunchServices does not expose an application for the selected PID; this does not establish that the process is absent")
guard let app = NSRunningApplication(processIdentifier: request.pid), let url = app.executableURL else {
try fail("target-mismatch", "Selected PID does not expose an application executable URL")
}
guard let url = app.executableURL else {
try fail("application-executable-unavailable", "The selected application does not expose an executable URL; select a PID with an identifiable application executable")
}
guard let canonicalPointer = realpath(url.path, nil) else { try fail("executable-path-unavailable", "Running executable path cannot be resolved") }
guard let canonicalPointer = realpath(url.path, nil) else { try fail("target-mismatch", "Running executable path cannot be resolved") }
defer { free(canonicalPointer) }
let canonicalExecutable = String(cString: canonicalPointer)
guard canonicalExecutable == request.executable else {
@@ -120,9 +117,7 @@ func observe(_ request: Request) async throws -> [String: Any] {
let childCountResult = childCount(element)
var actions: CFArray?
AXUIElementCopyActionNames(element, &actions)
let observedValue = captureAccessibilityValue(attribute(element, kAXValueAttribute))
if let gap = observedValue.gap { gaps.append("\(gap) at path \(path)"); truncated = true }
nodes.append(["path": path, "role": text(element, kAXRoleAttribute), "title": text(element, kAXTitleAttribute), "value": observedValue.value, "actions": actions as? [String] ?? [], "children_count": childCountResult.value.map { $0 as Any } ?? NSNull()])
nodes.append(["path": path, "role": text(element, kAXRoleAttribute), "title": text(element, kAXTitleAttribute), "value": text(element, kAXValueAttribute), "actions": actions as? [String] ?? [], "children_count": childCountResult.value.map { $0 as Any } ?? NSNull()])
guard let totalChildren = childCountResult.value else {
truncated = true
gaps.append("AX child count unavailable at path \(path): AXError \(childCountResult.error ?? -1)")
+6 -3
View File
@@ -661,15 +661,18 @@ def trace(debugger, config):
open(observation_path, "wb")
)
process = target.Launch(_launch_info(target, config, listener), error)
process_created = process.IsValid()
if not error.Success() or not process_created:
if not error.Success() or not process.IsValid():
exit_observed = _retire_failed_launch(
process, config, listener, stdout, stderr
)
return {
"status": "launch-error",
"error": error.GetCString(),
"target_process_created": process_created,
**(
{"target_process_created": True}
if process.IsValid()
else {}
),
**(
{"target_process_exit_observed": True}
if exit_observed
-67
View File
@@ -25,73 +25,6 @@ spec.loader.exec_module(tracer)
class NativeTraceBudgetTests(unittest.TestCase):
def test_invalid_launch_process_reports_explicit_no_creation(self):
class InvalidProcess:
def IsValid(self):
return False
def Kill(self):
raise AssertionError("an invalid process cannot own target cleanup")
class LaunchError:
def Success(self):
return False
def GetCString(self):
return "No such file or directory: fixture/absent"
class Target:
def IsValid(self):
return True
def Launch(self, _launch_info, _error):
return InvalidProcess()
class Debugger:
def SetAsync(self, _enabled):
pass
def GetInstanceName(self):
return "fixture"
def CreateTarget(self, *_arguments):
return Target()
class Pipe:
def __init__(self, *_arguments):
self.closed = False
pipes.append(self)
def start(self):
pass
def close(self, expected_exit=False):
self.closed = True
pipes = []
debugger_api = types.SimpleNamespace(SetInternalVariable=lambda *_arguments: None)
with patch.object(tracer.lldb, "SBDebugger", debugger_api, create=True), patch.object(
tracer.lldb, "SBError", LaunchError, create=True
), patch.object(tracer.lldb, "SBListener", lambda _name: object(), create=True), patch.object(
tracer, "_hash_file", return_value="expected"
), patch.object(tracer, "_file_identity", return_value=(1, 2)), patch.object(
tracer, "_module_identity", return_value=("fixture", (1, 2), "fixture-uuid")
), patch.object(tracer, "_launch_info", return_value=object()), patch.object(
tracer, "_OutputPipe", Pipe
):
result = tracer.trace(Debugger(), {
"executable": "fixture", "architecture": "arm64", "expected_sha256": "expected",
"breakpoints": [], "working_directory": "fixture/absent",
"stdout_path": "stdout", "stdout_capture_path": "stdout-capture",
"stderr_path": "stderr", "stderr_capture_path": "stderr-capture",
})
self.assertEqual(result["status"], "launch-error")
self.assertEqual(result["error"], "No such file or directory: fixture/absent")
self.assertIs(result["target_process_created"], False)
self.assertNotIn("target_process_exit_observed", result)
self.assertEqual(len(pipes), 2)
self.assertTrue(all(pipe.closed for pipe in pipes))
def test_failed_launch_does_not_infer_exit_from_a_failed_kill(self):
class Process:
def IsValid(self):
+12 -38
View File
@@ -15,11 +15,6 @@ import sys
PROFILE = "pwntools@4.15.0;pyelftools@0.33;unicorn@2.1.2"
PACKAGES = {"pwntools": "4.15.0", "pyelftools": "0.33", "unicorn": "2.1.2"}
OUTPUT_BYTES = 64 * 1024 * 1024
# (e_machine, ELF class bits, byte order) -> architecture pwntools must also report.
PROFILES = {(62, 64, "little"): "amd64", (3, 32, "little"): "i386", (40, 32, "little"): "arm",
(8, 32, "little"): "mips", (8, 32, "big"): "mips"}
PROFILE_SCOPE = ("Layout profile requires ELF64 x86-64, ELF32 i386, ELF32 ARM little-endian "
"or ELF32 MIPS little- or big-endian objects.")
class LayoutFailure(Exception):
@@ -121,16 +116,13 @@ def inspect_elf(path, cache):
raise LayoutFailure("format", "Selected object does not contain an ELF header.")
if len(content) < 16:
raise LayoutFailure("format", "Selected ELF identification is truncated.")
if content[4] not in (1, 2) or content[5] not in (1, 2):
raise LayoutFailure("unsupported-target", "Selected ELF declares an unknown class or byte order.")
bits, header_size = (32, 52) if content[4] == 1 else (64, 64)
order = "little" if content[5] == 1 else "big"
if len(content) < header_size:
raise LayoutFailure("format", f"Selected ELF{bits} header is truncated.")
profile = (int.from_bytes(content[18:20], order), bits, order)
if profile not in PROFILES:
raise LayoutFailure("unsupported-target", PROFILE_SCOPE)
if int.from_bytes(content[16:18], order) not in (1, 2, 3):
if content[4] != 2 or content[5] != 1:
raise LayoutFailure("unsupported-target", "Initial layout profile requires ELF64 little-endian objects.")
if len(content) < 64:
raise LayoutFailure("format", "Selected ELF64 header is truncated.")
if int.from_bytes(content[18:20], "little") != 62:
raise LayoutFailure("unsupported-target", "Initial layout profile requires x86-64 ELF objects.")
if int.from_bytes(content[16:18], "little") not in (1, 2, 3):
raise LayoutFailure("unsupported-target", "Selected ELF type is outside the EXEC/DYN/REL layout profile; recorded core analysis is separate.")
from pwnlib.context import context
with context.local(cache_dir=str(cache), log_level="warning"):
@@ -140,11 +132,6 @@ def inspect_elf(path, cache):
try:
headers = ELFFile(io.BytesIO(content))
section_count = headers.num_sections()
if section_count:
location(headers.header.e_shoff, section_count * headers.header.e_shentsize, len(content))
segment_count = headers.num_segments()
if segment_count:
location(headers.header.e_phoff, segment_count * headers.header.e_phentsize, len(content))
if headers.header.e_shstrndx == 0xffff and section_count == 0:
raise LayoutFailure("format", "ELF extended section-name index requires a declared section-zero header.")
raw_name_index = headers.header.e_shstrndx
@@ -160,21 +147,9 @@ def inspect_elf(path, cache):
if names_header.sh_type != "SHT_STRTAB":
raise LayoutFailure("format", f"ELF section-name table {section_name_index} is not SHT_STRTAB.")
location(names_header.sh_offset, names_header.sh_size, len(content))
except (ELFError, ValueError, OverflowError, AssertionError, IndexError) as error:
except ELFError as error:
raise LayoutFailure("format", str(error)) from error
class LayoutELF(ELF):
def search(self, needle, writable=False, executable=False):
# This adapter exposes structural facts, GOT/PLT and mitigation
# indicators, not kernel config/version discovery. Upstream's
# constructor searches virtual memory for those unrelated
# heuristics, materializing NOBITS tails and interpreting
# ordinary IKCFG_ST data as a compressed kernel config.
return iter(())
def _populate_kernel_version(self):
# Kernel banner decoding is outside this report too.
return None
def _get_section_name(self, header):
# SHN_UNDEF declares no names; upstream would otherwise read
# strings at the inactive section-zero offset.
@@ -193,8 +168,8 @@ def inspect_elf(path, cache):
if type is None or section.header.sh_type == type)
try:
image = LayoutELF(str(path), checksec=False)
if (image.arch, image.bits, image.endian) != (PROFILES[profile], bits, order):
raise LayoutFailure("unsupported-target", PROFILE_SCOPE)
if image.arch != "amd64" or image.bits != 64 or image.endian != "little":
raise LayoutFailure("unsupported-target", "Initial layout profile requires x86-64 ELF64 little-endian objects.")
if image.header.e_type not in ("ET_EXEC", "ET_DYN", "ET_REL"):
raise LayoutFailure("unsupported-target", "Selected ELF type is outside the EXEC/DYN/REL layout profile; recorded core analysis is separate.")
length = len(content)
@@ -216,7 +191,7 @@ def inspect_elf(path, cache):
})
if h.sh_type in ("SHT_SYMTAB", "SHT_DYNSYM"):
if h.sh_entsize < image.structs.Elf_Sym.sizeof():
raise LayoutFailure("format", f"ELF symbol entry is smaller than the decoded Elf{bits}_Sym structure.")
raise LayoutFailure("format", "ELF symbol entry is smaller than the decoded Elf64_Sym structure.")
if h.sh_link >= len(all_sections) or all_sections[h.sh_link].header.sh_type != "SHT_STRTAB":
raise LayoutFailure("format", f"ELF symbol table {index} links to invalid string table {h.sh_link}.")
strings = all_sections[h.sh_link]
@@ -319,7 +294,6 @@ def inspect_elf(path, cache):
"Relocatable relocation targets with SHN_UNDEF or inactive SHT_NULL headers retain their reported section index/offset as unknown; they do not establish an associated target section.",
"GOT/PLT are derived upstream convenience maps; PLT inference can emulate selected instructions in Unicorn. They do not establish target runtime behavior; aliases may collapse and warnings may indicate incomplete coverage. Their completeness is unknown.",
"Mitigations are upstream static heuristics, not runtime protection. ET_DYN does not prove an executable; absent canary symbols do not prove every function unprotected.",
"Kernel configuration/version discovery is not performed; these memory-content scans do not supply the reported structural, linkage or mitigation facts.",
"This profile inspects ELF EXEC/DYN/REL layout only. It does not analyze recorded cores, resolve loaded libraries, launch the target as a host process or start a debugger.",
"Symbol/relocation inventories reflect original section tables. A sectionless image can still report dynamic dependency names; missing tables do not prove absence of dynamic symbols or relocations.",
*(["Absent section-name table: section-dependent RELRO/canary heuristics may be incomplete; protection coverage is unknown."] if all_sections and section_name_index == 0 else []),
@@ -328,7 +302,7 @@ def inspect_elf(path, cache):
}
except LayoutFailure:
raise
except (ELFError, ValueError, OverflowError, AssertionError, IndexError) as error:
except (ELFError, ValueError, AssertionError, IndexError) as error:
raise LayoutFailure("format", "Selected ELF failed unchanged upstream structural parsing: " + str(error)) from error
+8 -14
View File
@@ -88,18 +88,16 @@ def inspect_core(path, cache):
segments, notes, padding, threads, signals = [], [], [], [], []
for segment_index, segment in enumerate(image.iter_segments()):
h = segment.header
header_offset = header.e_phoff + segment_index * header.e_phentsize
type_id = int.from_bytes(content[header_offset:header_offset + 4], "little")
backing = type_id != 0 and h.p_filesz > 0
backing = h.p_type != "PT_NULL" and h.p_filesz > 0
if backing:
location(h.p_offset, h.p_filesz, len(content))
if type_id == 1 and h.p_filesz > h.p_memsz:
if h.p_type == "PT_LOAD" and h.p_filesz > h.p_memsz:
raise CoreFailure("format", "Recorded PT_LOAD file bytes exceed memory size.")
segments.append({"index": segment_index, "type": h.p_type, "type_id": type_id,
"header_location": location(header_offset, header.e_phentsize, len(content)),
segments.append({"index": segment_index, "type": h.p_type,
"header_location": location(header.e_phoff + segment_index * header.e_phentsize, header.e_phentsize, len(content)),
"offset": hex(h.p_offset), "file_size": hex(h.p_filesz), "memory_size": hex(h.p_memsz),
"virtual_address": hex(h.p_vaddr), "physical_address": hex(h.p_paddr), "alignment": hex(h.p_align), "flags": hex(h.p_flags), "file_backing": "file" if backing else "none"})
if type_id != 4 or h.p_filesz == 0:
if h.p_type != "PT_NOTE" or h.p_filesz == 0:
continue
position, end = h.p_offset, h.p_offset + h.p_filesz
header_size = image.structs.Elf_Nhdr.sizeof()
@@ -111,7 +109,6 @@ def inspect_core(path, cache):
padding.append({"segment_index": segment_index, "location": location(position, len(raw), end), "bytes_base64": encoded(raw)})
break
note = struct_parse(image.structs.Elf_Nhdr, image.stream, stream_pos=position)
note_type_id = int.from_bytes(content[position + 8:position + 12], "little")
owner_start = position + header_size
descriptor_start = owner_start + ((note.n_namesz + 3) // 4) * 4
note_end = descriptor_start + ((note.n_descsz + 3) // 4) * 4
@@ -122,10 +119,7 @@ def inspect_core(path, cache):
display = owner.split(b"\0", 1)[0].decode("utf-8", "replace") if owner else None
descriptor = content[descriptor_start:descriptor_start + note.n_descsz]
note_index = len(notes)
# pyelftools selects ET_CORE labels without owner context. Keep
# those labels only for exact CORE owners; other kinds are opaque.
note_type = note.n_type if owner == b"CORE\0" else note_type_id
notes.append({"index": note_index, "segment_index": segment_index, "type": note_type, "type_id": note_type_id,
notes.append({"index": note_index, "segment_index": segment_index, "type": note.n_type,
"location": location(position, note_end - position, end),
"owner_location": location(owner_start, len(owner), end), "owner_bytes_base64": encoded(owner), "owner_display": display,
"descriptor_location": location(descriptor_start, len(descriptor), end), "descriptor_bytes_base64": encoded(descriptor)})
@@ -133,7 +127,7 @@ def inspect_core(path, cache):
# Only the initial Linux CORE ABI is interpreted. Other owners retain exact bytes.
if owner != b"CORE\0":
continue
if note_type_id == 1:
if note.n_type == "NT_PRSTATUS":
if len(descriptor) < ctypes.sizeof(prstatus_type):
raise CoreFailure("format", "Linux " + profile_label + " NT_PRSTATUS descriptor is truncated.")
status = prstatus_type.from_buffer_copy(descriptor)
@@ -142,7 +136,7 @@ def inspect_core(path, cache):
relative = prstatus_type.pr_reg.offset + getattr(registers_type, name).offset
registers.append({"name": name, "value": hex(getattr(status.pr_reg, name)), "location": location(descriptor_start + relative, ctypes.sizeof(kind), end)})
threads.append({"note_index": note_index, "historical_pid": ctypes.c_int32(status.pr_pid).value, "recorded_current_signal": status.pr_cursig, "registers": registers})
elif note_type_id == 0x53494749:
elif note.n_type == "NT_SIGINFO":
if len(descriptor) < ctypes.sizeof(elf_siginfo_64):
raise CoreFailure("format", "Linux " + profile_label + " NT_SIGINFO descriptor is truncated.")
signal = elf_siginfo_64.from_buffer_copy(descriptor)
-17
View File
@@ -32,14 +32,6 @@ export default defineConfig({
{ text: "IL2CPP metadata", link: "/il2cpp-metadata" },
{ text: "Model artifacts", link: "/model-artifacts" },
{ text: "Rust dependencies", link: "/rust-binaries" },
{ text: "PE certificates", link: "/pe-certificates" },
{ text: "PE imports", link: "/pe-imports" },
{ text: "PE TLS/load config", link: "/pe-load-config" },
{ text: "ELF notes", link: "/elf-notes" },
{ text: "Apple IPS crash reports", link: "/apple-ips" },
{ text: "Linux kernel modules", link: "/kernel-modules" },
{ text: "CPython pyc files", link: "/python-pyc" },
{ text: "Erlang BEAM", link: "/erlang-beam" },
{
text: "JavaScript and Electron",
link: "/javascript-artifact-reconstruction",
@@ -58,10 +50,6 @@ export default defineConfig({
{ text: "Electron fuses", link: "/electron-fuses" },
{ text: "Process capture", link: "/process-capture" },
{ text: ".NET assemblies", link: "/managed-code-analysis" },
{
text: "Python packaged applications",
link: "/python-packaged-applications",
},
{ text: "Android applications", link: "/android-analysis" },
{ text: "Apple applications", link: "/apple-application-analysis" },
{ text: "Desktop installations", link: "/desktop-applications" },
@@ -82,7 +70,6 @@ export default defineConfig({
{ text: "Ghidra raw binary", link: "/raw-binary" },
{ text: "Ghidra NativeAOT", link: "/ghidra-nativeaot" },
{ text: "Ghidra PowerPC", link: "/ghidra-ppc" },
{ text: "Ghidra GameCube DOL", link: "/ghidra-dol" },
{ text: "Ghidra semantics", link: "/ghidra-provider" },
],
},
@@ -92,10 +79,6 @@ export default defineConfig({
items: [
{ text: "MCP contracts", link: "/mcp-contracts" },
{ text: "Tool design", link: "/tool-design" },
{
text: "Admission limits and recovery",
link: "/design/limits-and-recovery",
},
{ text: "Testing", link: "/testing" },
{ text: "Client verification", link: "/client-verification" },
{
+9 -54
View File
@@ -90,7 +90,7 @@ existing analysis families directly, for example
| `read_adb_logcat` | read | Bounded `logcat -d -t` dump for one buffer, optional pid filter |
| `list_adb_packages` | read | `pm list packages -f` with scope; base APK device paths |
| `resolve_adb_packages` | read | Case-insensitive package-name substring search with exact-match flag |
| `inspect_adb_package` | read | `dumpsys package` projection: versions, install times, installer, flags, and count of listed requested-permission rows (including Unicode names) |
| `inspect_adb_package` | read | `dumpsys package` projection: versions, install times, installer, flags, permission count |
| `list_adb_directory` | read | NUL-framed immediate children, including hidden entries: kinds, permissions, owner, group, sizes, symlink targets |
| `pull_adb_package` | read | Complete split APK set from `pm path`, each file digested |
| `pull_adb_file` | read | One absolute device path pulled and digested |
@@ -103,11 +103,6 @@ existing analysis families directly, for example
| `start_adb_activity` | **mutates device** | Starts an intent (`am start -a` with optional URI, component, and up to 16 typed extras); preserves the activity manager's verdict |
| `stop_adb_app` | **mutates device** | Force-stops every process of a package, ending a dynamic-analysis run |
When `push_adb_file` receives an existing directory, adb places the local file
inside it under the local basename; Evidence reports that effective file path
while retaining the caller's directory path in the input parameters. The
private read-only staging copy preserves the source file's executable bits.
For `inspect_adb_window`, `focus_contexts` retains each display header, its exact
display ID when recognized, and every focus declaration with its original line.
Declarations without a display header are explicitly `unscoped`. A missing
@@ -136,38 +131,13 @@ and during shutdown.
## Interpret the result
- **Package numeric declarations retain failures.** `inspect_adb_package`
admits the first valid `userId` or `appId` as `user_id`: a nonnegative safe
integer, including zero. A null value means no valid declaration was found.
Malformed declarations before or after a valid value remain in
`numeric_issues`: the first ten issues with command, field, reason and original
`raw_line`, plus the exact `total`. Reasons are `invalid_format` or
`unsafe_integer`. Any issue makes package coverage partial; existing section
omissions can also make it partial. `version_code` remains exact source text.
- **Display nulls preserve uncertainty.** `inspect_adb_display` admits positive
safe integers. Absent or malformed optional values are null; `numeric_issues`
distinguishes malformed declarations from absence. It keeps the first ten
issues and exact total, with command, field, original line and reason
(`invalid_format`, `unsafe_integer` or `nonpositive`). The last recognized
labeled declaration wins, even when malformed. A physical-density label
suppresses bare-number fallback even when malformed; without that label, the
first unsigned bare number wins, including zero or an unsafe integer that
leaves the value null. Display coverage is complete only with known physical
density and no numeric issues. Missing or malformed required physical size
produces a protocol failure with `details.partial_observation`: an
`inspect_adb_display` observation from provider `adb`, reason
`physical_size_absent` or `physical_size_malformed`, and a `result` retaining
client, serial, optional display facts, numeric issues, null physical size
and partial coverage.
- **Directory names retain whitespace and newlines.** Symlink targets do too,
including trailing newlines. Records containing invalid UTF-8 remain as
labelled hex in `unparsed_lines` and make directory coverage partial.
- **Inferences carry their basis.** Device `kind` reports
`kind_basis` (serial prefix or USB transport); APK `role` reports
`role_basis: file_name`; `emulator_observed` is derived from the build's
`ro.kernel.qemu` property: `true` only when its value is `"1"`, `false` for
any other reported value, and `null` when the property is absent. The raw
property value remains available in `properties`.
`role_basis: file_name`; `emulator_observed` is the build's own
`ro.kernel.qemu` value, `null` when absent.
- **Device paths are not host paths.** `base_apk_device_path` values live on
the device filesystem. Only pulls and captures produce host-local paths,
each paired with byte size and SHA-256 digest. A pulled file's host name is
@@ -192,9 +162,8 @@ and during shutdown.
APKs keep other names report the `unknown` role rather than guessing.
- **Partial results keep facts.** Failed individual pulls return successful
artifacts with `coverage: "partial"` and per-file `failures`. Listings
and package searches that contain unrecognized lines report the first ten
as `unparsed_lines`, state the exact total in a limitation when more were
seen, and mark partial coverage instead of dropping rows.
that contain unrecognized lines report them as `unparsed_lines` and mark
partial coverage instead of dropping rows.
- **Mutations are explicit.** `push_adb_file`, `install_adb_package`, and
`uninstall_adb_package` declare `mutatesTarget` and never run implicitly:
pushes check for existing device files before transfer and refuse them
@@ -202,9 +171,7 @@ and during shutdown.
transfer even with `overwrite`; only a clean `test -e` exit of 1 with no
output is interpreted as absence. The check is not atomic, so a different
process can create the path before `adb push` writes it. When available, the device's
`sha256sum` verifies the transfer. A missing command or output without a
recognizable digest reports `device_digest_source: "unavailable"` with an
unverified-transfer limitation; installs preserve the
`sha256sum` verifies the transfer; installs preserve the
device's `Failure [...]` reason verbatim.
- **Producer identity stays attached.** Every result embeds the producing
adb client (`binary_path`, `path_source`, `version`, `installed_path`).
@@ -216,11 +183,9 @@ and during shutdown.
The app/activity lifecycle tools use `am start -W` and `am force-stop`.
- Per-invocation budgets: client/devices 10-20 s, shell reads 20-60 s with
64 KiB-8 MiB capture caps (logcat 4 MiB, screen 32 MiB), transfers and
bugreports 600 s. Output capture overflow reports `resource_constraint`
and names the byte allowance. Deadline expiration, including the
client-version probe, reports `provider_timeout` and names the effective
milliseconds. This release has no runtime override for those deadlines or
capture caps. Command cancellation preserves the caller's exact reason;
bugreports 600 s. Output capture overflow reports `resource_constraint`;
deadline expiration, including the client-version probe, reports
`provider_timeout`. Command cancellation preserves the caller's exact reason;
a later abort does not replace an already completed command failure.
- Pull and capture outputs are additive: the destination must not already
exist. Re-running a completed pull refuses the existing directory instead
@@ -265,13 +230,3 @@ Record: verified against adb 34.0.5 on Linux against an Android 14
287 services, 92 features, a real two-APK split set pulled with byte-exact
digests, a verified push/pull roundtrip, a 1.3 MB screen capture, and the
complete package lifecycle; see `docs/testing.md` for the lane summary.
Intent integer extras use signed decimal text. The ADB argument encoder removes
redundant leading zeros for Android's radix-sensitive `--ei` parser while retained
Evidence keeps the caller's original spelling. Package inspection accepts both
`userId` and Android 16's `appId` spelling for the legacy `user_id` result field.
A completed push and device digest verification are separate observations. A
verification timeout, cancellation, or output budget failure returns the typed
failure with partial Evidence of the completed transfer. An unavailable checksum
command or unrecognized checksum output leaves verification explicitly unknown.
File diff suppressed because it is too large. Load diff
+5 -93
View File
@@ -34,53 +34,6 @@ One-shot CLI output remains complete. Older servers require the existing complet
request after schema discovery. APK authenticity and runtime behavior remain
unverified.
## Manifest component projection
`inspect_android_package` also returns a static inventory of declared
activities, aliases, services, receivers and providers from the decoded
manifest. `declared_name` preserves the source attribute; `name` resolves a
relative name only when the manifest package is known and the resolved copy
fits the UTF-8 byte budget. An unresolved package or exhausted name budget
retains the declared name and marks the inventory partial. Empty component
names and names with forbidden DEX characters retain their raw spelling and
are not treated as identities. Valid DEX spaces, including boundary spaces,
are preserved when resolving component names and matching alias targets.
Whitespace in the manifest package is rejected by its separate package grammar.
Component permissions inherit the application
permission when no component permission is declared, except an activity alias
with no permission: Android documents that case as requiring no permission
through the alias. `permission_source` distinguishes a local declaration,
inherited value, no requirement (including an explicit empty override), and a
value omitted because its resource/manifest-placeholder token is unresolved
or the total permission-byte budget was reached. Resource and placeholder
references are not expanded at this parser boundary. Repeated inherited
values share one inventory-wide byte budget.
Provider `android:readPermission` and `android:writePermission` overrides are
not projected as separate fields; their presence marks the inventory partial
and is called out in `limitations`. Relative component names are not resolved
against a manifest package with surrounding whitespace.
Class-name segments follow DEX `SimpleName` characters, including a leading
digit or hyphen, rather than Java source identifier rules. Relative-name
expansion does not prove class existence, DEX-version compatibility or runtime
loadability. SDK defaults accept JADX's decimal and hexadecimal integer
spellings; the original SDK declaration strings remain unchanged.
`exported` is the declared literal `true` or `false`, otherwise null.
`exported_default` is the documented default only where it can be established;
it remains null for unknown provider target SDKs, nonliteral declarations, or
components with filters targeting API 31 or later that omit the now-required
explicit declaration. A limitation records the latter validation error.
Provider authorities and all inventory dimensions are bounded. `status:
"partial"` and `limitations` identify omitted declarations or values; a
capacity limitation appears only when an actual collection limit was reached.
The projection does not merge manifests, expand resource references, or
observe runtime enabled/export state.
The service `foreground_service_type` field preserves the declared attribute,
including an empty value or surrounding spaces; `null` means the attribute is
absent or the component is not a service. This field does not validate service
type tokens or establish runtime eligibility.
## Supply tools
Use a **full JDK 17 or newer** (including its compiler module) and
@@ -194,21 +147,6 @@ queries accept the same `overload_index`; the bridge resolves the selected
method node and the result reports its index and displayed signature. These
indices are tied to the current artifact and engine, not stable across builds.
`inspect_android_class` also reports `dex_static_value` for each `static final`
field whose class definition encodes a value in its DEX static values, so
compile-time constants are readable even when the class has no `<clinit>`.
Each value is typed: `boolean`, `byte`, `short`, `char` (UTF-16 code unit) and
`int` values are JSON booleans or numbers, while `long`, `float` and `double`
values are strings holding Java's text for the value, so 64-bit values stay
exact. The field reports `null` when no value is encoded, the encoded value is
null or of another kind, or the field is not `static final`. The value is read from parsed
metadata without generating source, and a static initializer can still assign
the field; use `inspect_android_method` on `<clinit>` for that evidence.
Only `inspect_android_class` asks the bridge for these values.
`inspect_android_method` and `trace_android_references` read the member-only
class summary, so large encoded strings cannot push their member lookup past
the 8 MiB protocol frame limit.
Smali fallbacks select one complete block using the original method name and DEX
descriptor, before applying the source byte limit. This preserves the selected
overload even when Java code generation renames a method. If the selected Java
@@ -233,14 +171,8 @@ the same source byte limit and explicit truncation reporting.
## Interpret the result
- Package inspection retains decoded manifest XML, summary declarations,
permissions and class/resource counts. REA's bridge selects the exact root
`AndroidManifest.xml` resource by original archive name and manifest type,
independently of asset order. One namespace-aware JDK XML parse supplies
summary attributes and direct permission/SDK declarations; namespace prefixes
and XML entity spelling do not change their values. Raw decoded XML is retained
exactly and only its displayed text is subject to the existing source byte cap.
Summaries use the complete selected XML before that cap. Document types are
unsupported; parsing does not load external DTDs, schemas, or XInclude files.
permissions and class/resource counts. Summary fields are extracted by the
upstream engine and should be checked against the retained XML when needed.
Signature verification is explicitly `not_performed`.
- Class search filters the engine's cached top-level names before transferring
pages, then follows contiguous inventory positions without rescanning earlier
@@ -255,13 +187,6 @@ the same source byte limit and explicit truncation reporting.
full reported display types, fields and inner class names. Synthetic members
that code generation hides can appear in the inventory; it stays consistent
after method decompilation within the retained session.
Class and method inspection and reference tracing accept either the exact
reported class name or its original binary name, including `$` in nested and
anonymous classes. The bridge keeps both identities from JADX; it does not
replace separators or guess suffix matches. Ambiguous selectors are refused.
Follow-up calls preserve the selected name, while results report the frozen
JADX display name. Quote `$` names in the shell, for example
`'com.example.Manager$WorkerThread'`.
Exact DEX prototypes are reported from frozen JADX method metadata as
`dex_descriptor`, for example `(I)V`. Display names can be JADX aliases;
the descriptor alone is not a complete raw DEX method ID. Overload indices
@@ -331,10 +256,6 @@ such as
`libreactnativejni.so`, `libreactnative.so`, `libhermes.so`,
`libhermes_executor.so`, `libflutter.so`, and `libunity.so`; unrelated names
such as `libcommunity.so` and `libreactivity.so` do not identify those families.
The signing list retains the `META-INF/MANIFEST.MF`, `.SF`, `.RSA`, `.DSA`, and
`.EC` paths selected by Android's v1 verifier, including nested paths. These are
inventory metadata candidates, not proof that a v1 signature verifies or that
the APK is signed; use `inspect-apk-signature` for signature analysis.
The bridge basis is inferred from the native path and repeated for each managed
component, so it does not identify a specific JNI binding. Bridge hypotheses form a DEX/JVM-class by native-library
Cartesian product. The projection computes the product and exact serialized
@@ -414,18 +335,9 @@ export REA_JADX_OPERATION_TIMEOUT_MS=600000
```
The value must be a decimal millisecond count from 1 to 2147483647; anything
else is refused before Java starts. REA reads it from the environment captured
at process start. Changing that environment requires a new REA process. On a
running MCP server, set `jadx.operation_timeout_ms` through
`update_analysis_settings` to change the default for newly accepted operations;
queued or running operations retain their accepted budget. The same deadline
bounds each upstream call. Timeout recovery reports the effective milliseconds
and range, with `details.limit` identifying the runtime setting. An invalid
provider-local launch value can also be repaired by a runtime override; reset
exposes that invalid launch value again. See the
[settings design](design/limits-and-recovery.md#invalid-launch-configuration).
A passing JADX configuration check in `rea doctor` reports its launch-derived
`operation_timeout_ms`; it does not inspect another running server's overrides. Explicit
else is refused before Java starts. The same deadline bounds each upstream
call, a timeout reports the selected value, and a passing JADX configuration
check in `rea doctor` reports the effective `operation_timeout_ms`. Explicit
cancellation still ends the operation immediately, and partial observations
and owned-process cleanup are unchanged. Raising the deadline does not extend
the bridge's 30-second startup handshake or the upstream **90-second**
-290
View File
@@ -1,290 +0,0 @@
# APK signature schemes
REA reads the signing of a selected local Android APK (or ZIP/AAB) statically,
so an analyst can answer **who signed this, under which schemes, and whether the
declared content digests match these bytes** before trusting or triaging it. The reader
is in-process and needs no external engine: it parses the ZIP tail and the APK
Signing Block, reads certificates with the platform X.509 parser, and
recomputes the content digest. No Android code executes, and no device or
`adb` is involved.
CLI and MCP share one workflow and return artifact-bound Evidence inline.
```sh
rea inspect-apk-signature /path/app.apk
```
## Selected facts and retained Evidence
MCP defaults to a summary with scheme presence, signer certificate fingerprints,
row counts, posture and explicit coverage. `detail: "complete"` returns the full
report. A selected page can answer a narrower question in the acquisition call:
```json
{
"path": "/path/app.apk",
"view": {
"collection": "signers",
"schemes": ["v3"],
"fields": ["certificate_sha256"],
"limit": 32
}
}
```
The returned `parent_evidence_id` identifies the retained complete report.
Subsequent reads use `inspect_analysis_view`, including after the APK is deleted:
```json
{
"source": {
"kind": "retained-evidence",
"evidence_id": "<returned parent_evidence_id>"
},
"view": {
"kind": "apk-signature",
"collection": "digests",
"signer_ids": ["<returned signer id>"],
"fields": ["signature_algorithm_id", "algorithm", "matches_apk"],
"offset": 0,
"limit": 32
}
}
```
Collections are `signers`, `digests` and declared `lineage` nodes. `ids`,
`signer_ids` and `schemes` filters intersect; omitted filters select all available
records and `[]` selects none. Omitted `fields` returns every collection column;
`fields: []` retains identity and relationship links. IDs include the complete
APK digest, scheme and source ordinal; child records retain `signer_id` and
`signer_index`, and declared certificates retain their raw-DER SHA-256 identity.
Lineage pages expose `previous_id` when selected. Certificate fingerprints can
be composed across versions without treating a declaration as verified identity.
Page coverage describes examined declared records. Exhausting a page does not
establish certificate authenticity, platform acceptance, v1 entry coverage or a
verified rotation. Partial decoding and its reason remain visible even when a
lineage page contains no rows. A weak cache reuses validated immutable report
facts; each read checks its parent subject, and new file inspections read again.
CLI defaults to complete delivery. A compact one-shot response requires a complete
Evidence export:
```sh
rea inspect-apk-signature /path/app.apk --detail summary \
--view '{"collection":"signers","fields":["certificate_sha256"]}' \
--evidence_output /path/app-signing-evidence.json --json
```
The export contains the same complete parent used by MCP. Historical CLI pages
use `inspect-analysis-view` with an `evidence-file` source record. The CLI and MCP
share the application workflow, projection, retention and publication owners.
## What it reports
- **Schemes present:** v1 (JAR signing, from the central directory), and the
APK Signing Block's v2, v3 and v3.1. v1 follows apksig's selection: entries
under `META-INF/` at any depth ending in `.SF`, or in `.RSA`/`.DSA`/`.EC`
(case-sensitive). v1 is `present` only when a signature block has its
matching `.SF` file (`META-INF/x/CERT.RSA` pairs with `META-INF/x/CERT.SF`);
`signature_files` and `block_files` list every candidate either way.
- **Per v1 signer** (each block paired with its `.SF`): REA inflates the block
and reads its PKCS#7 `SignedData`. `certificate` is the certificate the
block's one `SignerInfo` names by `issuerAndSerialNumber` or
`subjectKeyIdentifier` (not by position), with the same facts and raw-DER
SHA-256 as a v2/v3 signer, so a v1-only APK can be fingerprinted and
clustered with v2/v3-signed ones. apksig resolves only
`issuerAndSerialNumber`, so its v1 verification fails for a block whose
`SignerInfo` uses `subjectKeyIdentifier`: REA still reports the declared
certificate (`identified_by: subject-key-identifier`), adds a limitation,
and treats that signer as unknown in `v1_signers_match_block_signers`. `certificates` lists every certificate the
block carries (SHA-256 and subject) and `signer_infos` gives each
`SignerInfo`'s certificate and digest/signature algorithm OIDs. The PKCS#7
`SignedData` envelope is read as BER, including constructed signature OCTET
STRINGs; each embedded certificate is still interpreted as DER by the X.509
owner. Issuer matching preserves the encoded Name and also admits equivalent
ASCII PrintableString/UTF8String values for CN, C, L, ST, O and OU: case and
repeated/edge spaces are normalized, RDN sequence order is preserved, and
attribute order inside one multivalued RDN is ignored. Other name forms
require exact encoded identity; semantic equivalence remains unestablished.
`certificate` is `null` when the block has more than one `SignerInfo` (with
nothing verified there is no single signer) or names a certificate it does
not carry. A block that cannot be read (malformed ASN.1, an unsupported
compression method, Zip64 sizes, a local header that disagrees with the
central directory on the name, data-descriptor flag, CRC-32 or a size, as
apksig refuses it, or over a bound) keeps its names and carries
`decode_error`; it is never reported as an absent signer. apksig ignores
the ZIP encryption flag (bit 0), so a block that sets it is read as stored:
the PKCS#7 decoding decides (bytes that really are encrypted fail to
decode), and a limitation names the block.
apksig rejects the v1 signature of an archive whose central directory
holds any name more than once ("Duplicate entry"). REA checks every entry:
a limitation names the first few duplicates (JSON-quoted; a name over 256
bytes is cut there and its stored length given) and counts the rest, and a
signer whose block, `.SF` or `META-INF/MANIFEST.MF` name is duplicated
(one signer per block name) carries `decode_error`
`duplicate entry: <name>`, so a duplicated manifest marks every signer.
A duplicate outside those names leaves the signers as declared.
Names are compared as apksig compares them, as Java strings decoded from
UTF-8: malformed bytes become U+FFFD, so `dup\xff` and `dup\xfe` are one
name (apksigner: `Duplicate entry: dup�`). REA reproduces Java's
replacement rather than WHATWG `TextDecoder`'s; the two differ for encoded
surrogates (`ED A0..BF`), which Java replaces as one unit. This was checked
against OpenJDK 25's `new String(bytes, UTF_8)` on about 300,000 malformed
and well-formed sequences, including a leading byte-order mark, which Java
keeps; other Java runtimes are not checked. The same
decoding names v1 entries and compares a local header's name with the
central directory's; as in apksig, a local name longer in bytes than the
directory's is refused before decoding. apksig takes the record count from the classic
end-of-central-directory record's 16-bit field and ignores a Zip64 count,
so it reads only the first 65,535 records of a larger directory: the
limitation then says so, counts as duplicates for apksig only names whose
second copy lies within those records, and names separately those
duplicated only past them. REA still reads v1 signing entries, and flags
duplicated signing names, from every record, so past record 65,535 it
reports signers apksig would not see.
Bytes after the block's `ContentInfo` are ignored, as apksig's BER parser
ignores them, and a limitation names the block and their count.
- **Per signer:** the certificate's **SHA-256 fingerprint over the raw embedded
DER** (the same value `apksigner --print-certs` prints), plus subject, issuer,
serial, ISO 8601 validity and public-key algorithm when the X.509 parser can
read the exact carried DER (each is `null` otherwise). The parser's returned
DER must equal those bytes before readable facts are combined with their
fingerprint; embedded PEM text and trailing data cannot select a different
certificate. Raw signature algorithm IDs, declared
digest hex and digest algorithms, signature lengths and public-key length; and for
v3/v3.1 the signer's declared min/max SDK.
- **Key rotation (v3/v3.1):** the declared proof-of-rotation lineage, oldest
first. Each node carries its certificate and the capability flags **retained
by that node's own certificate** after rotation (installed data, shared UID,
permission, rollback, auth). A lineage attribute that cannot be decoded is
reported with `decode_error`, never silently emptied.
- **Source stamp:** whether a source-stamp block is present and the stamp
certificate it declares (the V2 stamp block is preferred over V1, as apksig
does). `certificate` is `null` when no stamp is present, or when the block
could not be decoded and `decode_error` says why. Stamp signatures are not
verified.
- **Declared digest consistency:** for SHA-256/SHA-512 digests, REA recomputes
the v2/v3 content digest and reports `matches_apk` per digest and
`posture.self_consistent`. This only shows that the **declared** digest
equals the digest of the current bytes. It is **not a signature
verification**: anyone who changes the bytes can recompute the digest and
write it into the signed data, and REA still reports `true` while
`apksigner verify` rejects the APK. A changed byte with an unchanged
declared digest reads `false`.
- **Posture** (derived from the facts above):
- `v1_only`: v1 is present and no v2/v3/v3.1 block is. The v1
`X-Android-APK-Signed` marker is not read, so this cannot distinguish an
APK signed only with v1 from one whose v2/v3 block was removed.
- `schemes_agree_on_signer` (observed): every present block scheme presents
the same _set_ of signer certificates. An extra signer in one scheme is
disagreement; a signer without a certificate makes it `null` (unknown).
Build-tools 33+ rotate through v3.1 by default, so a rotated app usually
reads `false` here and `true` below.
- `signer_change_declared_by_rotation` (declared): the signers differ, but
each appears in a lineage the APK declares. Read the two together: a
rotated APK shows `false` / `true`; signers that differ with no declared
lineage containing them show `false` / `false`. It is `null` when a
signer's identity is unknown, or when the signers differ and a declared
lineage could not be decoded.
- `v1_signers_match_block_signers` (observed): the v1 signer certificates
equal what the lowest block scheme that has signers (v2, else v3, else
v3.1) expects v1 to carry. That is its signer set, except that a v3/v3.1
signer declaring a rotation lineage expects the lineage's first
(original) certificate, as apksig requires. `false` is notable: the JAR
signature names a different key than the block signature. A key rotation
keeps v1 (and v2) on the original key, so it reads `true` while
`schemes_agree_on_signer` reads `false`, with or without v2. `null` when
v1 or a block scheme is absent, any signer's identity is unknown (a
`decode_error`, a `null` certificate or a `subjectKeyIdentifier`
`SignerInfo`), or the lineage's original certificate cannot be
identified; a limitation then says why.
- `strongest_scheme`, `rotation_present`, `source_stamp_present`,
`self_consistent`.
## What it does not claim
This is **declared identity and digest consistency, not trust**. No signature
is cryptographically verified (including the v1 PKCS#7 signature and the
`.SF`/`MANIFEST.MF` digests, so a v1 signer is reported even where `keytool`
or `apksigner` reject the archive), certificate chains and revocation are not
evaluated, and nothing here says an app is authentic. `self_consistent: true`
means the declared digest equals the digest of the current bytes; it is not a
signature verification, and it does not prove who made the bytes. Rotation-lineage node signatures are not verified either, so a lineage is
what the APK _declares_: an attacker who re-signs an APK can declare any
lineage, which is why `signer_change_declared_by_rotation` is kept apart from
the observed agreement. SHA-256 identifies exact bytes, not authenticity.
Not yet read: v4 `.apk.idsig` (a sibling file), verity (`0x0421`/`0x0423`/`0x0425`) digests (reported with
`matches_apk: null`), and the v1 `X-Android-APK-Signed` marker.
## Bounds and errors
The archive is read once, bounded at 512 MiB, without following symlinks and
with change detection. ZIP tail offsets, the central-directory size and signing
block spans used by the reader are checked against their containing sections,
including the complete central-directory extent before ZIP end records. A process-wide queue permits
one active whole-file inspection and eight pending scans across provider
instances; pending scans retain no APK bytes and can be cancelled with their
original reason. Further requests receive a capacity error. The active slot
is held through parsing and cleanup. Signers per scheme, v1 signers and
lineage nodes are capped at 32 each as resource admission limits, not format
maxima (apksig separately accepts at most 10 v1 signers). Each v1 block is
inflated to at most 1 MiB, and its BER is read within 32 levels of nesting and
65,536 elements, with at most 32 certificates and 32 `SignerInfo`s; BER
indefinite lengths and constructed OCTET STRING fields are accepted. Embedded
X.509 certificates retain their DER requirement. The report's size is
bounded from above by those counts before anything is materialised, and the exact
serialized size of the report together with its limitations (which repeat
archive names) is then checked against an 8 MiB output budget (the exact check
is authoritative); an over-budget report is refused, not truncated.
The whole-file reader allocates the selected snapshot and bounded read scratch;
total process memory exceeds the selected file's size. It also computes the complete
file SHA-256, so inspection is not header-only. Content digests are recomputed
at most once per SHA-256/SHA-512 algorithm during one inspection. Input,
inflation, decoded-record and serialized-output bounds keep work finite;
resource refusals are distinct from malformed format errors.
If the stable read completes but closing its descriptor fails or is cancelled,
the error retains `details.partial_observation` with the selected path, complete
file SHA-256 and byte count. These facts describe the acquired file; they do not
establish any decoded signing facts. Failed cleanup remains visible until the
provider releases the descriptor on a cleanup retry.
A file that is not a ZIP is refused as an unsupported target. A damaged ZIP
(local headers but no end-of-central-directory), a central directory that
cannot be walked or whose declared record count does not account for all of
its bytes, or a malformed signing block or signer is an input error naming the
offset or scheme and reason. Zip64 tail damage includes a missing required
locator, an impossible locator disk/count pair, a fixed end record that does
not fit before its locator, or an incorrect end-record signature. These are
input errors, not evidence of an unsupported format. Coherent multi-disk
locator metadata and 64-bit offsets/counts/sizes beyond the reader's safe
integer range remain unsupported. The locator offset is disk-relative, so
multi-disk metadata is checked before applying local file bounds. This follows
the record fields in [PKWARE APPNOTE 6.3.10, sections 4.3.14–4.3.15](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT);
it does not establish full validation of Zip64 versions or extensible data.
Signer framing includes signature records, the public-key field and matching
signed/outer v3 SDK ranges. Suffix bytes beyond known
fields are counted as uninterpreted, as Android readers may ignore extensions;
their presence does not establish malformed signing.
When the central directory is not immediately followed by
the end-of-central-directory record (a declared-size mismatch or a Zip64
record), the content digest is not recomputed and a limitation says so. A
rotation attribute that cannot be decoded still counts as `rotation_present`
(the attribute exists) and carries its `decode_error`.
Verified by the `verify:apk-signature` real lane. It builds a real minimal APK
with `aapt2`, signs it with the SDK's `apksigner` (default v2+v3, v1-only with
RSA and EC keys, v1+v2+v3 on a legacy target, a rotated key lineage with and
without v1, rotation carried by v3.1, a source stamp, and a copy
with one changed byte), and cross-checks REA against
`apksigner verify --print-certs`, `apksigner lineage --print-certs` and
`keytool -printcert -jarfile` through the CLI and MCP.
The real-toolchain CI lane uses pinned, checksum-verified Build Tools 36.1.0
and platform 36 oracle files in its job workspace. It requires the existing
host JDK and runs `npm run verify:apk-signature -- --require-tools`; absent
oracles fail rather than silently establishing provider coverage. REA itself
requires none of these external tools.
+4 -27
View File
@@ -65,7 +65,6 @@ rea decode-android-resources /targets/Example.apk --locale de
rea decode-android-resources /targets/Example.apk --locales de --locales pt-BR
rea decode-android-resources /targets/Example.apk --locales de --locales pt-BR --string-tables-only
rea decode-android-resources /targets/Example.apk --no-strings
rea decode-android-resources /targets/Example.apk --decode-timeout-ms 900000
```
Equivalent MCP methods are `inspect_apktool_client` and
@@ -93,21 +92,9 @@ still suppresses all string projection, including in this mode.
`target.bytes` bind the observation to the exact APK that was decoded.
The Evidence subject carries the same snapshot digest and caller-selected
path, including when the original file changes during decoding.
A change during snapshot acquisition reports retryable `artifact_changed`;
wait for a stable input before retrying. Host read permission failures report
`access_denied`, while a missing or non-regular selected path reports
`invalid_request` with the rejected `path` field.
- **Metadata comes from apktool.yml as apktool printed it.** Version codes
keep their own spelling (`'7'` versus `7`); `package_name` is read from
the decoded manifest, not inferred.
Apktool 3's emitted `version: 3.x.y` header selects its single-line string
dialect: literal hashes, single quotes, empty strings, and the text `null`
stay part of the value; producer backslashes and Unicode escapes decode
once. Older/tagged documents retain YAML scalar semantics; double-quoted
values also retain YAML string semantics.
This distinction follows the released
[3.0.3 metadata writer](https://github.com/iBotPeaches/Apktool/blob/18b5e99cb56ff9451e8aa55b065dcf5bbd616975/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/VersionInfo.java#L49-L55)
and [string escape owner](https://github.com/iBotPeaches/Apktool/blob/18b5e99cb56ff9451e8aa55b065dcf5bbd616975/brut.j.yaml/src/main/java/brut/yaml/YamlStringEscapeUtils.java#L55-L116).
- **The manifest is the decoded text**, bounded at 512 KiB and reported as
truncated when larger.
- **Strings are projected verbatim.** Escaped entities stay escaped, and
@@ -146,14 +133,9 @@ still suppresses all string projection, including in this mode.
## Resource and lifecycle limits
- The decode wall clock is `decode_timeout_ms` (CLI `--decode-timeout-ms`).
Omitted means 600000 ms. The integer range is 1 to 2147483647. A change
applies on the next decode. It does not raise the 4194304-byte (4 MiB)
output capture budget. That capture budget has no runtime override. A
stopped decode reports `provider_timeout` and names the effective
milliseconds; an exceeded capture budget reports `resource_constraint`.
- The version probe waits 20000 ms and retains 65536 bytes. This release has
no runtime override for that probe. `decode_timeout_ms` does not raise it.
- The decode runs with a 600 s deadline and a 4 MiB output capture budget;
a stopped decode reports `provider_timeout` and exceeded budgets report
`resource_constraint`.
- Manifest reads are bounded to 512 KiB before decoding UTF-8. Strings XML
reads are bounded to 8 MiB; larger documents are skipped with partial
coverage and an explicit limit, rather than reported as missing.
@@ -179,11 +161,6 @@ Multi-locale and table-only projection were also verified through real CLI and
stdio MCP workflows on Linux with Apktool 3.0.3/JDK 21 against NewPipe 0.29.1,
ApiDemos 6.0.18 and Appium Settings 8.2.1. Independent XML projection checks,
exact facts/Evidence parity and bounded task costs are recorded in the
[shared-decode and table-only checkpoint](https://github.com/morluto/rea/blob/42e1a17d6e6c0487f17be0bae248553c4caba7f7/experiments/analysis-efficiency/apktool-string-tables.md).
[shared-decode and table-only checkpoint](../experiments/analysis-efficiency/apktool-string-tables.md).
This qualifies selected resource facts, not application execution or independent
binary resource-decoder completeness.
If the selected APK changes while its private snapshot is copied, acquisition
returns `artifact_changed`; request-schema correction cannot recover a stable
snapshot. Launcher spawn permission denial is distinct from a missing launcher,
and configuration diagnostics retain the selected command and system error.
+12 -43
View File
@@ -156,10 +156,6 @@ anatomy components.
The projection does not read plists. Use `inspect_plist` on a bundle's
`info_plist_path` or a launchd plist to read `CFBundleExecutable`, identifiers,
`SMPrivilegedExecutables`, `BundleProgram`, URL schemes, and usage descriptions.
MCP calls require an existing `open_binary` or `open_bundle_resources` session,
including when `inspect_plist.path` is explicit. For a bundle without a resolved
executable, open its resource session first. The CLI `rea inspect-plist <path>`
opens the selected artifact for its one invocation.
Use `inspect_signature` on an executable candidate for code-signing claims.
## Dylib load resolution
@@ -213,51 +209,24 @@ Resolution follows dyld:
already loaded image reuses it.
- 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 `\`
as its separator, and a dyld component that contains `\` or `:` cannot name
a file in the analyzed root, so its candidate is `absent`.
Each edge lists every candidate path tried, with its outcome: `resolved`,
`absent`, `not-mach-o`, `malformed`, `unsupported` (recognized Mach-O outside
parser coverage, such as big-endian images), `architecture-missing`,
`not-loadable`, `platform-mismatch`, `outside-target`, `escapes-target`, or
`undetermined`. `not-loadable` means a
matching-architecture Mach-O image was found but it is not a dylib and cannot
satisfy a dylib dependency. `platform-mismatch` means a matching-architecture
dylib was found but its observed platform is incompatible with the process.
These outcomes reject that candidate; dyld search may continue and resolve a
later candidate. Candidate outcomes describe individual search paths, while
`resolution.status` describes the final edge. For each selected process-root
slice, candidate slices are matched by CPU type and dyld's subtype preference
order, independently of FAT table order. An ARMv7s root slice prefers ARMv7s,
then ARMv7; other known ARM32 subtypes use their compatibility rows from
[Apple's dyld source](https://github.com/apple-oss-distributions/dyld/blob/dyld-852.2/src/dyld2.cpp#L2811-L3105).
An `x86_64h` process also loads generic `x86_64`. Unknown ARM32 subtype policy
remains undetermined when ARM32 candidate slices exist. This is a static model
keyed to the selected root slice; it does not observe the target device's host
CPU or prove which slice the device will execute. The edge's `resolution` is
one of:
`outside-target`, `escapes-target`, or `undetermined`. Slices follow dyld's
graded architectures, so an `x86_64h` process also loads `x86_64`. The edge's `resolution` is one of:
| Status | Meaning |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resolved` | An image inside the analyzed root loads, and no earlier candidate was undeterminable. |
| `conditional` | An in-root image may load, but an earlier outside candidate, unmodeled environment input, or unknown platform compatibility can change which image dyld loads. |
| `unresolved` | Every candidate is definitively absent or unusable inside the root. |
| `undetermined` | No candidate resolves inside the root, and some candidate lies outside it or depends on the environment. |
| Status | Meaning |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resolved` | An image inside the analyzed root loads, and no earlier candidate was undeterminable. |
| `conditional` | An image inside the root resolves, but an earlier candidate outside the root (for example `/usr/lib/swift`) would win if it exists on the running system. |
| `unresolved` | Every candidate is definitively absent or unusable inside the root. |
| `undetermined` | No candidate resolves inside the root, and some candidate lies outside it or depends on the environment. |
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
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
reported slice fields `platform: null` and `platforms: []` remain the parsed
facts. For other process contexts the platform remains unknown, coverage is
partial, and the candidate is not treated as incompatible. If a later known
candidate resolves, it remains the traced image with conditional resolution;
the trace does not claim the unknown candidate's dependency graph. Findings on
conditional edges say that they apply only if the conditional image loads.
A `conditional` image is still traced, because it is the image the analyzed
root supplies. Every edge below it has `loader_conditional: true`, and a
request that reuses it by install name is `conditional` too. Findings on those
edges say that they apply only if the conditional image loads.
Absolute install names that name a path beneath the analyzed root are checked
within that root, with symlinks and `..` resolved by the same bounded tree
-123
View File
@@ -1,123 +0,0 @@
# Apple IPS crash reports
`inspect_apple_ips` / `inspect-apple-ips` answers whether one selected local
Apple `.ips` file is a crash report (`bug_type` string `"309"`), which process,
exception, and termination facts that report records, which threads and
`usedImages` rows it lists, and which documented crash classes its strings
match. It reads the file without executing a target and does not require Xcode,
atos, a disassembler, or pwntools.
```bash
rea inspect-apple-ips /absolute/path/to/report.ips --json
```
The CLI resolves relative paths against the caller's working directory. MCP
requires an absolute filesystem path and preserves that exact selection:
```json
{
"name": "inspect_apple_ips",
"arguments": { "path": "/artifacts/report.ips" }
}
```
## Observations
The inline Evidence result contains the artifact path, SHA-256, and byte length.
`metadata` is the first-line JSON object. `bug_type` stays a string. The crash
body is parsed only when that string is `"309"`. Other types, including `"288"`
stackshots, return no crash result.
`process`, `exception`, `termination`, `faulting_thread`, `threads`, and
`images` reproduce documented body fields when present, including each image's
`name` and each frame's `symbolLocation` (as `symbol_location`, the byte offset
from the start of `recorded_symbol`). `termination` keeps the terminating
process as `by_proc` and `by_pid` and its `flags`; `process.parent_pid` keeps
`parentPid` next to `parent`; and `metadata` keeps `build_version` and
`platform`. Integers keep their
decimal literals and a hexadecimal form, including arm64e registers above
2^53. Frame roles follow the recorded frame index: row zero of the resolved
faulting thread is `faulting_instruction`, row zero of other threads is
`current_instruction`, and later rows are `return_address`. Each runtime
address uses that row's own image base and offset. The recorded `inline` flag
does not supply a missing offset or authorize borrowing another row's address.
When `faultingThread` is missing or does not resolve to a recorded thread
object, row-zero roles are `unknown`; the recorded lookup remains available.
A translated process records Rosetta's registers in a nested
`threadState.rosetta` object; each one is reported as `rosetta.<name>` and
counts toward the register budget. A [public ReportCrash `.ips` log](https://github.com/Mortennn/Dozer/issues/189)
shows `rosetta.tmp2`, `tmp1`, and `tmp0` beside ordinary x86 registers. The
test fixture quotes that register excerpt inside an authored report body; it
does not generate a new Rosetta crash during the test.
Observed JSON strings retain `""`; `null` means the field was missing or JSON
`null`. This includes image paths, frame symbols, process text, and the older
string `osVersion` form. Unlisted JSON keys and generic register names also
retain empty spellings. An empty image architecture remains recorded as `""`
while `uuid_unknown` stays true, since that spelling does not establish an
architecture. An empty UUID is malformed, so its exact spelling appears in
`coverage.type_mismatches` and its canonical UUID remains unknown.
`process.os_version` keeps the producer's dictionary (`train`, `build`,
`release_type`, `is_embedded`) or the older string form. Image UUIDs are uppercase 8-4-4-4-12; an all-zero UUID
or a missing or empty `arch` is `uuid_unknown`. Image `source` letters keep their
documented meaning (`P` process, `S` shared cache, `C` shared cache library,
`K` kernel, `U` kernel cache, `T` kernel text exec, `A` absolute). Keys the
documentation does not list remain in `unlisted_keys` or `unlisted_body_keys`.
Each frame's `image_index` refers to its entry in `images`. Image architecture,
UUID and `uuid_unknown` are retained once on that image. Resolve the reference
when using those facts; an absent or out-of-range index leaves the image
unknown. This avoids repeating image text for every stack frame.
A present value whose JSON type differs from the documented one is never read
as absent: it is listed in `coverage.type_mismatches` with its path, the
expected type, and the observed type, and `coverage.status` becomes
`partial`. A crash body without `threads` or `usedImages` is also `partial`.
Malformed image UUID strings likewise make coverage partial; their exact spelling
is retained in the mismatch's `recorded_value`, while the canonical image UUID
remains unknown. Both 32 hexadecimal digits and canonical hyphenated UUIDs are accepted.
Unresolved thread/image references also make coverage partial. A rejected
integer lookup keeps its exact decimal literal in `recorded_integer`, including
negative values and values above uint32. Valid uint32 lookup values remain in
`faulting_thread` or `image_index` even when the referenced row is unavailable.
`classification` matches only documented SIGKILL codes (compared by their
canonical hexadecimal form), `EXC_GUARD` subtypes,
pointer-authentication suffixes, and dyld missing-library strings. A report
that matches none of those rules is `unclassified`.
Opened Mach-O and dSYM UUID joins remain unobserved. Symbol names printed
inside the report are recorded claims and are not verified. Linux ELF cores
use the separate [recorded crash](recorded-crashes.md) workflow.
## Limits and failures
The selected file is limited to 1 MiB by default (16 MiB maximum). At most 256
threads, 8192 frames, 1024 `usedImages` rows, and 16384 `threadState`
register slots are examined by default (4096 / 65536 / 4096 / 262144
maximum). Budgets are checked before each row is parsed, and body members the
report does not interpret are syntax-checked without being kept. Fixed
allowances bound JSON nesting (64 levels), unlisted key names per object
(1024), and `termination.reasons` entries (256). Malformed metadata, a
truncated crash body, invalid UTF-8, a NUL in the text, and exhausted budgets
fail without partial success.
`max_integer_text_bytes` (`--max-integer-text-bytes` in the CLI) separately
bounds retained decimal and hexadecimal integer text, including every derived
runtime address and rejected lookup literal. Its default is 8 MiB of UTF-16 text storage and its maximum is
64 MiB. Each appearance counts, even when an object could be shared in memory
but would be serialized more than once. The parser reserves a conservative
upper bound before numeric conversion or address expansion, then releases the
unused reservation after each value. It reuses the parsed source integer when
the same image base contributes to many frames.
Raise this per-call allowance on the next call when more room is needed; no
restart is required. File-size and row budgets do not raise it. This allowance
is separate from JSON wire size, MCP response limits and the client's receive
buffer. An exhausted allowance reports a typed resource error without partial
success or truncated integer facts.
```bash
rea inspect-apple-ips /absolute/path/to/report.ips --max-integer-text-bytes 16777216 --json
```
This workflow does not execute a target, launch atos, or search for dSYMs.
+5 -45
View File
@@ -1,6 +1,4 @@
graph TD
%% Selected layer owners and dependency/wiring paths, not a directory or import inventory.
%% Current deep-provider candidates are wired in src/composition/binary.ts.
subgraph "Entry Points"
CLI["src/cli.ts + src/cli/<br/>One-shot CLI adapter<br/>setup, diagnostics, analysis, decompilation"]
MAIN["src/main.ts + src/main/<br/>MCP server adapter: stdio default, streamable HTTP opt-in<br/>config, session, process lifetime"]
@@ -23,26 +21,20 @@ graph TD
SESSION["SessionProviderRouter + BinarySession<br/>one immutable deep binding per target<br/>disjoint auxiliary routing"]
REGISTRY["AnalysisProviderRegistry<br/>sorted deep candidates<br/>deterministic selection, no fallback"]
RECORDS["src/application/investigation/<br/>composed InvestigationRecords + narrow ports<br/>EvidenceLedger and Unknown ownership"]
SNAPSHOT["BinarySessionRecords + AnalysisSnapshotCache<br/>binary target/profile snapshots and session file ownership<br/>record coordination and post-commit notifications"]
SNAPSHOT["BinarySessionRecords + AnalysisSnapshotCache<br/>binary target/profile snapshots<br/>compatibility facade and post-commit notifications"]
JS_RECON["src/application/javascript/ + src/artifacts/javascript/<br/>JavaScript artifact reconstruction<br/>directory/ASAR reads<br/>AST-only bundle projection"]
end
subgraph "Provider Layers (selected adapters)"
subgraph "Provider Layers"
PROVIDER_OWNERS["src/providers/<br/>shared deferred client and inspection owners<br/>exact cleanup retry and attributed failures"]
PWNTOOLS["src/native/pwntools/ + src/native/pwndbg/<br/>BYO offline ELF/core adapters<br/>owned snapshots/Python and optional core-only GDB<br/>unchanged upstream parsers, ABI structures and mapping API"]
WASM["src/wasm/<br/>caller-supplied WABT; immutable artifact inspection<br/>owned process supervision and retryable workspace cleanup"]
APKSIG["src/apkSignature/<br/>in-process APK Signing Block + v1 reader<br/>bounded single read, no device or execution"]
EVM["src/evm/<br/>unchanged EVMole/WASM in bounded owned worker<br/>explicit carrier parsing and inferred interface adapter"]
HOPPER["src/hopper/<br/>Hopper launch & Unix-socket protocol<br/>BridgeLauncher, HopperClient, protocol"]
GHIDRA["src/ghidra/<br/>Ghidra discovery, profile & function analysis<br/>snapshot, Unix/Windows transport, bounded lifecycle"]
IDA["src/ida/<br/>Upstream MCP contract adaptation<br/>live GUI binding or owned headless database"]
KUNA["src/kuna/<br/>BYO Kuna deep analysis candidate<br/>discovery reads metadata; version probe on target open"]
IDA_MCP["External ida-pro-mcp<br/>GUI proxy or database supervisor<br/>upstream installation and engine lifecycle"]
KUNA["src/kuna/<br/>BYO Kuna decompiler, a deep analysis candidate<br/>discovery reads metadata only; version probe when a target opens"]
AUX["src/artifacts/ + src/native/<br/>artifact inventory, bounded directory triage and extraction<br/>format readers and native capabilities"]
BROWSER["src/browser/<br/>CDP / Playwright capture<br/>captured scripts and native module resolution<br/>execution/: coverage, listener and source attribution"]
JS_CODEC["src/javascript/sourceMaps/<br/>pinned upstream map codec<br/>owned heap-limited Node process, complete point evidence"]
INSPECTOR["src/inspector/<br/>passive V8 Inspector discovery and capture<br/>script and target locations"]
@@ -54,19 +46,12 @@ graph TD
FIRMWARE["src/firmware/<br/>Binwalk / Unblob adapters<br/>owned processes, report normalization, private extraction"]
ANDROID["src/android/<br/>Static APK adapter<br/>owned SDK stdio, pinned headless JADX, normalization"]
REFERENCE["src/reference/<br/>Filesystem reference-source reader<br/>bounded traversal, validation, errors"]
ANDROID_TOOLS["src/adb/ + src/apktool/ + src/jeb/<br/>BYO device, resource-decoding and JEB adapters<br/>owned adb and Apktool processes; JEB engine client over MCP"]
CAPTURE_TOOLS["src/captures/ + src/pcap/ + src/signals/ + src/sqlite/<br/>BYO tshark, pcap, sigrok and SQLite child inspection<br/>selected captures, recordings and databases; owned processes"]
NATIVE_READERS["src/go/ + src/rust/ + src/il2cpp/ + src/flutter/ + src/gpu/<br/>Bundled in-process binary metadata readers<br/>Go build info, Rust dependencies, IL2CPP, Dart build ID, GPU code objects<br/>src/compression/: shared codec-result helper for rust and gpu"]
FORMAT_READERS["src/model/ + src/python/ + src/erlang/ + src/electron/<br/>Bundled in-process format readers<br/>GGUF and safetensors headers, Python .pyc, Erlang BEAM, Electron fuses"]
RUNTIME["src/frida/ + src/cutter/<br/>Frida instrumentation sessions and live Cutter bridge client"]
end
subgraph "Provider Process Foundation"
PROCESS["src/process/<br/>owned process groups, private roots<br/>deadlines, bounded file reads and diagnostics, cleanup"]
WINDOWS["src/windows/<br/>Windows host boundary adapters<br/>package loader, opaque handles, owned jobs and private runtimes"]
WINDOWS_NATIVE["native/windows/src/<br/>Node-API implementation<br/>NTFS admission, protected DACLs and atomic Job Object assignment"]
FILESYSTEM["src/filesystem/<br/>owned file and directory handles<br/>stable reads, staged output, caller paths"]
end
subgraph "Contracts Layer"
@@ -75,7 +60,7 @@ graph TD
end
subgraph "Domain Layer"
DOMAIN["src/domain/<br/>Pure side-effect-free modules<br/>format declarations, Evidence, comparisons, application graphs"]
DOMAIN["src/domain/<br/>Pure side-effect-free modules<br/>evidence, comparisons, application graphs"]
end
subgraph "Configuration"
@@ -86,8 +71,8 @@ graph TD
TESTS["tests/<br/>Vitest suite<br/>fixtures/ holds fake launcher, bridge, and CDP seams"]
end
subgraph "Development Evaluation"
EVALUATION["tests/evaluation/model-evals/<br/>Agent release evaluation<br/>session transcript scoring"]
subgraph "Evaluation"
EVALUATION["src/evaluation/<br/>Agent release evaluation<br/>session transcript scoring"]
end
DISPATCH --> CLI
@@ -116,8 +101,6 @@ graph TD
SERVER -->|narrow record ports| RECORDS
RECORDS --> DOMAIN
COMPOSITION --> WASM
COMPOSITION --> APKSIG
APKSIG --> DOMAIN
WASM --> PROCESS
WASM --> DOMAIN
COMPOSITION --> PWNTOOLS
@@ -126,7 +109,6 @@ graph TD
COMPOSITION --> HOPPER
COMPOSITION --> GHIDRA
COMPOSITION --> IDA
COMPOSITION --> KUNA
COMPOSITION --> AUX
IDA --> IDA_MCP
IDA --> CONTRACTS
@@ -157,7 +139,6 @@ graph TD
GHIDRA --> GHIDRA_BRIDGE
HOPPER --> PROCESS
GHIDRA --> PROCESS
KUNA --> PROCESS
GHIDRA --> PROVIDER_OWNERS
APP --> PROVIDER_OWNERS
PROVIDER_OWNERS --> PROCESS
@@ -175,7 +156,6 @@ graph TD
CONTRACTS --> DOMAIN
HOPPER --> DOMAIN
GHIDRA --> DOMAIN
KUNA --> DOMAIN
AUX --> DOMAIN
BROWSER --> DOMAIN
PROCESS --> DOMAIN
@@ -192,23 +172,3 @@ graph TD
APP -->|EVM interface port| EVM
EVM --> PROCESS
EVM --> DOMAIN
COMPOSITION --> KUNA
KUNA --> PROCESS
KUNA --> FILESYSTEM
KUNA --> DOMAIN
COMPOSITION --> ANDROID_TOOLS
ANDROID_TOOLS --> PROCESS
ANDROID_TOOLS --> DOMAIN
COMPOSITION --> CAPTURE_TOOLS
CAPTURE_TOOLS --> PROCESS
CAPTURE_TOOLS --> DOMAIN
COMPOSITION --> NATIVE_READERS
NATIVE_READERS --> DOMAIN
COMPOSITION --> FORMAT_READERS
FORMAT_READERS --> DOMAIN
SERVER --> RUNTIME
CLI --> RUNTIME
RUNTIME --> DOMAIN
PROCESS --> FILESYSTEM
AUX --> FILESYSTEM
APP --> FILESYSTEM
-118
View File
@@ -1,118 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="263" viewBox="0 0 960 263" role="img" aria-labelledby="title desc">
<title id="title">REA showcases</title>
<desc id="desc">Three illustrations. A brick-breaker screen with a speaker on each side stands for the DX-Ball sound-pan showcase. A browser window joined by a wire to a clipboard stands for the Notion clipboard-bridge showcase. A ring of 24 dots with one orange dot around a dark centre stands for the TH04 bullet-ring showcase.</desc>
<rect width="960" height="263" fill="#11111b"/>
<rect x="43" y="16.5" width="262" height="235.5" fill="#181825"/>
<rect x="354" y="14.5" width="258" height="237.5" fill="#181825"/>
<rect x="664" y="16.5" width="260" height="235.5" fill="#181825"/>
<g id="dx-ball">
<rect x="108.5" y="54" width="140" height="160" fill="#000000" fill-opacity="0.45"/>
<rect x="106.85" y="52.1" width="136" height="157" fill="#181825" stroke="#cdd6f4" stroke-width="3.7"/>
<rect x="110.9" y="55.9" width="128.2" height="149.6" fill="none" stroke="#6c7086" stroke-width="2.2"/>
<rect x="112.6" y="58.5" width="28.4" height="14.3" fill="#000000" fill-opacity="0.45"/>
<rect x="145.6" y="58.5" width="28.4" height="14.3" fill="#000000" fill-opacity="0.45"/>
<rect x="178.6" y="58.5" width="28.4" height="14.3" fill="#000000" fill-opacity="0.45"/>
<rect x="211.6" y="58.5" width="28.4" height="14.3" fill="#000000" fill-opacity="0.45"/>
<rect x="112.6" y="77.39999999999999" width="28.4" height="14.3" fill="#000000" fill-opacity="0.45"/>
<rect x="145.6" y="77.39999999999999" width="28.4" height="14.3" fill="#000000" fill-opacity="0.45"/>
<rect x="178.6" y="77.39999999999999" width="28.4" height="14.3" fill="#000000" fill-opacity="0.45"/>
<rect x="211.6" y="77.39999999999999" width="28.4" height="14.3" fill="#000000" fill-opacity="0.45"/>
<rect x="111.8" y="57.900000000000006" width="26.2" height="11.9" fill="#45475a" stroke="#cdd6f4" stroke-width="2.4"/>
<rect x="144.79999999999998" y="57.900000000000006" width="26.2" height="11.9" fill="#45475a" stroke="#cdd6f4" stroke-width="2.4"/>
<rect x="177.79999999999998" y="57.900000000000006" width="26.2" height="11.9" fill="#45475a" stroke="#cdd6f4" stroke-width="2.4"/>
<rect x="210.79999999999998" y="57.900000000000006" width="26.2" height="11.9" fill="#45475a" stroke="#cdd6f4" stroke-width="2.4"/>
<rect x="111.8" y="75.0" width="26.2" height="11.9" fill="#45475a" stroke="#cdd6f4" stroke-width="2.4"/>
<rect x="144.79999999999998" y="75.0" width="26.2" height="11.9" fill="#45475a" stroke="#cdd6f4" stroke-width="2.4"/>
<rect x="177.79999999999998" y="75.0" width="26.2" height="11.9" fill="#45475a" stroke="#cdd6f4" stroke-width="2.4"/>
<rect x="210.79999999999998" y="75.0" width="26.2" height="11.9" fill="#45475a" stroke="#cdd6f4" stroke-width="2.4"/>
<ellipse cx="173.4" cy="157.2" rx="8.4" ry="8.2" fill="#000000" fill-opacity="0.45"/>
<ellipse cx="171.9" cy="154.5" rx="7.3" ry="7.2" fill="#585b70" stroke="#cdd6f4" stroke-width="2"/>
<rect x="165.5" y="149.5" width="3.2" height="2.6" fill="#a6adc8"/>
<rect x="145.5" y="195.2" width="57.6" height="12.4" fill="#585b70" stroke="#cdd6f4" stroke-width="2.2"/>
<rect x="144.5" y="204" width="59.3" height="6.5" fill="#cdd6f4"/>
<g transform="translate(0 0)">
<path d="M61.5 117.5H69.6L78 108.5V141.2L69.6 132.8H61.5Z" fill="#585b70" stroke="#cdd6f4" stroke-width="2.3" stroke-linejoin="miter"/>
<path d="M78.1 106V143" stroke="#cdd6f4" stroke-width="3.4" fill="none"/>
<g fill="none" stroke="#89b4fa" stroke-width="2.2" stroke-linecap="butt" stroke-dasharray="2.6 1.4">
<path d="M84.8 118.2Q87.6 125 84.8 131.8"/>
<path d="M90.2 112.4Q94.5 125 90.2 137.6"/>
<path d="M95.8 108.2Q101.5 125 95.8 141.8"/>
</g>
</g>
<g transform="translate(190 0)">
<path d="M61.5 117.5H69.6L78 108.5V141.2L69.6 132.8H61.5Z" fill="#585b70" stroke="#cdd6f4" stroke-width="2.3" stroke-linejoin="miter"/>
<path d="M78.1 106V143" stroke="#cdd6f4" stroke-width="3.4" fill="none"/>
<g fill="none" stroke="#89b4fa" stroke-width="2.2" stroke-linecap="butt" stroke-dasharray="2.6 1.4">
<path d="M84.8 118.2Q87.6 125 84.8 131.8"/>
<path d="M90.2 112.4Q94.5 125 90.2 137.6"/>
<path d="M95.8 108.2Q101.5 125 95.8 141.8"/>
</g>
</g>
</g>
<g id="clipboard-bridge">
<rect x="390.3" y="83.6" width="100.6" height="82.6" fill="#000000" fill-opacity="0.45"/>
<rect x="387.7" y="81.2" width="97.8" height="79.7" fill="#181825" stroke="#cdd6f4" stroke-width="2.9"/>
<rect x="389.2" y="82.7" width="94.9" height="13" fill="#45475a"/>
<path d="M387.7 97H485.5" stroke="#cdd6f4" stroke-width="3.1"/>
<rect x="394.7" y="86.6" width="4.6" height="5" fill="#fab387"/>
<rect x="402.7" y="86.6" width="4.6" height="5" fill="#f9e2af"/>
<rect x="410.8" y="86.6" width="4.6" height="5" fill="#a6adc8"/>
<rect x="398" y="107.4" width="10.6" height="9.4" fill="#7f849c"/>
<rect x="412.4" y="107.9" width="16.8" height="6" fill="#7f849c"/>
<rect x="434" y="107.9" width="42.4" height="6.2" fill="#7f849c"/>
<rect x="412.2" y="117.3" width="64" height="2.6" fill="#6c7086"/>
<rect x="412.2" y="124.9" width="55.3" height="3.6" fill="#6c7086"/>
<rect x="412.2" y="133.2" width="50.3" height="3.8" fill="#6c7086"/>
<rect x="412.2" y="142" width="13.2" height="3.4" fill="#6c7086"/>
<rect x="430" y="142" width="14.4" height="3.4" fill="#6c7086"/>
<rect x="449" y="142" width="27.6" height="3.2" fill="#313244"/>
<rect x="412.2" y="150" width="12.6" height="2.9" fill="#6c7086"/>
<path d="M455.2 162V187H476M497 187H508.2V163.2H523" fill="none" stroke="#89b4fa" stroke-width="3" stroke-linejoin="miter"/>
<rect x="477.5" y="178.3" width="23" height="21" fill="#000000" fill-opacity="0.45"/>
<path d="M484.7 173.2V177" stroke="#cdd6f4" stroke-width="2.2"/>
<rect x="476.4" y="177.5" width="20.2" height="18.1" fill="#585b70" stroke="#cdd6f4" stroke-width="2.2"/>
<rect x="481.4" y="178.6" width="4.6" height="16" fill="#313244"/>
<rect x="477.2" y="191" width="4.6" height="4.4" fill="#f9e2af"/>
<rect x="481.6" y="183.8" width="3.4" height="3" fill="#fab387"/>
<rect x="525.4" y="114.6" width="60.6" height="75" fill="#000000" fill-opacity="0.45"/>
<rect x="524.7" y="112.5" width="56.4" height="69.4" fill="#45475a" stroke="#cdd6f4" stroke-width="3"/>
<rect x="531.6" y="118.6" width="42.3" height="53.4" fill="#1e1e2e"/>
<rect x="531.6" y="172" width="42.3" height="8.4" fill="#45475a"/>
<path d="M531.6 175.8H575" stroke="#cdd6f4" stroke-width="1.8" stroke-opacity="0.8"/>
<path d="M547.4 103.3H556.6V106.2H559.6V109.2H564.9V120.3H540.1V109.2H545V106.2H547.4Z" fill="#585b70" stroke="#cdd6f4" stroke-width="2" stroke-linejoin="miter"/>
<rect x="549.4" y="108.4" width="2.8" height="1.9" fill="#181825"/>
<rect x="536.6" y="131.8" width="32.3" height="3.4" fill="#7f849c"/>
<rect x="536.6" y="138.8" width="32.3" height="3.4" fill="#7f849c"/>
<rect x="536.6" y="145.8" width="32.3" height="3.4" fill="#7f849c"/>
<rect x="536.6" y="152.8" width="26.8" height="3.4" fill="#7f849c"/>
<rect x="536.6" y="159.8" width="14.6" height="3.4" fill="#7f849c"/>
</g>
<g id="bullet-ring">
<circle cx="783" cy="74.5" r="3.8" fill="#89b4fa"/>
<circle cx="798" cy="72" r="3.8" fill="#89b4fa"/>
<circle cx="812.5" cy="74.5" r="3.8" fill="#89b4fa"/>
<circle cx="769.5" cy="81" r="3.8" fill="#89b4fa"/>
<circle cx="826.5" cy="81" r="3.8" fill="#89b4fa"/>
<circle cx="756.5" cy="90" r="3.8" fill="#89b4fa"/>
<circle cx="839" cy="90" r="3.8" fill="#89b4fa"/>
<circle cx="746.5" cy="103" r="3.8" fill="#89b4fa"/>
<circle cx="849" cy="103" r="3.8" fill="#89b4fa"/>
<circle cx="740.5" cy="117" r="3.8" fill="#89b4fa"/>
<circle cx="854.5" cy="117" r="3.8" fill="#89b4fa"/>
<circle cx="738" cy="134" r="3.8" fill="#89b4fa"/>
<circle cx="856.5" cy="133.5" r="3.8" fill="#89b4fa"/>
<circle cx="741" cy="149" r="3.8" fill="#89b4fa"/>
<circle cx="854" cy="149" r="3.8" fill="#89b4fa"/>
<circle cx="747.5" cy="163" r="3.8" fill="#89b4fa"/>
<circle cx="848" cy="163" r="3.8" fill="#89b4fa"/>
<circle cx="757.5" cy="175" r="3.8" fill="#89b4fa"/>
<circle cx="770" cy="184" r="3.8" fill="#89b4fa"/>
<circle cx="826.5" cy="184" r="3.8" fill="#89b4fa"/>
<circle cx="838.5" cy="175" r="3.8" fill="#89b4fa"/>
<circle cx="784" cy="191" r="3.8" fill="#89b4fa"/>
<circle cx="798" cy="192.5" r="3.8" fill="#89b4fa"/>
<circle cx="812" cy="190.5" r="3.8" fill="#89b4fa"/>
<circle cx="833.5" cy="109" r="4.1" fill="#fab387"/>
<circle cx="797.4" cy="133" r="10.9" fill="#cdd6f4"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

-118
View File
@@ -1,118 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="263" viewBox="0 0 960 263" role="img" aria-labelledby="title desc">
<title id="title">REA showcases</title>
<desc id="desc">Three illustrations. A brick-breaker screen with a speaker on each side stands for the DX-Ball sound-pan showcase. A browser window joined by a wire to a clipboard stands for the Notion clipboard-bridge showcase. A ring of 24 dots with one orange dot around a dark centre stands for the TH04 bullet-ring showcase.</desc>
<rect width="960" height="263" fill="#fafbfb"/>
<rect x="43" y="16.5" width="262" height="235.5" fill="#f4f6f5"/>
<rect x="354" y="14.5" width="258" height="237.5" fill="#f4f6f5"/>
<rect x="664" y="16.5" width="260" height="235.5" fill="#f4f6f5"/>
<g id="dx-ball">
<rect x="108.5" y="54" width="140" height="160" fill="#000000" fill-opacity="0.27"/>
<rect x="106.85" y="52.1" width="136" height="157" fill="#f4f6f5" stroke="#191a1d" stroke-width="3.7"/>
<rect x="110.9" y="55.9" width="128.2" height="149.6" fill="none" stroke="#a5a6a9" stroke-width="2.2"/>
<rect x="112.6" y="58.5" width="28.4" height="14.3" fill="#000000" fill-opacity="0.27"/>
<rect x="145.6" y="58.5" width="28.4" height="14.3" fill="#000000" fill-opacity="0.27"/>
<rect x="178.6" y="58.5" width="28.4" height="14.3" fill="#000000" fill-opacity="0.27"/>
<rect x="211.6" y="58.5" width="28.4" height="14.3" fill="#000000" fill-opacity="0.27"/>
<rect x="112.6" y="77.39999999999999" width="28.4" height="14.3" fill="#000000" fill-opacity="0.27"/>
<rect x="145.6" y="77.39999999999999" width="28.4" height="14.3" fill="#000000" fill-opacity="0.27"/>
<rect x="178.6" y="77.39999999999999" width="28.4" height="14.3" fill="#000000" fill-opacity="0.27"/>
<rect x="211.6" y="77.39999999999999" width="28.4" height="14.3" fill="#000000" fill-opacity="0.27"/>
<rect x="111.8" y="57.900000000000006" width="26.2" height="11.9" fill="#5a636d" stroke="#191a1d" stroke-width="2.4"/>
<rect x="144.79999999999998" y="57.900000000000006" width="26.2" height="11.9" fill="#5a636d" stroke="#191a1d" stroke-width="2.4"/>
<rect x="177.79999999999998" y="57.900000000000006" width="26.2" height="11.9" fill="#5a636d" stroke="#191a1d" stroke-width="2.4"/>
<rect x="210.79999999999998" y="57.900000000000006" width="26.2" height="11.9" fill="#5a636d" stroke="#191a1d" stroke-width="2.4"/>
<rect x="111.8" y="75.0" width="26.2" height="11.9" fill="#5a636d" stroke="#191a1d" stroke-width="2.4"/>
<rect x="144.79999999999998" y="75.0" width="26.2" height="11.9" fill="#5a636d" stroke="#191a1d" stroke-width="2.4"/>
<rect x="177.79999999999998" y="75.0" width="26.2" height="11.9" fill="#5a636d" stroke="#191a1d" stroke-width="2.4"/>
<rect x="210.79999999999998" y="75.0" width="26.2" height="11.9" fill="#5a636d" stroke="#191a1d" stroke-width="2.4"/>
<ellipse cx="173.4" cy="157.2" rx="8.4" ry="8.2" fill="#000000" fill-opacity="0.27"/>
<ellipse cx="171.9" cy="154.5" rx="7.3" ry="7.2" fill="#4d5863" stroke="#191a1d" stroke-width="2"/>
<rect x="165.5" y="149.5" width="3.2" height="2.6" fill="#aeb1b4"/>
<rect x="145.5" y="195.2" width="57.6" height="12.4" fill="#525c65" stroke="#191a1d" stroke-width="2.2"/>
<rect x="144.5" y="204" width="59.3" height="6.5" fill="#191a1d"/>
<g transform="translate(0 0)">
<path d="M61.5 117.5H69.6L78 108.5V141.2L69.6 132.8H61.5Z" fill="#56616b" stroke="#191a1d" stroke-width="2.3" stroke-linejoin="miter"/>
<path d="M78.1 106V143" stroke="#191a1d" stroke-width="3.4" fill="none"/>
<g fill="none" stroke="#274b77" stroke-width="2.2" stroke-linecap="butt" stroke-dasharray="2.6 1.4">
<path d="M84.8 118.2Q87.6 125 84.8 131.8"/>
<path d="M90.2 112.4Q94.5 125 90.2 137.6"/>
<path d="M95.8 108.2Q101.5 125 95.8 141.8"/>
</g>
</g>
<g transform="translate(190 0)">
<path d="M61.5 117.5H69.6L78 108.5V141.2L69.6 132.8H61.5Z" fill="#56616b" stroke="#191a1d" stroke-width="2.3" stroke-linejoin="miter"/>
<path d="M78.1 106V143" stroke="#191a1d" stroke-width="3.4" fill="none"/>
<g fill="none" stroke="#274b77" stroke-width="2.2" stroke-linecap="butt" stroke-dasharray="2.6 1.4">
<path d="M84.8 118.2Q87.6 125 84.8 131.8"/>
<path d="M90.2 112.4Q94.5 125 90.2 137.6"/>
<path d="M95.8 108.2Q101.5 125 95.8 141.8"/>
</g>
</g>
</g>
<g id="clipboard-bridge">
<rect x="390.3" y="83.6" width="100.6" height="82.6" fill="#000000" fill-opacity="0.27"/>
<rect x="387.7" y="81.2" width="97.8" height="79.7" fill="#f4f6f5" stroke="#191a1d" stroke-width="2.9"/>
<rect x="389.2" y="82.7" width="94.9" height="13" fill="#5a636d"/>
<path d="M387.7 97H485.5" stroke="#191a1d" stroke-width="3.1"/>
<rect x="394.7" y="86.6" width="4.6" height="5" fill="#c3693b"/>
<rect x="402.7" y="86.6" width="4.6" height="5" fill="#be9a53"/>
<rect x="410.8" y="86.6" width="4.6" height="5" fill="#b3b3b4"/>
<rect x="398" y="107.4" width="10.6" height="9.4" fill="#666671"/>
<rect x="412.4" y="107.9" width="16.8" height="6" fill="#666c74"/>
<rect x="434" y="107.9" width="42.4" height="6.2" fill="#666c74"/>
<rect x="412.2" y="117.3" width="64" height="2.6" fill="#7a7c82"/>
<rect x="412.2" y="124.9" width="55.3" height="3.6" fill="#7a7c82"/>
<rect x="412.2" y="133.2" width="50.3" height="3.8" fill="#6d7279"/>
<rect x="412.2" y="142" width="13.2" height="3.4" fill="#6d7279"/>
<rect x="430" y="142" width="14.4" height="3.4" fill="#6d7279"/>
<rect x="449" y="142" width="27.6" height="3.2" fill="#e8e8e8"/>
<rect x="412.2" y="150" width="12.6" height="2.9" fill="#6d7279"/>
<path d="M455.2 162V187H476M497 187H508.2V163.2H523" fill="none" stroke="#274b77" stroke-width="3" stroke-linejoin="miter"/>
<rect x="477.5" y="178.3" width="23" height="21" fill="#000000" fill-opacity="0.27"/>
<path d="M484.7 173.2V177" stroke="#191a1d" stroke-width="2.2"/>
<rect x="476.4" y="177.5" width="20.2" height="18.1" fill="#525c65" stroke="#191a1d" stroke-width="2.2"/>
<rect x="481.4" y="178.6" width="4.6" height="16" fill="#323435"/>
<rect x="477.2" y="191" width="4.6" height="4.4" fill="#be9a53"/>
<rect x="481.6" y="183.8" width="3.4" height="3" fill="#c3693b"/>
<rect x="525.4" y="114.6" width="60.6" height="75" fill="#000000" fill-opacity="0.27"/>
<rect x="524.7" y="112.5" width="56.4" height="69.4" fill="#545e69" stroke="#191a1d" stroke-width="3"/>
<rect x="531.6" y="118.6" width="42.3" height="53.4" fill="#ededec"/>
<rect x="531.6" y="172" width="42.3" height="8.4" fill="#545e69"/>
<path d="M531.6 175.8H575" stroke="#191a1d" stroke-width="1.8" stroke-opacity="0.8"/>
<path d="M547.4 103.3H556.6V106.2H559.6V109.2H564.9V120.3H540.1V109.2H545V106.2H547.4Z" fill="#545e69" stroke="#191a1d" stroke-width="2" stroke-linejoin="miter"/>
<rect x="549.4" y="108.4" width="2.8" height="1.9" fill="#f4f6f5"/>
<rect x="536.6" y="131.8" width="32.3" height="3.4" fill="#706f74"/>
<rect x="536.6" y="138.8" width="32.3" height="3.4" fill="#706f74"/>
<rect x="536.6" y="145.8" width="32.3" height="3.4" fill="#706f74"/>
<rect x="536.6" y="152.8" width="26.8" height="3.4" fill="#706f74"/>
<rect x="536.6" y="159.8" width="14.6" height="3.4" fill="#706f74"/>
</g>
<g id="bullet-ring">
<circle cx="783" cy="74.5" r="3.8" fill="#274b77"/>
<circle cx="798" cy="72" r="3.8" fill="#274b77"/>
<circle cx="812.5" cy="74.5" r="3.8" fill="#274b77"/>
<circle cx="769.5" cy="81" r="3.8" fill="#274b77"/>
<circle cx="826.5" cy="81" r="3.8" fill="#274b77"/>
<circle cx="756.5" cy="90" r="3.8" fill="#274b77"/>
<circle cx="839" cy="90" r="3.8" fill="#274b77"/>
<circle cx="746.5" cy="103" r="3.8" fill="#274b77"/>
<circle cx="849" cy="103" r="3.8" fill="#274b77"/>
<circle cx="740.5" cy="117" r="3.8" fill="#274b77"/>
<circle cx="854.5" cy="117" r="3.8" fill="#274b77"/>
<circle cx="738" cy="134" r="3.8" fill="#274b77"/>
<circle cx="856.5" cy="133.5" r="3.8" fill="#274b77"/>
<circle cx="741" cy="149" r="3.8" fill="#274b77"/>
<circle cx="854" cy="149" r="3.8" fill="#274b77"/>
<circle cx="747.5" cy="163" r="3.8" fill="#274b77"/>
<circle cx="848" cy="163" r="3.8" fill="#274b77"/>
<circle cx="757.5" cy="175" r="3.8" fill="#274b77"/>
<circle cx="770" cy="184" r="3.8" fill="#274b77"/>
<circle cx="826.5" cy="184" r="3.8" fill="#274b77"/>
<circle cx="838.5" cy="175" r="3.8" fill="#274b77"/>
<circle cx="784" cy="191" r="3.8" fill="#274b77"/>
<circle cx="798" cy="192.5" r="3.8" fill="#274b77"/>
<circle cx="812" cy="190.5" r="3.8" fill="#274b77"/>
<circle cx="833.5" cy="109" r="4.1" fill="#c3693b"/>
<circle cx="797.4" cy="133" r="10.9" fill="#1c222a"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.7 KiB

+3 -254
View File
@@ -8,12 +8,9 @@ ordinary binary inspection use the same modular capability.
## Bring your own engine
Real verification covers **Linux x64** hosts and **EXEC/DYN/REL** objects in
these profiles: ELF64 x86-64, ELF32 i386, ELF32 little-endian ARM and ELF32
little- or big-endian MIPS. Other machines, classes and byte orders, including
AArch64 and big-endian ARM, return `unsupported_target`. Provide an absolute
Python executable whose environment already contains unchanged pwntools 4.15.0,
pyelftools 0.33 and Unicorn 2.1.2:
Initial real verification covers **Linux x64, ELF64 x86-64 little-endian
EXEC/DYN/REL**. Provide an absolute Python executable whose environment already
contains unchanged pwntools 4.15.0, pyelftools 0.33 and Unicorn 2.1.2:
```sh
REA_PWNTOOLS_PYTHON=/absolute/isolated-env/bin/python \
@@ -32,16 +29,6 @@ the selected object as a host process, or requests runtime library resolution. T
uses isolated mode and an owned cache. Exact upstream profiles are recorded in
[upstream provenance](https://github.com/morluto/rea/blob/main/third_party/pwntools/README.md).
The adapter skips pwntools kernel configuration and version discovery: those
memory-content scans supply none of this report's fields. Ordinary `IKCFG_ST`
data is not interpreted as a kernel-config request, and virtual zero-filled
LOAD tails are reported from their headers without materializing them for that
scan. GOT/PLT inference and the advertised static mitigation indicators still
use the pinned upstream implementations. Header-table file ranges are checked
before dependency seeks. Structural inventory and the convenience-map engine
still parse the snapshot separately; eliminating that remaining duplication
requires preserving the upstream GOT/PLT and mitigation facts.
## Interpreting results
- Artifact path, SHA-256 and size identify original selected bytes. All parsing
@@ -103,12 +90,6 @@ one section or symbol, or a stable page without re-running the decoder. CLI
one-shot invocations pass portable inline Evidence JSON; an MCP session uses
the retained `evidence_id`. Page `limit` is required and bounded by the
measured MCP stdio budget documented on the tool contract.
An empty retained section or symbol collection has no valid item index. Its page
view can still return an empty page.
Malformed authenticated layout input remains rejected with the owning schema's
safe field diagnostics under `source.normalized_result`, including the field
path and type or value reason. Evidence authentication and decoder validation
remain separate boundaries.
Over MCP, `inspect_binary_layout` with `"detail": "summary"` retains the
complete layout Evidence in the session and returns only its summary view.
@@ -189,31 +170,6 @@ subject to earlier cancellation or the startup deadline. `output_incomplete`
marks a snapshot taken before stream closure; cleanup ownership remains with
the session until release succeeds.
A kernel or container OOM killer ends Ghidra with `SIGKILL` and prints no
`OutOfMemoryError`. When a process or protocol failure records
`exit_signal: "SIGKILL"` and no `stop_requested`, REA reports the signal
instead of generic `rea doctor` advice. The process supervisor records
`stop_requested: true` once REA begins stopping Ghidra, before it sends any
signal, so REA's own SIGKILL is never reported as an outside kill.
On Linux with cgroup v2, REA also reads its own cgroup's `memory.events` before
launch and again after the kill, locating the group through the cgroup2 mount
in `/proc/self/mountinfo`. `memory_cgroup` records the group path,
`oom_kill_before`, `oom_kill_after` and `memory_max`. If the count rose, REA
returns `resource_constraint` with `resource: "memory"` and `cgroup_memory_max`
in `reported_limits`. The count covers every process in REA's cgroup and its
child groups, which can include processes outside REA's process tree when the
group is shared, so a rise shows that the kernel OOM killer ended a process in
that cgroup or a child group, not which process it chose. `memory_max` is the
limit in bytes, `"max"` when the group sets none, or `null` when it could not be
read; a limit on a parent group is not read. The limit also covers Ghidra's
native decompiler processes, whose number follows the available CPUs, so the
advice does not recommend a larger Java heap. When the session had already
completed an operation, the advice keeps the instruction to reopen the target
with `open_binary`. Without a cgroup v2 memory group, or when the count did not
rise, the failure stays `execution_failure` and names the signal as a possible
out-of-memory kill.
## Portable PE resources
`inspect_pe_resources` / `inspect-pe-resources` inspects an explicit PE32 or
@@ -237,14 +193,6 @@ reported. Directory headers, entries, data entries and payloads retain original
file offsets and lengths. Each opaque payload has its own SHA-256; code page and
reserved data-entry fields are preserved.
Every `directories[]` row retains its identity `path` and the matching incoming
`entry_locations`: both are empty for the root, then contain the type and
optionally the name. Empty branches keep these identities even when they have
no payload leaves. Shared directory bytes produce a row for each incoming path,
with the original directory range and each path's own entry locations. These
rows are included in the metadata budget. A present empty root remains distinct
from an absent resource data directory (`directory: null`, `directories: []`).
RT_GROUP_ICON records expose original image descriptors and RT_ICON candidates.
A reference resolves only when an icon with the same numeric ID and language
exists. Missing references, other-language candidates and declared-size mismatch
@@ -267,202 +215,3 @@ Large Evidence envelopes may require a caller-configured MCP transport receive
buffer because envelope copies add overhead. Requests honor cancellation during
snapshot reading, directory traversal and payload hashing. The operation reads
regular files only and rejects symbolic links.
## Byte entropy triage
`inspect_byte_entropy` / `inspect-byte-entropy` profiles any local regular file
without an analysis engine, execution, extraction or network access.
```sh
rea inspect-byte-entropy ./firmware.bin --block-size 4096 --json
rea inspect-byte-entropy ./firmware.bin --offset 4096 --length 65536 --json
```
```json
{
"name": "inspect_byte_entropy",
"arguments": { "path": "/artifacts/firmware.bin", "detail": "summary" }
}
```
Complete Evidence includes `artifact` (original path, byte size and SHA-256),
`block_size`, `window`, `file_entropy`, `thresholds`, `blocks`, `runs` and
`limitations`. For a 4096-byte zero-filled file, the entropy fields are:
```json
{
"block_size": 4096,
"window": { "offset": 0, "length": 4096 },
"file_entropy": 0,
"thresholds": { "high": 7.2, "low": 1 },
"blocks": [{ "offset": 0, "length": 4096, "entropy": 0 }],
"runs": [{ "offset": 0, "length": 4096, "classification": "low" }]
}
```
Offsets and lengths are decimal byte counts. Blocks begin at the requested
`offset` (default zero); omitted `length` extends to EOF. Explicit windows past
EOF fail rather than silently clipping. The final block may be shorter.
`block_size` must be a power of two from 256 through 65536, default 4096. A
window may produce at most 32,768 block rows; larger requests fail before the
file scan with a capacity error. Choose a shorter window or larger block size.
SHA-256 and `file_entropy` always cover the entire file, including when a window
is selected. Empty files and empty windows are accepted; empty entropy is zero
by convention. The tool reads the size the file reports. A file that reports size
0, such as a Linux `/proc` entry, returns no blocks with a limitation that the
result does not show the file is empty. File identity, size and timestamps are checked across the read;
detected changes produce an error rather than Evidence.
Shannon entropy is reported in bits per byte from 0 to 8, rounded to four decimal
places. Adjacent blocks with reported entropy at least 7.2 form `high` runs;
blocks at most 1.0 form `low` runs. Other blocks interrupt runs. Entropy does not
prove encryption, and this measure cannot distinguish encrypted from compressed
data. Small blocks can underestimate entropy; the result is a triage clue.
MCP `detail: "summary"` retains complete Evidence in the session and returns
file entropy, block/run counts, thresholds, the selected window, and the ten
longest runs (ties ordered by file offset). Use its `parent_evidence_id` to
retrieve complete Evidence, or request its summary with `inspect_analysis_view`.
The CLI returns complete Evidence through the same service. Summary detail
requires a session capable of retaining Evidence.
Reads use one block-sized buffer and a 256-entry histogram. Complete output
can become very large because it includes one row per selected block, and
summary delivery still retains that full list. Use a shorter length or a
larger block size to reduce it. Full Evidence and its serialization require
memory proportional to the number of selected blocks. The 32,768-row ceiling bounds the actual retained block objects and
their serialized representation: a cap-sized run below produced 1,514,877
serialized bytes and increased V8 heap use by 21.2 MiB on the measured host.
The cap therefore bounds this output independently of file size; it is not a
portable process-memory guarantee. Narrow the window or increase block size to
reduce retained rows further.
Local service measurement (macOS arm64, Node 24.18.0, 100,000,000-byte file,
4096-byte blocks, file already in the OS cache): first process run took 1.113 s
wall / 1.148 s CPU with 145.3 MiB peak RSS; a second run in the same process took
1.015 s / 1.035 s CPU, with cumulative peak RSS 155.8 MiB. Each read 100,000,000
bytes and produced 24,415 blocks. Measurements include Evidence construction,
summary projection and JSON serialization, with no child processes. Complete
Evidence serialized to 1,122,303 bytes and its summary to 2,705 bytes. A
cap-sized 134,217,728-byte sparse file with 4096-byte blocks produced 32,768
rows and 1,514,877 serialized bytes; V8 heap use increased by 21.2 MiB and RSS
by 18.1 MiB in that process. This separate measurement used the same macOS
arm64 host and Node version. Cold disk I/O, token counts and an agent
investigation were not measured; there was no previous equivalent tool to
benchmark. These figures do not establish a portable peak-memory bound,
constant memory for retained Evidence or faster agent task completion.
## Portable native exports
`inspect_native_exports` / `inspect-native-exports` reads the export table of an
explicit PE or ELF image on any supported host, without an active target,
native engine, `objdump` or Windows SDK:
```sh
rea inspect-native-exports ./runtimes/linux-x64/native/libexample.so --json
```
```json
{
"name": "inspect_native_exports",
"arguments": { "path": "/artifacts/native/example.dll" }
}
```
The complete Evidence identifies the stable artifact snapshot by path, byte
count and SHA-256. Complete delivery remains the default. For a small initial
MCP reply, select `detail: "summary"`; this retains the complete Evidence and
returns artifact identity, table coverage and row counts. Use its
`normalized_result.parent_evidence_id` with `inspect_analysis_view`:
```json
{
"source": {
"kind": "retained-evidence",
"evidence_id": "ev_<parent digest>"
},
"view": {
"kind": "native-exports",
"collection": "exports",
"names": ["selected_name"],
"fields": ["name"],
"offset": 0,
"limit": 32
}
}
```
Names match exactly and preserve every same-name version or alias. Optional
`indices` selects original row positions, intersecting a name filter when both
are supplied; empty filters select nothing. `source_index` always remains and,
with the parent ID, identifies the original export or issue row. Fields default
to the complete row; an empty field list returns only its identity. ELF `name`
selection also returns raw-byte and encoding qualifications. Fields from the
other format are rejected. `collection: "issues"` pages the complete diagnostic
records, with optional original indices. Offset and limit default to 0 and 64
and apply to matching rows in original order.
View `coverage` describes the selected page; `summary.coverage` or
`exports.coverage` preserves source-table coverage. An exhausted or empty page
does not make a partial source complete. Retained views never reopen the file.
The CLI uses the same projector and requires a durable bundle for a summary:
```sh
rea inspect-native-exports ./libexample.so --detail summary --evidence_output ./exports.bundle.json --json
# Pass the summary's parent ID and the same view as JSON to inspect-analysis-view,
# together with --evidence-bundle ./exports.bundle.json.
```
- PE rows reproduce the export directory: each used export-address-table slot
with its biased `ordinal` (`OrdinalBase` plus the slot index), one row per
name (aliases share an ordinal), `address`
as the preferred-base virtual address, or `forwarder` text when the slot
forwards to another DLL. Unused zero-address slots are omitted;
ordinal-only exports have a null `name`. The name-ordinal table stores an
unbiased 16-bit EAT index; reported biased ordinals may exceed 16 bits. The
directory's DLL name, ordinal base and timestamp are reported, and an image
without an export directory is a complete empty inventory.
- ELF rows list defined `.dynsym` symbols with global, weak or GNU-unique
binding and default or protected visibility. Each row keeps `value`
(`st_value` as stored; bit 0 marks Thumb code on 32-bit ARM), `size`, `type`
and `binding`. ELF string tables do not declare a text encoding. Names that
contain non-ASCII bytes are decoded as UTF-8 when those bytes form valid
UTF-8; `name_utf8_decodable` records whether the non-ASCII name bytes are
valid UTF-8 and `name_bytes_base64` retains those exact bytes (`null` for
ASCII names). When they are not valid UTF-8, `name` uses a byte-preserving
Latin-1 mapping and the raw bytes remain available. Older retained results
report null for the newly added encoding fields. `version` is null for an
unversioned symbol. A versioned
symbol reports its GNU index, a name resolved from `.gnu.version_d` or
`.gnu.version_r`, the corresponding `name_bytes_base64` and
`name_utf8_decodable` facts, and the `hidden` flag from `.gnu.version`.
Version names use the same UTF-8-when-valid, otherwise byte-preserving
Latin-1 display mapping as symbol names; ELF does not declare either
string's text encoding. Older retained results report null for the added
version-name encoding fields. `"unknown"`
means the symbol-version table could not be read. A hidden version is not
the default version that a by-name `dlsym` lookup selects.
Definition names take precedence when both tables name the same index.
Unreadable names, out-of-range name ordinals and unreadable forwarders are
listed in `issues`, their entries are not reported, and coverage is `partial`.
PE export names are bounded null-terminated byte strings and may continue
across adjacent file-backed section mappings when the virtual bytes are
unambiguous. Gaps, zero-fill and conflicting mappings remain unreadable names.
Unresolved symbol versions keep their rows: the version name is null when no
definition or requirement names the index, and `version` is
`"unknown"` when the version table cannot be read; coverage is likewise
`partial`. A dynamic symbol table without its null entry is malformed. An ELF
without section headers is `unsupported`. With section headers, a missing
`SHT_DYNSYM` section is also `unsupported` when a `PT_DYNAMIC` segment is
observed, including a directory containing only the inactive null section.
Enumerating dynamic symbols from that segment is not implemented. A present
section directory with neither a dynamic symbol table nor a dynamic segment
reports a complete empty inventory. Mach-O exports are reported by
`inspect_macho`.
`max_file_bytes` defaults to 256 MiB (maximum 512 MiB; CLI `--max-file-bytes`).
Export directories in PE headers are read like those in sections. An export or
dynamic symbol table above 262144 entries, more than 262144 rows, or more than
16 MiB of retained names fails without partial success. Export rows do not establish that a symbol is reachable, safe to call
or loaded at runtime.
+47 -250
View File
@@ -12,30 +12,16 @@ For request/response bodies and traffic debugging, see
[Fiddler Everywhere captures and MCP](web-network-captures.md#reading-fiddler-captures-saz).
Inspect native SAZ or HAR 1.2 offline with REA, or connect Everywhere's separate MCP server.
HTTP discovery and browser WebSocket inputs require an explicit port. An
explicit default `:80` remains valid and is retained in the canonical endpoint;
omitting the port remains invalid. HTTP `/json/list` discovery preserves usable
siblings when optional metadata is malformed: attachment becomes unknown and
row/field diagnostics appear in `limitations`. Target identity and socket
authority are validated independently before selection and connection.
## Shipped surfaces
- `list_browser_targets` / `rea list-browser-targets` discovers all eligible page targets at the endpoint by default, with an optional exact-origin filter.
- `inspect_web_page` / `rea inspect-web-page` captures DOM structure, accessibility nodes, scripts, resources, safe response/DOM metadata, attach-window network and console metadata, WebSocket frame sizes, workers, quota, and optionally selected storage key names, redacted storage-content fingerprints, or script sources.
- `analyze_web_bundle` / `rea analyze-web-bundle` parses selected script artifacts without execution and derives chunk edges, route and endpoint candidates, vendor fingerprints, static WebMCP declarations, and optionally source-map/original-source evidence.
- `observe_web_session` / `rea observe-web-session` arms an observation window of the requested duration for an external user action and records ordered reload, SPA navigation, redirect, failure, lifecycle, and target-termination metadata.
- `discover_webmcp_tools` / `rea discover-webmcp-tools` uses the experimental CDP WebMCP domain to return observed in-scope registrations and their complete observed `input_schema` declaration and canonical `input_schema_sha256` digest inline. Declarations are untrusted JSON data, including declared types, required fields, enums, examples and reference URLs. REA does not resolve schema references. An omitted optional `inputSchema` is a valid parameterless registration, represented by null schema and digest. These fields replace `input_schema_shape`. Registrations with the same name and URL in distinct observed frames retain separate identities. `tool_key` includes the observed frame ID; it is not a stable identity across navigation or browser sessions. Same-frame registration updates replace that owner only, and removal leaves other owners intact. Declared `readOnly`, `untrustedContent`, `consequential`, and `autosubmit` annotation hints are reported per registration: `true`/`false` are declared values, `null` means the declaration did not provide the hint in that capture, and a missing `consequential` key means the hint was not recorded, either in a historical capture or because the observed annotation was invalid. Consult discovery completeness and exclusions to distinguish these cases. Malformed annotation values or registration stacks retain the registration with incomplete coverage and an `invalid_protocol_value` exclusion. Other malformed protocol payloads can fail discovery explicitly. REA never exposes `WebMCP.invokeTool`.
- `discover_webmcp_tools` / `rea discover-webmcp-tools` uses the experimental CDP WebMCP domain to return every in-scope registration and its complete observed `input_schema` declaration and canonical `input_schema_sha256` digest inline. Declarations are untrusted JSON data, including declared types, required fields, enums, examples and reference URLs. REA does not resolve schema references. These fields replace `input_schema_shape`. Registrations with the same name and URL in distinct observed frames retain separate identities. `tool_key` includes the observed frame ID; it is not a stable identity across navigation or browser sessions. Same-frame registration updates replace that owner only, and removal leaves other owners intact. Declared `readOnly`, `untrustedContent`, `consequential`, and `autosubmit` annotation hints are reported per registration: `true`/`false` are declared values, `null` means the declaration did not provide the hint in that capture, and a missing `consequential` key means the hint was not recorded, either in a historical capture or because the observed annotation was invalid. Consult discovery completeness and exclusions to distinguish these cases. Malformed annotation values retain the registration with incomplete coverage and an `invalid_protocol_value` exclusion. Other malformed protocol payloads can fail discovery explicitly. REA never exposes `WebMCP.invokeTool`.
- `compare_web_captures` / `rea compare-web-captures` compares passive captures or exact-step-aligned browser scenarios. Scenario results commit reproducible literal normalization and expose alignment failures plus artifact-level action, screenshot, DOM, accessibility, URL, history, storage, and event differences. Missing or truncated evidence is never treated as equivalence.
For a compact route query, select `detail: "summary"` and a
`view: {kind: "javascript-routes", collection: "routes", limit: 20}`. The
shared [route inventory](javascript-route-inventory.md) preserves source IDs,
precise locations, partial coverage, and the complete parent Evidence for
subsequent filtered pages. Static candidates never establish complete application
coverage or reachability.
For `analyze_web_bundle`, each `webmcp_declarations` entry's
`schema_property_names` is a source-derived projection of statically known own
property keys, not schema validation. `schema_property_names_coverage` reports
@@ -61,57 +47,6 @@ from a protocol omission. Rejected DOM facts also mark derived URL and agent-hin
metadata incomplete, while valid facts from other nodes and documents remain
available in the capture.
Page network and console coverage starts independently at the first observed
event from that domain or its successful enable acknowledgment. The capture's
lifecycle start is earlier and does not establish domain readiness. Events
received before the enable reply remain part of the observation.
Worker output is a final target-inventory snapshot. `attached` describes any
debugging client, not an REA-owned observing session. The selected page-session
collector does not collect worker-session requests, console events, or scripts,
and workers that disappeared before the snapshot may be missing; worker
coverage is therefore attach-limited. Missing ownership metadata is reported as
`unattributed_target` with unavailable worker attribution, separately
from a proven foreign owner excluded as `out_of_target_scope`. A shared origin
alone establishes neither relationship.
Passive comparison keeps lookup identity, cross-capture correspondence, content
evidence, and inventory coverage separate:
| Observation | Comparison outcome |
| ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
| Renamed WebMCP tool keys/frame IDs with one matching name, owner origin, and frame URL | Compare the registration content; local IDs do not prove change |
| Duplicate WebMCP owners with the same name, origin, and frame URL | Unknown correspondence; complete group count differences still prove change |
| Matching script inventory keys with retained source digests | Compare the retained SHA-256 values even without a producer hash |
| One corresponding script source is unavailable, or neither source nor producer hash is available | Source equality remains unknown; inventory changes and other proven differences survive |
| A partial HTTP response group is a subset of the complete peer | Unknown; omitted repeated responses can explain the difference |
| An observed HTTP response fingerprint is absent from the complete peer group | Changed; omissions cannot explain that retained response |
| A WebSocket connection or frame inventory is incomplete | Do not align retained payloads by position |
Storage fingerprints decode supported CDP primitive/JSON values before hashing.
Debugger descriptions, previews, and remote references do not represent storage
content. Actual JSON properties with those names remain content. Reference-only
objects, unsupported values, and opaque cookie partition identities remain
incomplete. Previously exported fingerprints are not recomputed; recapture both
sides when comparing captures produced across this decoder change.
IndexedDB schema fingerprints with `projection_version: 2` include the observed
database version and object-store definitions. A missing, malformed, or imprecise
version leaves the schema fingerprint incomplete while retaining observed store
facts. Historical fingerprints without this marker covered object stores only:
matching historical hashes cannot establish unchanged database versions, and a
historical hash is not compared with a version-bearing hash. Recapture both
sides for a complete IndexedDB version comparison; other proven storage changes
remain reportable.
Script comparison groups observations by reported URL and compares unordered
content samples. Producer inventory keys include optional metadata, so a changed
key cannot by itself prove script removal or addition. Missing module, length,
language or source-hash facts remain unknown; shared known values and complete
retained source digests can still prove a group change. Partial inventories use
the same omission rule as repeated HTTP samples. WebMCP dropped-tool counts also
prevent equality, and two incomplete owner inventories cannot prove tool
correspondence merely from their different retained registrations.
- `capture_web_screenshot` / `rea capture-web-screenshot` returns a content-addressed visible-viewport PNG.
- `compare_web_screenshots` / `rea compare-web-screenshots` performs bounded local PNG pixel comparison without OCR or external services.
- The CLI comparison commands accept each capture, screenshot artifact or normalization policy as inline JSON or a JSON file path. Use files for screenshot artifacts and scenario captures, whose embedded PNG bytes can exceed the host's command-line length limit.
@@ -155,11 +90,6 @@ preserve these authority and lifecycle differences. They share one exclusion
vocabulary (`BrowserExclusionReason`): every denial reason maps onto it, even
where a wire schema keeps a narrower historical bucket.
Passive page and Inspector capture enable the `Debugger` domain. A target's
`debugger` statement can then pause execution; these observation paths do not
suppress pauses with `Debugger.setSkipAllPauses` or resume the target. Their
lack of explicit pause commands does not guarantee uninterrupted execution.
CDP attachment is not a guarantee that the browser preserves all state.
Chromium can clear another client's `navigator.platform` emulation when REA's
page session disconnects, even if that session sent no emulation commands.
@@ -195,11 +125,14 @@ bundle, for example
`"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"`. Fedora installs
Chromium as `chromium-browser`.
REA attaches through the browser's HTTP discovery endpoint on a loopback port,
or, for some passive tools, through a
[browser socket](#browsers-without-http-discovery). Connection failures report
`endpoint_unreachable`; a reachable HTTP service that returns a non-200 or
non-JSON discovery response reports `invalid_endpoint_response`.
REA needs the browser's HTTP discovery endpoint on a loopback port. A browser
driven over `--remote-debugging-pipe` or a private channel, for example by an
automation SDK, exposes no such endpoint, so `list_browser_targets` has nothing
to attach to. Connection failures report `endpoint_unreachable`; a reachable
HTTP service that returns a non-200 or non-JSON discovery response reports
`invalid_endpoint_response`.
Reproduce that flow in a Chromium-family browser started with
`--remote-debugging-port`.
In a minimal Linux container, headed Chromium under Xvfb can keep running while
its DevTools port never opens. Install the GLES, EGL, and Mesa DRI libraries
@@ -251,9 +184,7 @@ file for you.
the origins you are investigating.
A browser driven only through `--remote-debugging-pipe` or a vendor's private
automation channel has neither endpoint, and REA cannot attach to it. To
inspect such a page, reproduce it in a Chromium-family browser started with
`--remote-debugging-port`.
automation channel has neither endpoint, and REA cannot attach to it.
## Request boundary
@@ -330,11 +261,7 @@ read as registration state only after domain availability is confirmed.
Discovery is attach-only: the command needs a reachable loopback CDP endpoint
and a target ID from `list_browser_targets`, and it never navigates, reloads,
or evaluates the page. Observe the page in the state you want to measure;
registered tools reflect what the protocol delivered. If an in-scope subframe
already exists when discovery attaches, `webmcp_tools` remains attach-limited:
Chromium replay may omit its older registrations. Later live registrations
retain their frame ownership but do not establish recovery of those missing
descriptors. REA does not reload, evaluate, or invoke tools to fill that gap.
registered tools reflect the document as loaded.
Accessibility names/descriptions, console primitive text, JSON body shapes, WebSocket shapes, script content, and storage key names are selected independently:
@@ -376,20 +303,6 @@ are reported in the result.
Call `list_browser_targets` first because target IDs are browser-instance-specific. REA rechecks the selected target's current type and origin immediately before attaching. Without an origin filter, discovery lists all eligible pages; an individual capture defaults to the selected page's current origin.
REA establishes a main-frame origin boundary before enabling Runtime, Debugger, or Network observation, rechecks it before document capture, and validates it again afterward. If the main frame navigates during final capture, REA discards the mixed result and returns `target_changed`.
An observed navigation also takes precedence over failures from stale
document reads, such as a script ID invalidated by reload; cancellation remains
cancellation. Retry after navigation settles, or refresh target discovery if
the selected page was replaced. Origin-scope refusals from page inspection,
runtime observation, screenshots, and WebMCP discovery explain the permitted
HTTP(S) origin scope and direct the caller to select a permitted document or
explicitly revise `allowed_origins`. Page capture also preserves an observed
main-frame scope violation when a later document read fails. An unresolved
initial main frame does not establish an origin violation; its bounded startup
retries retain a distinct refusal.
Screenshot capture requires a valid main-frame identity. Malformed identity
produces a protocol failure; a readable child frame cannot substitute for the
main frame.
The web-session timeline uses CDP monotonic seconds where the event producer
provides a comparable timestamp; absent or invalid timestamps are `null` rather
@@ -403,62 +316,31 @@ events retain their paired prior-hop response context when the timestamped
next-request event falls inside the window. The pre-arm timeline buffer is bounded
by original CDP message bytes. If clock sampling completes after that budget is
exhausted, the operation returns `payload_limit` instead of a partial window.
Cancellation and clock-request failures retain their own outcomes. Session
`load_failed` rows require a previously observed main-frame `Document` request;
subresources, child-frame requests and failures without admitted request ownership
are excluded. Redirects reuse their request identity, and completed/failed requests
release it. Active request identity metadata uses the existing 8 MiB retained-event
allowance; exhaustion fails the observation explicitly instead of guessing scope.
Cancellation and clock-request failures retain their own outcomes.
## Data minimization
Response metadata parses repeated CDP header field lines independently. The
Link projection retains their declarations in order. `csp.directives` lists the
directives of every CSP policy in order, across field lines and comma-separated
policies, without recording policy boundaries, so a directive name can repeat.
Single-valued policy fields combine their lines as one comma-separated value,
as RFC 9110 specifies. Each link keeps its target in `href` and
its relationship context in `context_url` with a separate `context_scope`;
absent `anchor` uses the response URL, while a relative `anchor` resolves
against that URL. Anchored links remain in the projection, but a `link_rel`
agent hint is emitted only when the resolved context matches the response URL
before userinfo credentials are removed. Following [RFC 8288](https://www.rfc-editor.org/rfc/rfc8288.html),
the `rel`, `anchor`, and `type` parameters use their first occurrence, and
quoted-pair escapes are decoded as specified by [RFC 9110 §5.6.4](https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.4).
The context fields are optional when reading older saved captures. Commas
inside URI targets or quoted parameters remain part of those values.
Well-known-resource hints require an exact supported pathname; path case is
preserved because a case variant may identify a different HTTP resource.
The Link projection parses repeated CDP header field lines independently and
retains their declarations in order, including derived untrusted agent hints.
Commas inside URI targets or quoted parameters remain part of those values.
COOP and COEP projections retain the exact declared token after validating the
complete Structured Field Item, including parameters such as `report-to`.
These are declarations, not an assessment of the document's effective policy;
absent, malformed or non-token items project as `null`.
Console entries, WebSocket frame summaries, and selected request/response JSON
shape outputs share one 8 MiB retained-observation allowance per capture. Shape
inference admits expanded path bytes before allocating those paths, including
escaped and Unicode property names. Replacing or removing a request's shapes
refunds their retained charges. A refused body shape preserves network metadata
and any captured peer shape with `resource_budget_exhausted` coverage; a refused
WebSocket frame is counted as dropped rather than reclassified as non-JSON text.
The allowance has no runtime override. Protocol messages and JSON input parsing
remain subject to their separate transport admission; this output allowance does
not bound the whole process heap.
REA retains local observation data and removes only URL userinfo credentials, explicitly declared scenario secrets, and values from structured credential/storage fields:
- URL observations preserve the complete local URL, including query values, fragments, duplicate parameters, and their order. Only username/password userinfo is removed; malformed original URL strings remain available with `origin: null`.
- DOM snapshots retain node types, node names, value lengths, and attribute names, but not text or attribute values.
- Supported DOM URL attributes are projected separately as resolved destinations. Empty `action` on HTML or XHTML forms and empty `formaction` on HTML or XHTML submit controls resolve to the document URL; other relative URL attributes resolve against the captured document base.
- Accessibility structure and reported roles are retained by default, while names and descriptions require `include_accessibility_text: true`. The browser protocol may omit an individual role; that role remains unknown for comparison. Other observed accessibility differences can still establish a change.
- Accessibility structure and roles are retained by default, while names and descriptions require `include_accessibility_text: true`.
- Network observations retain method, status, MIME type, size, type, initiator stack location, and complete URLs with userinfo credentials removed. Redirects reported by CDP are attached as ordered `redirects` on the one request ID, with the prior request URL, CDP response URL, method/type, response status/MIME/encoded length, request timestamp, and the next `requestWillBeSent` event timestamp. CDP delivers `redirectResponse` with that next request event, so the latter is an event boundary rather than an exact response timestamp. Redirect response headers and bodies are discarded; hops outside the selected origins are discarded with the request chain. Headers are discarded after an allowlisted projection of length/encoding, structured CSP/Link/policy fields, and untrusted agent hints. Cookies and authorization headers are never retained.
- Request/response bodies are not requested or parsed by default. When selected, only allowed-origin JSON media types are read, converted immediately to complete value-free typed paths, types and observation counts, and discarded. Each path segment is either `{ "kind": "property", "name": "raw property name" }` or `{ "kind": "array-element" }`; array-element types include primitives and nested arrays. Literal `*` properties remain distinct from array elements. `Network.getResponseBody` is never sent unless JSON body shape capture is selected. A JSON-typed request body that CDP reports through `hasPostData` but omits from `postData`, or that cannot be parsed as JSON, makes `json_body_shapes` unavailable. That request's `body_shapes.status` is `partial`, not `included`, when only its response shape was captured. Base64 response bytes must be valid UTF-8 before JSON interpretation; invalid bytes leave the response shape unavailable and preserve any observed request shape and network metadata. Producer-decoded strings and genuine U+FFFD characters retain their reported meaning. Comparison uses body shapes only when both captures selected them, and compares request/response shapes only where both observations provide those facts. Unavailable or truncated selected coverage makes equivalence unknown; observed differences still report changed.
- Request/response bodies are not requested or parsed by default. When selected, only allowed-origin JSON media types are read, converted immediately to complete value-free typed paths, types and observation counts, and discarded. Each path segment is either `{ "kind": "property", "name": "raw property name" }` or `{ "kind": "array-element" }`; array-element types include primitives and nested arrays. Literal `*` properties remain distinct from array elements. `Network.getResponseBody` is never sent unless JSON body shape capture is selected. Comparison uses body shapes only when both captures selected them, and compares request/response shapes only where both observations provide those facts. Unavailable or truncated selected coverage makes equivalence unknown; observed differences still report changed.
For normalized passive inputs with complete request inventories on both sides,
request additions and removals remain observable even when selected body shapes,
response metadata, or WebSocket coverage are unavailable. HTTP request and
WebSocket inventory coverage are evaluated separately; complete HTTP coverage
cannot prove a missing WebSocket connection. A missing request in a limited inventory cannot prove absence; a retained
request absent from a complete peer can prove a change. This comparison rule does not make live CDP's
cannot prove a missing WebSocket connection. Limited request inventory still
prevents request-absence claims. This comparison rule does not make live CDP's
attach-limited inventory complete or establish body-shape equality.
An unavailable HTTP resource type also leaves request identity incomplete:
observations with the same method and URL remain possible matches across types,
@@ -473,17 +355,13 @@ REA retains local observation data and removes only URL userinfo credentials, ex
uses `websocket_connections` and `websocket_frames`. Drops in other event kinds
and the aggregate total do not by themselves limit HTTP inventory. Proven
modifications remain reportable despite incomplete inventory.
Repeated HTTP samples retain multiplicity. A partial group that is contained
in a complete peer cannot prove modification, even when the omitted response
had different content. A retained sample absent from the complete peer can
prove modification. Two partial groups cannot establish response
correspondence from their different retained samples alone.
Repeated identical HTTP content samples establish multiplicity changes only
with complete request inventories; limited inventories retain known content
comparisons without interpreting duplicate sample counts as changes.
WebSocket stream inventories and connection counts likewise require complete
WebSocket coverage. Retained frame positions are compared only with complete
connection/frame inventories; matching direction/opcode sequences alone do
not prove alignment after drops. Redirect resource types use only shared known metadata:
WebSocket coverage. Redirect resource types use only shared known metadata:
an omitted type leaves equality unknown rather than proving a modification.
- Console observations with a stack source retain call type, argument types, the CDP `Runtime.Timestamp` in milliseconds since Unix epoch, and source location. An absent or invalid timestamp is `null` and makes `console_events` incomplete; an observed zero remains zero. Argument types are listed by CDP argument position. An absent or non-array argument inventory is `null`, and a malformed argument keeps its position as `unknown`; either makes `console_events` incomplete, while an observed empty inventory remains `[]`. A malformed stack or top stack frame is not replaced by its caller: the console event is excluded, and a request initiator location or WebMCP `registration_source` is `null`; each is reported as `invalid_protocol_value`. This epoch-millisecond clock is separate from the monotonic-second timestamps in `observe-web-session`. When console text capture is selected, already-delivered primitive values are retained verbatim with their original `argument_index`; objects, getters, and remote properties are never expanded. Text capture is `unavailable` when the argument inventory is unknown.
- Console observations with a stack source retain call type, argument types, the CDP `Runtime.Timestamp` in milliseconds since Unix epoch, and source location. An absent or invalid timestamp is `null` and makes `console_events` incomplete; an observed zero remains zero. This epoch-millisecond clock is separate from the monotonic-second timestamps in `observe-web-session`. When console text capture is selected, already-delivered primitive values are retained verbatim; objects, getters, and remote properties are never expanded.
- WebSocket observations retain direction, opcode, and payload byte length. A missing or malformed CDP opcode is returned as `null`, with opcode-dependent payload byte length and shape also `null`, and marks `websocket_frames` incomplete. Missing or wrong-typed payload data is not treated as empty; its byte length is `null`, and a text payload shape is unknown. Numeric opcode `0` and an observed empty payload remain distinct from missing data. Opcode is nullable in the public inspection schema, so clients must handle unknown protocol values. When shape capture is selected, text messages are classified as text or complete value-free JSON shape; binary bytes, hashes, prefixes, and raw payloads are never retained.
- Storage observations always redact values. Key names, IndexedDB names, and cache names require `include_storage_keys`; stable content fingerprints require the additional `include_storage_fingerprints` selection. Cookie fingerprints cover cookies applicable to the current main-frame URL, including its path, rather than every cookie path on that origin. Other storage remains scoped to the origin. Cache fingerprints hash all decoded response bytes, including bodies above 64 KiB, subject to the [64 MiB per-message CDP limit](#request-boundary). Missing or undecodable cache bodies and partial IndexedDB remote objects make fingerprint coverage incomplete, so identical observations remain `unknown`.
- Unavailable or malformed storage inventories mark `storage_keys` unavailable, even without fingerprints. Comparisons suppress inferred key additions and removals while preserving modifications proven by complete fingerprints on both sides. Previously exported captures are not retroactively corrected; recapture them to obtain these coverage markers.
@@ -493,11 +371,6 @@ Frames, resources, scripts, events, and workers outside the selected origin scop
Retained workers include validated opener-target and parent-frame IDs when CDP
provides them; these relationships do not expand the exact-origin boundary or
prove static module ownership.
Workers without either relationship are withheld with an `unattributed_target`
exclusion and `workers` in `unavailable_sections`: a same-origin URL does not
establish which page owns a worker. A supplied relationship to another target or
frame remains `out_of_target_scope`. Missing ownership also remains unknown when
the worker URL is empty; withheld target details are not returned.
## Completeness and limits
@@ -512,17 +385,8 @@ validation and resource bounds. Omitted script metadata makes its affected
section explicitly truncated.
Disallowed-origin entries are filtered before retention.
Script metadata and selected source reads use one fixed admitted-script snapshot.
New in-scope script IDs still retained when the final CDP read completes are
counted in `scripts.total` and `completeness.dropped_events.scripts`, with
`scripts` marked truncated; they do not extend the source-read pass. Repeated
late events for the same ID count once. The original snapshot is not replaced
or reduced by later updates to an existing ID. These omissions do not imply
that a source byte or time budget was exhausted.
Selected script-source collection shares a 32 MiB aggregate UTF-8 retention
budget and the `command_timeout_ms` deadline (default 20000 ms) across the
capture. The byte budget has no runtime override. An oversized source is
budget and a 20-second deadline across the capture. An oversized source is
omitted while later smaller sources may still fit. When the deadline expires,
remaining source reads are skipped. Script metadata, per-item omission reasons,
and `script_sources` completeness remain available. A single CDP response can
@@ -530,25 +394,19 @@ still be materialized up to the separate 64 MiB transport limit before source
admission; the retention budget does not bound that transient allocation.
Bundle parsing and semantic inspection run sequentially in an owned child,
without executing captured code. These are separate phases. `max_heap_mb`
defaults to 1024 MiB V8 old-space (range 128..16384). `analysis_timeout_ms`
defaults to 30000 per script, including startup and transfer.
`total_analysis_timeout_ms` defaults to 300000 across script analysis. A
per-script deadline above the effective total is rejected. A failed or
timed-out script becomes an explicit unknown while completed findings and later
scripts remain available. Total-deadline expiry returns a typed timeout with
partial Evidence and identifies unstarted scripts. `source_map_fetch_timeout_ms`
defaults to 30000 and is an item limitation, not that analysis timeout. These
controls are also available as CLI flags with hyphenated names. Worker protocol
and result byte budgets have no runtime override.
without executing captured code. `max_heap_mb` defaults to 1024 MiB V8
old-space; `analysis_timeout_ms` defaults to 30000 per script, including startup
and transfer. A failed or timed-out script becomes an explicit unknown while
completed findings and later scripts remain available. `total_analysis_timeout_ms`
defaults to 300000 across script analysis; expiry returns a typed timeout with
partial Evidence and identifies unstarted scripts. These controls are also
available as CLI flags with hyphenated names. Capture and optional source-map
fetching have separate budgets.
One web-bundle capture/analysis is admitted per REA process at a time. Completed
script results share an 8 MiB serialized transfer budget, with host heap
reservation checked before decoding and validating another result.
When the host reservation is what reaches zero, the failure names that heap
headroom and the remaining fixed budget. When the 8 MiB allowance itself is
exhausted, the failure names that fixed budget and says it has no runtime
override. Either stop ends further script launches and returns a typed
Exhausted result capacity stops further script launches and returns a typed
resource failure with completed facts and explicit unstarted-script unknowns.
Worker control frames are limited to 64 KiB. Cleanup must confirm child termination
before releasing its private files; failed cleanup retains its exact owner and
@@ -558,10 +416,8 @@ facts, including cancellation during final cleanup.
The worker heap setting is not a process-family RSS limit. Node enforces V8
old-space flags; Bun ignores them. Windows Bun instead uses an owned process
commit limit with a 512 MiB runtime reserve; other Bun hosts have no enforced
memory limit. An allocation failure before that Windows limit is reported as
reached still names the configured limit and says it was not reached. These
bounds do not establish an aggregate host-memory limit across other REA
operations or external processes.
memory limit. These bounds do not establish an aggregate host-memory limit
across other REA operations or external processes.
Resource-tree inventories distinguish a valid empty `resources` array from a
missing or malformed collection. Invalid resource entries are omitted with
@@ -574,31 +430,13 @@ An explicitly empty MIME type is valid. The optional content size may be absent;
an invalid supplied size remains unknown with incomplete resource coverage while
the resource's other facts are retained. Zero and nonnegative fractional sizes
are preserved.
`resource_key` remains the exact observation identity used by script
reconciliation. Capture comparison groups resources by URL, origin, type and
MIME type, then compares known sizes within a group. If a matched group contains
an unknown size, comparison reports unknown rather than treating the observation
as a resource addition or removal; complete inventory coverage still establishes
when an entire group is absent.
CDP discovery has a 10-second response deadline. HTTP discovery documents
use an 8 MiB budget. Browser-socket discovery (`Browser.getVersion`,
`Target.getTargets`) uses the 64 MiB protocol message budget. Neither budget
nor the discovery deadline has a runtime override. `list_browser_targets` accepts no
`command_timeout_ms`: HTTP discovery and browser-socket discovery
(`Browser.getVersion`, `Target.getTargets`) stay on that fixed deadline, and
the socket closes without a later command. Other CDP tools use
`command_timeout_ms` (default 20000) for each WebSocket handshake and command
response, separate from the caller-selected observation window, discovery, and
the 5000 ms cleanup grace. A browser-level socket's handshake and the following
`Target.attachToTarget` each receive that full deadline; they do not share one
budget. A stalled handshake or command reports
`provider_timeout` and names `command_timeout_ms` only when that operation
accepts it; caller cancellation still reports cancellation. WebSocket messages
remain subject to the [64 MiB limit](#request-boundary), which has no runtime
override. REA does
not impose a message-count or pending-command quota. On cancellation, it closes
only its own WebSocket,
CDP discovery has a 10-second deadline. Each WebSocket handshake and command
response has its own 20-second deadline, separate from the caller-selected
observation window. A stalled handshake or command reports `provider_timeout`;
caller cancellation still reports cancellation. WebSocket messages remain subject
to the [64 MiB limit](#request-boundary). REA does not impose a message-count or
pending-command quota. On cancellation, it closes only its own WebSocket,
waiting up to one second before terminating it. Malformed unsolicited events
and malformed replies to pending commands poison the connection so later
commands fail closed. A correlated reply's session ID must match the command's
@@ -607,63 +445,22 @@ rejects it as unknown; other command rejections retain their reported failure.
Network and console coverage starts only after REA attaches and enables the relevant CDP domains. `prior_activity_available` is always `false`; absence from these arrays is not evidence that an event never occurred. Scripts are different: scripts the page parsed before REA attached are still reported under `scripts`. A page that loaded before attach can therefore list its scripts while `network.requests` lacks the requests that fetched them. If CDP reuses a request ID without supplying redirect metadata, REA retains the predecessor observation and marks network coverage incomplete.
Source maps are not fetched by inspection. Bundle analysis fetches them only when `fetch_source_maps` is selected, with exact-origin redirect checks and no credentials, cookies, or referrer. Each fetch operation uses `source_map_fetch_timeout_ms` (default 30000) and a 64 MiB aggregate streamed-response budget across redirects. The byte budget has no runtime override. A fetch overrun is an item limitation, not a thrown analysis timeout. A deadline or byte-limit failure remains explicitly unavailable and makes bundle completeness partial. Source-map decoding also bounds raw resolved identities to 64 MiB and aggregate inline evidence to 32 MiB. Failed maps preserve their failure context when it fits; if another context cannot fit, collection stops with the full requested count, the returned processed count, and an explicit unknown-results limitation. Retained mappings and original sources are returned inline. See [source-location limits](web-source-location.md) for the structural decoder bounds. Original source artifacts with `.ts`, `.tsx`, `.mts`, or `.cts` suffixes carry `text/typescript`; other source names retain `text/javascript`.
Source maps are not fetched by inspection. Bundle analysis fetches them only when `fetch_source_maps` is selected, with exact-origin redirect checks and no credentials, cookies, or referrer. Each fetch operation has a 30-second deadline and a 64 MiB aggregate streamed-response budget across redirects. A deadline or byte-limit failure remains explicitly unavailable and makes bundle completeness partial. Source-map decoding also bounds raw resolved identities to 64 MiB and aggregate inline evidence to 32 MiB. Failed maps preserve their failure context when it fits; if another context cannot fit, collection stops with the full requested count, the returned processed count, and an explicit unknown-results limitation. Retained mappings and original sources are returned inline. See [source-location limits](web-source-location.md) for the structural decoder bounds. Original source artifacts with `.ts`, `.tsx`, `.mts`, or `.cts` suffixes carry `text/typescript`; other source names retain `text/javascript`.
A fully read response that fails UTF-8, JSON, mapping, or structural validation
retains `artifact: null`, empty original-source, module-edge, and mapping
arrays, and an explicit failure limitation. Invalid content is `invalid`;
decoder or structural capacity refusal is `fetch_failed`. Its optional
`response_identity` records the complete body's SHA-256 digest, byte count,
and `fetch-response-body-bytes` representation without retaining malformed
text or charging that text to the 32 MiB output budget. These are bytes
delivered by Fetch after HTTP content decoding and before UTF-8 decoding, not
HTTP wire bytes. The identity is absent when the body was not fully read, and
older observations may omit it. A response whose text cannot fit the existing
output budget is likewise `fetch_failed` with text omitted and completed-body
identity; JSON parsing and source-map decoding are not attempted. Successful and partial maps keep their
full text artifacts. Their original text, including an initial BOM or XSSI
line, remains distinct from the parsing view that removes that framing.
Collection reads the body once and does not raise its response or output limits.
When fetching is selected, source-map `excluded` facts preserve the capture
ledger's reasons and counts for declarations rejected before download. No
disallowed URL details are retained or fetched. `requested` and `processed`
count the admitted fetch requests; exclusions are counted separately. Excluded
declarations make the collection unavailable, or partial when maps were retained.
An empty collection with no declarations remains `not_requested`.
If the caller cancels while later source maps are being fetched, the failure's
partial Evidence retains the captured page and every completed map item. Its
`normalized_result.kind` is `web-bundle-acquisition` and
`analysis_status` is `not_performed`: no script-analysis result is claimed.
`source_maps.requested` remains the admitted request count,
`source_maps.processed` counts completed items, and any unprocessed requests
have an explicit unknown-results limitation. Capture exclusions remain in the
same collection. The cancellation error keeps the caller's exact reason;
an already observed response resource or cleanup failure keeps its own error
category and cleanup owner.
Source-map items retain the script's `declared_url` separately from the observed
`effective_url`. Successful maps use the latter as their source-resolution base.
Failed fetches retain the last response URL observed within the approved origins;
`effective_url` is `null` when no response was observed. Query parameters are
source-map items retain the script's `declared_url` separately from the observed
`effective_url`. successful maps use the latter as their source-resolution base.
failed fetches retain the last response URL observed within the approved origins;
`effective_url` is `null` when no response was observed. query parameters are
preserved, and both URLs count toward the inline evidence budget.
Current fetches emit a string or null; historical observations may omit
`effective_url` without changing their stored content or Evidence identity.
An observed URL must fit before its response body is read. If it cannot fit,
remaining map results are explicitly unknown; failed cancellation retains its
cleanup context and resource constraint.
Indexed maps whose decoded mappings cross the next section's starting position
are invalid under [ECMA-426](https://tc39.es/ecma426/2024/), rather than complete
maps with clipped mapping rows. Fetch collection and source tracing share this
validation.
## Non-goals and threat model
The browser provider deliberately does not expose generic CDP commands, `Runtime.evaluate`, WebMCP invocation, navigation, input, downloads, page closure, or browser closure. Screenshot capture is the sole pixel surface and returns an image only when requested. REA disables the domains it enabled, detaches the target session, and closes only its own WebSocket.
Discovery and attachment errors remain primary if closing REA's socket also
fails. The cleanup failure is reported alongside the original command failure;
it cannot replace the operation diagnostic.
This feature is not a browser sandbox or network containment mechanism. The attached page and browser continue running with their existing privileges and may make external requests independently of REA. CDP gives deep access to the selected browser profile, so use a dedicated profile, include only origins you intend to inspect, and treat other same-user processes as outside this boundary.
+14 -214
View File
@@ -10,46 +10,22 @@ liveness deadlines. `INPUT_JSON` may be inline JSON or a JSON file path.
The minimal request contains only browser launch/connect selection, `start_url`,
and at least one `actions` entry. Launch `headless` defaults to `true` and can
be set to `false` where the host supports a visible browser. Navigations follow
the destinations named by the scenario and the page's ordinary browser requests,
and [request steps](#request-steps) send only to the start URL's origin;
there is no separate origin allowlist. Request steps use a temporary dispatch
gate for cancellation; ordinary page requests retain their browser behavior. Environment
the destinations named by the scenario and the page's ordinary browser requests;
there is no separate origin allowlist or request-interception layer. Environment
settings (including service workers blocked by default), empty storage, and a
final sanitized URL capture are supplied by default. Set
`environment.service_workers` to `allow` when the application requires them.
The connected service-worker policy owner retains its exact CDP transport
through discovery, attachment and restoration. Failed admission preserves its
primary diagnostic when socket cleanup also fails; later policy cleanup retries
that retained transport and clears cleanup uncertainty only after close succeeds.
For attached browsers, `block` rejects captures when the selected page or its
frames are controlled by a service worker or already have a registration,
including after navigation. Policy checks bracket each artifact capture, including
the final step. If an external worker takes control while artifacts are being
captured, REA rejects the capture and retains the completed artifacts in
`details.diagnostics.collected_artifacts`; they do not establish policy-compliant
completion. Existing caller registrations are never removed.
including after navigation. Existing caller registrations are never removed.
Navigation can run destination code before a newly discovered worker is rejected;
that rejection does not roll back browser effects. REA removes its owned
future-document scripts, restores live frame methods, then disconnects.
Add secret declarations, storage seeds, and additional artifact or event
capture only when needed. Missing environment-backed secrets fail as input errors before browser acquisition, naming the declaration and required variable. An explicitly set empty string is a resolved value. Action, secret, and storage counts are not capped.
Duration, action, and navigation timeouts remain separate from CDP command
deadlines. `command_timeout_ms` (default 20000; omit it to keep that default
and leave it out of Evidence) bounds launch startup, the connect-over-CDP
handshake, target lookup, environment commands, and storage CDP commands,
including replies from attached child targets. It also bounds the initial
`start_url` navigation through the load event; a redirect or replacement
navigation cannot leave startup waiting indefinitely. Set it in the scenario JSON.
`capture-browser-scenario` has no `--command-timeout-ms` option.
It does not change action `timeout_ms`, `wait_for_timeout.duration_ms`, or
action navigation. Service-worker restoration keeps a fixed 5000 ms grace and does
not inherit a raised `command_timeout_ms`. CDP discovery stays on its own
fixed deadline. When service workers are blocked, the registration check
before discovery has its own fixed 10000 ms deadline; `command_timeout_ms`
does not apply because discovery has not started. The discovery deadline
covers endpoint discovery only. The following WebSocket handshake and
`Target.attachToTarget` each use `command_timeout_ms`. `capture.limits` controls retained event and network-content
budgets; it does not change those liveness deadlines.
capture only when needed. Action, secret, and storage counts are not capped.
Duration, action, and navigation timeouts remain fixed provider-owned liveness
limits. `capture.limits` controls retained event and network-content budgets;
it does not change those liveness deadlines.
## Minimal launch example
@@ -206,8 +182,7 @@ language environments.
HTTP(S) URLs may include ordinary query values and fragments directly. Use
ordered structured query entries when a value needs to reference a declared
environment-backed secret. Form, storage, and cookie values are either literal
strings or declared secret references; request step headers can also read a
cookie or local storage value from the page. Raw URL userinfo credentials are rejected.
strings or declared secret references. Raw URL userinfo credentials are rejected.
Captured credential-header
values are not retained, and declared secret values are redacted automatically.
A secret may be declared solely to
@@ -224,9 +199,8 @@ validation.
Cookies are installed before the initial navigation. Local storage entries are
seeded once per browser storage partition; session storage entries are seeded
once per origin in each instrumented top-level browsing context. Seeding occurs
before the first document scripts for the selected page and its instrumented
frames. An origin first reached by a later action
once per origin in each top-level browsing context. Seeding occurs before the
first document scripts for that area. An origin first reached by a later action
receives its seed on that first visit. Application updates, deletions, and
empty values then persist across later navigations in the same area; a new
scenario starts with its own seeds. Partitioned frames can receive separate
@@ -234,24 +208,11 @@ local storage seeds even when they share an origin. The pre-script mechanism
uses CDP debugger instrumentation. The observer resumes only pauses whose top
frame belongs to its private seed script; page breakpoints and `debugger`
statements remain under the attached debugger client's control.
If Chromium leaves the private isolated world's origin empty, REA looks up
that exact frame's reported security origin before selecting a seed. It does
not infer the origin from a frame URL or serialized storage key. Unresolved
frame identity or security origin fails initialization rather than silently
running with an unknown initial state.
Popup discovery does not establish that its first script was paused by the
selected page's storage instrumentation. When non-cookie seeds are declared
and a popup is discovered, the capture retains completed observations and
marks `storage` coverage missing, with equality ineligible. This applies even
if popup lifecycle events were not selected. Popup first-script storage
initialization is not currently guaranteed.
The result is Evidence with an initial state followed by one record per
declared action. Each step reports action status, elapsed time, sanitized URLs,
event bounds, and independently typed capture states for screenshot, DOM,
accessibility, URL, history, and storage. Actions after a failed or cancelled
action are not run and are recorded as `cancelled` with a not-executed error.
Console, page-error, network,
accessibility, URL, history, and storage. Console, page-error, network,
WebSocket, frame, worker, popup, and cancelled-download events are attributed to
steps. Retained events and requested artifacts are returned inline; omitted
events are counted by kind and identify affected network transactions.
@@ -318,113 +279,6 @@ external browser without closing it. The request itself defines the operation;
there is no additional REA grant step. The browser and host still enforce their
own access and process rules.
## Request steps
A `request` step sends one HTTP request from the page with the page's own
`fetch`, so the browser attaches the session's cookies and the request is
recorded in the step's network events like any request the application makes.
Use it to record an application's API, including writes, when driving the UI
for each call would be slower or less exact than calling the endpoint the UI
calls. It is the only action that changes application state without a user
gesture, so run scenarios with write requests only against an instance whose
data you may change. This scenario creates one item:
```json
{
"browser": {
"mode": "launch",
"executable_path": "/absolute/path/to/chromium"
},
"start_url": { "url": "http://127.0.0.1:3000" },
"actions": [
{
"step_id": "create-item",
"action": "request",
"method": "POST",
"path": "/api/items",
"query": [
{ "name": "notify", "value": { "source": "literal", "value": "false" } }
],
"headers": [
{
"name": "X-CSRFToken",
"value": { "source": "cookie", "name": "csrftoken" }
},
{
"name": "Authorization",
"value": {
"source": "local_storage",
"key": "auth",
"json_pointer": "/token"
},
"prefix": "Bearer "
}
],
"json": { "title": "Scratch item 1" },
"expected_status": [201],
"timeout_ms": 10000
}
]
}
```
- `method` is `GET`, `HEAD`, `POST`, `PUT`, `PATCH` or `DELETE`. `json` is the
body, sent with `Content-Type: application/json` unless a header sets one; a
`GET` or `HEAD` step cannot carry it.
- `path` starts with one `/` and must resolve to the start URL's origin, which
validation checks before the browser starts. When the step runs, the page must
be on that origin, checked again in the page as the request is sent; add a
`goto` step first if an earlier action left it. A query in `path` is sent as
written (a fragment is not sent), and `query` entries are appended after it
in order and may reference declared secrets.
- Request steps follow no redirects: a redirect answer fails the step, and
nothing is sent to the location it names, though a write the redirect
answered may already have been applied. `expected_status` cannot name a 3xx
status. Give the path the redirect names; with header values selected, the
capture records its `Location`.
- Header values are literals, declared secrets, a cookie the browser holds for
the request URL (`{"source": "cookie", "name"}`, HttpOnly cookies included,
whose value the page's scripts can then see while the request runs), or a
local storage entry on the start origin (`{"source": "local_storage",
"key"}`, with an optional `json_pointer` to a string or number in a stored
JSON value). `prefix` is sent before the value. A value read from the page is
redacted like a declared secret, as `[REDACTED:page:<step_id>.<header>]`,
from records made after the step reads it; a response recorded earlier, such
as the login response that returned a token, keeps the value. A value read
again keeps its first marker, and a value shorter than 8 characters is not
redacted, since redacting it would rewrite ids, ports and hosts throughout
the capture. A value is redacted as stored and as `fetch` sends it, without
surrounding spaces and tabs. A missing cookie, key or pointer fails the step.
Reading a page value raises the estimated cost of redacting every later
body, so the bodies kept within `max_network_body_allocation_bytes` (1 MiB
by default) get smaller: after a 32-character `X-CSRFToken` value, a body over
about 47 KiB is kept as `unavailable`. Raise `capture.limits` when the
application's responses are larger.
- Validation refuses a header name given twice in a step, header names a
page's `fetch` cannot set, such as `Cookie`, `Origin`, `Host` or `Sec-*`, and
`User-Agent`, which Chrome drops; it refuses line breaks and other ASCII
control characters in literal values and prefixes, and the step fails before sending
when a secret or page value holds them.
- The step completes when the whole response has been read. With
`expected_status`, any other status fails the step and the later actions are
recorded as `cancelled`; without it, any response completes the step. A
network error fails the step. `timeout_ms` covers reading the header values
and the request: a step whose time runs out, or that is cancelled, before
its request is sent sends nothing, but a request already sent can still reach
the application after the step fails.
An absolute deadline also covers browser evaluation queued behind page work.
A temporary route checks cancellation again at network dispatch and is removed
when the request evaluation settles. A page controlled by a service worker
is refused because its fetch can bypass that dispatch gate; use the default
blocked service-worker policy for request steps.
The request runs in the page's main world, so an application that wraps
`fetch` sees it, and it goes through the same network observation as other
page requests: select network content (bodies or header values) to record it.
Captures with request steps state in `limitations` that the steps sent their
declared requests through the page's fetch. A scenario without request steps
is unchanged.
## Network evidence
`capture.events: ["network"]` retains request, response, completion, failure,
@@ -465,17 +319,6 @@ and unfinished-request metadata. Select content independently:
All three options default to `false`. Any selected network content also enables
network metadata observation; listing `network` in `events` is then optional.
Each observed request object receives a capture-scoped `transaction_id`.
Each `request` event carries `observed_at`, the wall-clock time REA observed the
request start. Playwright reports the browser's own start only once a request
ends, so each `request-finished` or `request-failed` event carries `started_at`,
the browser's wall-clock start, and `elapsed_ms`, its time from that start to
the end of the response, when the browser reports them. The environment views
time scenario records from these, as HAR entries are timed: the browser's start
where it reported one, else `observed_at`, which trails it by up to tens of
milliseconds while REA reads other responses, and a duration of 0 where the
browser reported none. Captures made before these fields existed lack them and
are read in record order. REA versions before them refuse captures that carry
them. Scenario comparisons ignore all three (`ignore_network_request_timing`).
Requests sharing a URL remain distinct. Redirects link to the preceding
transaction only when it was observed; a null predecessor does not establish
that no earlier hop existed. Request events require both transaction fields;
@@ -509,16 +352,8 @@ Proxy-Authorization, Cookie, and Set-Cookie values are null with
and their standard URI/form or JSON-escaped representations are replaced without
decoding unrelated binary bytes. Other encodings are not interpreted. Digests describe the
retained representation after redaction.
The selected body-byte limit also bounds that retained representation after
secret expansion, before base64 serialization. Credential header values are
discarded before retained-header allocation accounting; their names and null
projections still count. The transport/API has already materialized incoming
bodies and headers, so these retained limits do not bound producer-side input
allocation.
Only completed responses are read. The producer-observed terminal state is
recorded independently of whether its event metadata fit the retention budget. An omitted completion event cannot turn a
finished transaction into an unfinished one. When the final step ends, observation
Only completed responses are read. At the end of the final step, observation
stops; requests still in flight emit `request-unfinished`, and their observed
responses have explicitly unavailable bodies. Already-admitted content reads
drain before browser cleanup. At most 16 reads run at once; queued work is
@@ -538,24 +373,6 @@ observed) and excludes the sequence reference from normalization.
Playwright scenario capture does not supply initiator stacks; passive CDP
observation retains its own initiator evidence.
## Storage value fingerprints
New storage snapshots include `fingerprint_version: 2`. Non-secret, well-formed
JavaScript strings retain their SHA-256 over UTF-8. Strings with unpaired UTF-16
surrogates use SHA-256 over a raw `0xff` byte followed by their exact UTF-16LE
code units; this prefix cannot occur in UTF-8. Declared secrets remain
`redacted-secret` with a null digest, without a second or legacy digest.
A missing version identifies legacy UTF-8 fingerprints, which replaced unpaired
surrogates and cannot establish value identity. Storage containing any legacy
hashed value is conservatively compared as `unknown`, including old-to-old
comparisons; recapture both sides to compare reliably. This also conservatively
withholds some otherwise provable legacy differences. Empty or entirely
secret-redacted storage keeps its existing comparison semantics. The version is
checked before normalization and excluded from content comparison: changing
encoding metadata alone does not establish a website change. Existing captures
and their completeness records are not rewritten.
## Scenario comparison
`compare_web_captures` and `compare-web-captures` also accept two complete
@@ -573,8 +390,8 @@ and compared as an unordered collection after caller rules apply, because
concurrent responses arrive in any order and can land in a later step; a
redirect retains the complete predecessor ancestry, including each hop's own
request, response outcome, and selected network content. Each transaction's
capture-local IDs, sequence/index fields, content source-sequence anchor and
request times (`observed_at`, `started_at`, `elapsed_ms`) are removed. Caller rules normalize retained strings before hashing. The
capture-local IDs, sequence/index fields, and content source-sequence anchor
are removed. Caller rules normalize retained strings before hashing. The
`preserve_redirect_transaction_ancestry` built-in rule fingerprints each hop
as canonical SHA-256 of `{ events, redirected_from }`: `events` is the sorted
list of its normalized event SHA-256 digests, and `redirected_from` is its
@@ -588,18 +405,6 @@ sorted canonically before application, making the same policy reproducible
regardless of input order. Rules operate only on already-redacted durable
capture fields and are preserved in the result.
Cookies, local storage, and session storage are compared as unordered inventories
after caller literal rules apply. Enumeration order alone does not establish a
storage change. Canonical ordering uses the complete entry, preserving cookie
scope/flags, value fingerprints, and duplicate entries even when redaction or
literal rules make names collide. Captured storage arrays retain their original
order. The `compare_storage_entries_as_unordered_collection` built-in rule
participates in the normalization commitment; new storage comparison digests
therefore differ from earlier order-sensitive comparisons. The
`ignore_network_request_timing` built-in rule likewise joins the commitment, so
normalization digests differ from comparisons made before it, though captures
with and without request times compare alike.
Each aligned step reports changed, unchanged, or unknown. Changed and unknown
action, screenshot, DOM, accessibility, URL, history, storage, and event
artifacts carry their normalized before/after digests and capture states.
@@ -612,11 +417,6 @@ selection. Captures with different selections report event evidence as unknown
with a recovery instruction, even when no selected family emitted an event.
Captures written before this field existed omit it, so their event evidence is
also unknown.
Retained-capture readers admit that omission without deriving a selection from
observed events; other artifact families remain comparable. Current capture
producers still require and publish the selected event families. Manifest,
environment and script-export readers share the retained-capture admission
rules and preserve the same missing fact.
Network normalization also commits the removal of capture-local transaction IDs,
attribution to the requesting step, and unordered transaction comparison. These
-90
View File
@@ -1,90 +0,0 @@
# Static analysis in GitHub Actions
The [minimal workflow](https://github.com/morluto/rea/blob/main/examples/github-actions/rea-analysis.yml) builds a small
JavaScript application, analyzes its files without executing them, and uploads
machine-readable results. It uses the existing CLI, not a separate GitHub Action
or an MCP client. It does not promise support for every REA provider.
## Run the example
1. Copy the example to `.github/workflows/rea-analysis.yml` in your repository and
commit it to the default branch. No existing application or package manifest is
needed for the self-contained demonstration.
2. Enable GitHub Actions if necessary. Select **REA static analysis example** in
the Actions tab and choose **Run workflow**.
3. Download the `rea-analysis` artifact after the run. Its files are:
| File | Meaning |
| ----------------- | -------------------------------------------------------------------------------------------------- |
| `analysis.json` | Original CLI JSON, including complete Evidence on success or the error report when one is emitted. |
| `analysis.stderr` | Original diagnostics/progress; an empty file is normal when there are none. |
| `exit-code.txt` | Original analysis process exit code. |
The example pins Node.js 24.22.1, REA 6.7.0, and action commit revisions. It installs
REA into the runner's temporary directory rather than changing the application's
dependencies. This pins the tool version, not every transitive npm dependency;
use a reviewed, committed package lock and `npm ci` if your integration requires
that stronger dependency-reproducibility guarantee.
## Prerequisites and trust
The runner is GitHub-hosted Ubuntu 24.04 with Bash, npm, and `ps` from `procps`.
Other Linux images must supply `procps`; REA's supervised process controls use it.
This static JavaScript operation needs no Hopper, Ghidra, IDA, Java, browser,
Docker, GPU, external API key, or user-supplied GitHub secret. The workflow only
requests `contents: read`; artifact upload uses the runner's normal Actions
credentials.
Replace **Build a known artifact** with your reviewed build, and set
`ARTIFACT_PATH` to its application directory (relative to the checkout) or an ASAR.
The demonstration creates its own tiny application; it deliberately throws if
executed. REA reads it statically. Your replacement build itself can execute code:
review it separately and do not add untrusted pull-request builds to a privileged
`pull_request_target` workflow. The example is manually triggered, not an
untrusted-PR execution policy.
Reports can contain paths, URLs, source-derived facts, and caller-selected data.
Only analyze artifacts and publish reports you are authorized to share. Do not
rely on REA inferring secrets from their names. Review artifact access and
retention for your repository; the example retains reports for seven days.
## Failure, partial results, and unsupported capabilities
The analysis step captures stdout and stderr separately, records `$?`, and exits
with that same status. There is no `continue-on-error`, `|| true`, output filter,
or pipeline that could turn a failed analysis green. Before checkout or other
fallible steps, the workflow creates a fresh report directory under `RUNNER_TEMP`.
Analysis and upload both use that directory, never a checked-in `reports/rea` or
other workspace files. The upload step uses `always()` when that directory was
prepared, so available diagnostics survive an analysis failure. If installation
or the build failed before analysis began, it is empty and upload warns rather
than publishing stale results or inventing a successful result. Runner loss or
cancellation can still prevent upload.
`--json --full-output` selects a machine-readable envelope; it does **not** mean
that analysis was complete. A successful operation can retain partial analysis,
unknowns, and limitations. Exit zero is not a security verdict, proof of complete
coverage, or a policy gate for those findings. Inspect the saved Evidence and
coverage before making such a decision. See [output and exit status](cli.md#output-and-exit-status).
A nonzero exit can represent invalid input, unavailable tooling, an unsupported
host/capability, or another operation failure. Keep the original JSON error (when
emitted), stderr, and exit code, and use their category/message to diagnose it.
An early startup failure may leave `analysis.json` empty; do not parse that as
successful analysis. Select a supported operation or explicitly provision its
[documented prerequisites](installation.md); do not silently switch providers,
execute the artifact, or ignore the failure to obtain a green job.
To exercise the failure path, temporarily set `ARTIFACT_PATH` to
`build/missing-artifact` without changing the demo build. Analysis must fail,
`exit-code.txt` must be nonzero, and the available reports must still upload.
To exercise unsupported input, set `ARTIFACT_PATH` to `build/rea-ci-demo/index.js`:
this operation accepts an application directory or ASAR, not a single source file.
The retained JSON error has category `unsupported_target`; the step exits nonzero.
Restore the correct path before using the example normally.
To exercise partial analysis, append `export const broken = ;` to the demo's
JavaScript. REA 6.7.0 retains `data.normalized_result.statistics.parse_failures`
and an incomplete-coverage limitation while the operation can still exit zero.
Inspect those facts instead of treating process success as full coverage. The
report JSON and diagnostics remain unfiltered in all three cases.
+40 -581
View File
@@ -3,8 +3,6 @@
Use the CLI for a direct inspection or a script. It uses the same application
workflows and evidence contracts as REA's MCP server. Each CLI invocation is a
separate process; save results when you want to use them in a later command.
For a pinned, noninteractive workflow with retained reports, see
[static analysis in GitHub Actions](ci-analysis.md).
## Run a command
@@ -39,11 +37,6 @@ pass an absolute path. REA reads and authenticates the file, then projects it
like retained Evidence, so a large file is bounded by the file reader rather
than charged as inline input.
Local file inspection commands reject empty paths and NUL bytes before resolving
relative inputs, auxiliary files or output destinations. An empty string never
selects the working directory; use an explicit `.` when selecting that directory
is supported by the command.
For example, a 334 MB Obsidian application Evidence file yields a summary view
of about 10 KB. Replace the view with
`{"kind": "page", "collection": "modules", "offset": 0, "limit": 32}` for a
@@ -51,122 +44,6 @@ module page. See
[JavaScript application workflows](javascript-application-workflows.md) for
views, feature traces and comparisons that take the same saved Evidence.
## JSON Lines record output
Use `--format jsonl` to consume row-oriented results one record per line:
```bash
cat > observations-view.json <<'JSON'
{"source":{"kind":"evidence-file","path":"app-evidence.json"},"view":{"kind":"javascript-facts","property_keys":[],"offset":0,"limit":32}}
JSON
rea inspect-analysis-view observations-view.json --format jsonl > observations.jsonl
```
This request selects up to 32 observation identities and their provenance,
without observation property values.
This changes JSONL object-collection framing. Scripts that need one complete
report or a scalar field should use `--format json`. For a full application
graph, first project a supported analysis view rather than selecting an
arbitrary nested graph array.
A top-level array emits its elements in source order. An object must have one
clear `rows`, `items`, `records`, or `occurrences` array, directly or under
`page`. Evidence uses the same boundaries inside `normalized_result`.
An arbitrary report or a scalar selection is refused with an actionable error;
use `--format json` for a complete document. If several collections exist,
select one with `--filter-output`, for example `normalized_result.rows`.
Object collections finish with a control record:
```json
{
"$rea": "jsonl/v1",
"type": "metadata",
"records": {
"path": ["normalized_result", "rows"],
"emitted": 2,
"source": 10,
"filter": "normalized_result.rows[0,2]"
},
"context": {
"evidence_id": "ev_…",
"normalized_result": {
"coverage": {
"status": "page",
"examined": 10,
"total": 100,
"next_offset": 10,
"exhausted": false
}
}
}
}
```
This abbreviated example illustrates framing. The actual `context` preserves
all non-row fields, including Evidence subject, provider, authority, confidence,
locations, links, limitations, parent/view digests, pagination and coverage.
With `--full-output`, it also retains the command envelope and its metadata.
A data filter selects row fields/slices while this context remains available;
excluded peer record collections and unselected Evidence `raw_result` are not
restored. An explicitly selected `raw_result` remains selected. Retain the
original Evidence for complete observations.
`records.emitted` and `records.source` count the selected output and its source
array; they do not assert analysis completeness. Source coverage and unknowns
retain their original meaning. Existing row identities and indices remain
unchanged unless your field selection excludes them. Empty object collections
still emit metadata; an empty top-level array emits no lines. Bare top-level
arrays have no synthetic coverage record.
Command and output-validation failures emit `{"$rea":"jsonl/v1","type":"error","ok":false,"result":…}`
and retain the original diagnostic payload. SDK validation/sentinel errors use
the SDK's exit status rather than guessing from data fields. `$rea` is reserved
at the top level of a data record, so control records are unambiguous; collisions
are refused before rows are written. Nested `$rea` values are ordinary data.
A missing final metadata record or nonzero exit can indicate an interrupted
export; do not treat already emitted rows as a complete result.
`--token-count` estimates this exact representation, including metadata, one
line at a time. Errors remain error records even with token counting enabled.
Token windows cannot preserve JSON Lines and are rejected. JSON encoding and
stdout writes are incremental, including large escaped strings; this is output
streaming over a completed result, not incremental analysis. The analysis still
needs its normal resources. A single record must fit the runtime string limit
for token estimation; remove `--token-count` to stream a larger record.
Standalone version and MCP doctor diagnostics remain single JSON documents
in JSON Lines mode. Incur's streaming generator events and successful sentinel
responses retain their SDK protocol. MCP application results and Evidence
contracts are unchanged.
## Shell completion
The CLI includes optional shell completion. Run `rea completions --help` to
see the available shells and their setup commands. For Bash, load the hook
into the current shell:
```bash
eval "$(rea completions bash)"
```
The Bash hook supports macOS system Bash 3.2. It uses optional completion
features when the shell provides them and falls back to portable registration.
Completion includes standard global options and the active command's options;
`rea mcp ` offers only `add` and `doctor`. Output-option values remain data when
resolving the command scope.
For future interactive non-login Bash sessions, add the command to
`~/.bashrc`. Interactive login Bash sessions read the first available file in
`~/.bash_profile`, `~/.bash_login`, and `~/.profile`; add the command to the
file your shell reads, or have that file source `~/.bashrc`. Remove the command
from the startup file to stop loading completion, and run `complete -r rea` to
remove the registration from the current shell. These are manual, opt-in
steps.
Completion is a discovery aid; use `rea COMMAND --help` to confirm the
available options and subcommands.
## Managed assemblies (.NET)
For a .NET PE/CLI assembly, inspect its identity and deployment form before
@@ -186,16 +63,6 @@ or repeat it to select several types.
Selection limits decoding to the selected types; member references remain an
assembly-wide inventory, so the output can still be large.
Select `--detail summary --evidence-bundle /absolute/path/members.json` to
receive identity, selection, coverage, limitations and collection counts while
saving the complete original Evidence in a new bundle. Summary requires that
explicit destination; existing files are preserved. Later
`inspect-analysis-view` calls can page the exact parent from the saved bundle
without reopening the assembly. See
[CLI summary and saved member rows](managed-code-analysis.md#cli-summary-and-saved-member-rows)
for the complete command sequence. Omitted or `--detail complete` retains the
full inline response.
Choose the workflow from the artifact's contents rather than its `.exe` or
`.dll` extension. The managed parser requires PE/CLI metadata; it does not
unpack .NET single-file hosts or decode IL2CPP. For the image, type and method
@@ -236,16 +103,6 @@ Formats 2, 3 and 4 retain header, directory and integrity facts. Compact deliver
keeps complete Evidence in the requested bundle. See [Godot PCK](godot-pck.md)
for embedded packs, batch queries and unknown encrypted coverage.
Inspect a Node.js SEA carrier without running it:
```sh
rea inspect-artifact ./application --json
rea extract-artifact ./application --json
```
See [Node.js SEA](node-sea.md) for supported layouts, script and asset paths,
fuse observations, and the real-fixture verification lane.
Inspect local model declarations without loading weights:
```bash
@@ -269,8 +126,8 @@ The command decodes a version 24, 27, 29, 31 or 39 `global-metadata.dat` and rep
images, type, method and member names, string literals, index ranges, header
sections and SHA-256. With `--binary PATH` it also reads the matching
`GameAssembly.dylib`, `GameAssembly.dll` or `libil2cpp.so` (never runs it) and
links each method to its code address; `--generic-instances` adds generic
method instances and `--ghidra-seeds PATH` then writes Ghidra function seeds. It does not decrypt metadata. MCP defaults to compact counts
links each method to its code address; `--ghidra-seeds PATH` then writes Ghidra
function seeds. It does not decrypt metadata. MCP defaults to compact counts
with retained Evidence; query indexed rows with filters, batch indices and
selected fields. The CLI defaults to complete Evidence;
`--detail summary --evidence_output FILE` exports the full result to a new file
@@ -288,87 +145,6 @@ dependency edges, and the section's file range. A missing `.dep-v0` section is
null metadata, not proof that the binary has no dependencies. See
[Rust binaries](rust-binaries.md).
Inspect a PE Authenticode certificate table without executing the image or
verifying signatures:
```bash
rea inspect-pe-certificates /absolute/path/to/application.exe --json
```
The workflow reports `WIN_CERTIFICATE` entries and, for PKCS#7 signedData,
embedded X.509 serial, issuer, subject, and validity fields. A missing security
directory is observed absence of that table. See
[PE Authenticode certificate tables](pe-certificates.md).
Inspect PE import and delay-load tables without executing the image:
```bash
rea inspect-pe-imports /absolute/path/to/application.exe --json
```
The workflow reports DLL names, named and ordinal functions, delay-load
descriptors, and a pefile-compatible import hash of the standard import table.
A missing import directory is observed absence of that table. See
[PE import and delay-load tables](pe-imports.md).
Inspect ELF PT_NOTE and SHT_NOTE tables without executing the image:
```bash
rea inspect-elf-notes /absolute/path/to/program --json
```
The workflow reports note owners, types, descriptor hashes, and GNU build-id
or ABI-tag fields when those descriptors match. A missing note table is
observed absence of those declarations. See [ELF note tables](elf-notes.md).
Inspect an Apple `.ips` crash report without executing a target:
```bash
rea inspect-apple-ips /absolute/path/to/report.ips --json
```
The workflow reports first-line metadata, process/exception/termination facts,
threads, usedImages, lossless integer registers, and documented crash classes
when `bug_type` is `"309"`. Other DiagnosticReports types return no crash
result. See [Apple IPS crash reports](apple-ips.md).
Inspect Linux kernel module metadata without executing the module or requiring
`modinfo`:
```bash
rea inspect-kernel-module /absolute/path/to/module.ko --json
```
The workflow reports ordered `.modinfo` keys (duplicate `alias` and `parm`
entries preserved), `__versions` symbol CRCs, presence of
`.gnu.linkonce.this_module` and `__ksymtab` sections, observed `.init.text`
and `.exit.text` sizes, and an appended signature trailer. `struct module` is
not decoded and the signature is not validated. Other ELF types are not
applicable. gzip, xz, and zstd containers are unsupported and name
`external_decompression` as the next probe: decompress with an external
utility, then inspect the decompressed file. See
[Linux kernel modules](kernel-modules.md).
Inspect a CPython 3.7–3.13 `.pyc` header and nested marshal code objects
without executing bytecode or installing Python:
```bash
rea inspect-python-pyc /absolute/path/to/module.pyc --json
```
The workflow reports the pinned CPython version for the magic, PEP 552 hash or
mtime/size fields, nested function names, and bytecode SHA-256. Bytecode is
not decompiled. See [CPython pyc inspection](python-pyc.md).
Inspect an Erlang BEAM file without executing bytecode:
```bash
rea inspect-erlang-beam /absolute/path/to/app.beam --json
```
The workflow reports IFF chunk names, atom tables, exports, imports, lambdas,
and the Code chunk header. Missing optional tables stay null. See
[Erlang BEAM files](erlang-beam.md).
Configure [Hopper or Ghidra](installation.md#hopper), the
[IDA adapter](ida-provider.md), or [Kuna](kuna-provider.md) before analyzing a
native target. Substitute
@@ -387,21 +163,11 @@ rea trace /absolute/path/to/program "search" --provider ghidra --json
a function dossier; `decompile` returns pseudocode. For assembly instructions
alone, use `rea instructions`. A macOS `.app` bundle can be supplied directly.
A complete dossier can reach several megabytes of value-flow and references.
`rea function ... --detail summary --evidence_output /absolute/path/dossier.json` returns the
compact summary and saves the complete dossier as an Evidence bundle, never
overwriting an existing file. Page that bundle without reanalysis by naming it
as an `inspect-analysis-view` source, selecting the summary's
`normalized_result.parent_evidence_id` when the bundle holds several records:
`{"source": {"kind": "evidence-file", "path": "/absolute/path/dossier.json", "evidence_id": "ev_…"}, "view": {"kind": "native", "facet": "pseudocode", "offset": 0, "limit": 4096}}`.
MCP `analyze_function` defaults to the same summary and retains the complete
dossier in the session for `inspect_analysis_view`.
Run `rea --help` or a command's `--help` for arguments and output options.
The [generated catalog](mcp-contracts.md#generated-catalog) lists all CLI commands and MCP
tools. Provider-specific target support is described in the
[native guide](native-investigation.md), [DOS guide](ghidra-dos.md),
[raw binary guide](raw-binary.md), [GameCube DOL guide](ghidra-dol.md), and
[raw binary guide](raw-binary.md), and
[Windows Ghidra guide](windows-ghidra-p0.md).
## Choose a provider
@@ -438,7 +204,7 @@ The selected provider remains bound to the session until an explicit switch
or close. Provider failures are returned with their original reason. For an
`ambiguous` selection error, choose from `details.candidate_ids`; for
`provider_unavailable`, follow `message`, `remediation.action`, and the original
reasons in `details.rejections`. For Hopper, Ghidra, IDA, JADX, and Kuna, run
reasons in `details.rejections`. For Hopper, Ghidra, IDA, and JADX, run
`rea doctor --provider ID --json` to diagnose the selected engine. Other adapters,
such as `pwntools-elf`, report their configuration requirements directly and are
not accepted by `doctor --provider`. See
@@ -465,18 +231,7 @@ an exact result when the target bytes, operation, parameters, provider and
settings match. Mutations and cursor-dependent calls are excluded from the
cache. Snapshot files are local and use owner-only permissions.
One-shot binary CLI Evidence reports the exact path argument in `caller_input_path`,
including relative spelling and symlink aliases. Its subject identifies the
inspected artifact; `caller_input_path` does not change the Evidence ID or
snapshot query. A replay reports the current invocation's path while leaving
the saved Evidence unchanged. Provider inspections continue to include the
established `parameters.input_path` field.
Importing an Evidence bundle refreshes retained request and subject display metadata.
It preserves cached analysis results, including observations for other targets.
Snapshot bindings that use a replaced record retain its updated display path.
Dispatching a provider metadata write invalidates the active session's snapshot cache,
Dispatching a metadata write invalidates the active session's snapshot cache,
even if its reply fails: the provider may already have applied the write.
Close and reopen the target before importing or saving another snapshot.
@@ -501,20 +256,6 @@ cached result. `close_binary` accepts `snapshot_path` and optional
`overwrite: true` to save before releasing provider resources. A failed save
leaves the session open so the caller can resolve the output failure.
If a selected snapshot's file descriptor cannot close, `open_binary` reports
`cleanup_incomplete`. `binary_session` reports `cleanup_pending` even when no
target opened. Retry `close_binary` to release the retained descriptor before
opening another target.
If a snapshot switch fails after the previous target was retired, restoration
keeps that target's analysis commitments with the replacement client. This
includes the native dispatch workflow profile on a profileless route, so its
existing snapshot and fresh observations retain the same provenance.
For native dispatch, the snapshot binding retains the reader profile; workflow
Evidence and `workflow_entries` retain the composed profile, which commits that
reader and the workflow's analysis caveats. Restoration preserves both profiles.
The MCP save receipt reports `primitive_entries`, `workflow_entries`, and
`evidence_records` separately. Zero primitive bindings can still accompany retained workflow
results and Evidence. These are cached observations, not a saved provider
@@ -532,41 +273,16 @@ canonical form, or compare two supplied bundles:
rea evidence-import /absolute/path/to/evidence/bundle.json
rea evidence-export /absolute/path/to/evidence/bundle.json /absolute/path/to/evidence/canonical.json
rea compare /absolute/path/to/evidence/left.json /absolute/path/to/evidence/right.json
rea compare left.json right.json --record_pairs '[{"left_evidence_id":"ev_<left-sha256>","right_evidence_id":"ev_<right-sha256>"}]' --json
```
`--record_pairs` supplies an explicit JSON array of observation pairs, matching
MCP `compare_bundles.record_pairs`. Pair IDs must exist on their named sides and
be one-to-one. Without pairs, one-sided membership remains unknown; REA does
not infer correspondence from position or display names.
Exports preserve an existing destination unless `--overwrite` is explicit.
Atomic Evidence output stages on the selected destination's filesystem. If the
OS pathname ceiling prevents its internal staging name, REA retries in a shorter
ancestor on that same filesystem and removes the unused stage. Existing output
protection still applies. A genuine pathname refusal uses `resource_constraint`
with `details.reason: path-limit` and `boundary: filesystem-path`.
For application traces and version comparisons, see
[JavaScript application workflows](javascript-application-workflows.md).
MCP [retained Evidence references](mcp-contracts.md#retained-application-evidence-inputs)
belong to one connection. Separate CLI calls can consume full saved records.
`inspect-analysis-view REQUEST.json --evidence-bundle BUNDLE.json` resolves
the request's `retained-evidence` source ID from that authenticated bundle,
including bundles exported from an MCP session. The same view JSON works for
every supported parent kind; no target is reopened and no record is selected
by file order. Omit `--evidence-bundle` when the request supplies inline Evidence.
belong to one connection. Separate CLI calls consume full saved records.
## Import historical source
Historical source inventories preserve exact filenames, including literal backslashes
on POSIX hosts. Relative import lookup uses the recorded source file's owning
directory and decodes URL percent escapes once; encoded slash, NUL and malformed
escapes remain unresolved. A decoded backslash remains part of a filename rather
than a directory separator. This inventory lookup does not claim that Node can
load that filename or apply Node's runtime file-URL admission policy.
Import an older source tree to compare with the current artifact:
```bash
@@ -576,26 +292,12 @@ rea import-reference-source /absolute/path/to/source --detail summary --outline-
The default (`--detail full`) returns the complete `HistoricalSourceGraph`.
Choose `--detail summary` for a `HistoricalSourceSummary` of that same committed
inventory: its `HistoricalSourceManifest` contains the semantic graph and root
inventory: its `HistoricalSourceManifest` contains the full graph and root
digests, with provenance, VCS facts, languages, manifest paths, entry and
relationship counts, parse-failure counts, file/directory states, exclusions
and limitations. The summary is a presentation view, not a graph accepted by
source-to-bundle comparison; keep the full output for that workflow.
Known filesystem failures commit the operation, parsed errno, entry-relative
location and recovery guidance. Entry `local_diagnostics`, when present,
retains the exact host message, including local paths, outside the semantic
graph commitment. Equivalent permission failures therefore retain their manifest
identity after relocation while preserving original source diagnostics in full
output and visible summary outline entries. Other substantive limitations still
participate in the graph commitment.
An unreadable child directory remains a partial inventory entry. Directory-scoped
ignore policy is required before enumerating a directory that can be opened;
an unreadable or invalid policy still fails the import. Project negations cannot
override defaults or caller exclusions; caller patterns retain their own ordered
negations. Configured secret exclusions remain final.
`--outline-depth` requires `--detail summary` and accepts a nonnegative safe
integer. Depth 1 shows root children, depth 2 (the default) also shows their
children, and depth 0 omits all outline entries. Paths retain canonical Unicode
@@ -615,26 +317,11 @@ output, not the amount of source examined. A shallow tree or many exclusions can
still produce a large summary; depth 0 removes the outline but preserves the
other facts. No fixed output-size or runtime improvement is promised.
Import failures use the shared error contract (`code`, `category`, `message`,
`retryable`, and `remediation`). When reads completed before failure,
`details.diagnostics.partial` retains the reader facts, with file bytes as
arrays of byte values. Unresolved cleanup uses `cleanup_incomplete` and
retains the source failure and resource paths in `details`.
The import records hashes and metadata for the supplied files, separately
from observations of the current app. For a directory inside a Git checkout,
it records the HEAD of the nearest enclosing repository; whether the working
tree matches that commit remains unknown. Root selection follows filesystem
symlink/parent semantics before canonical identity is derived. It honors
`.gitignore` files at the selected root and in traversed child directories,
with patterns relative to their containing directory and deeper negations
applied only when the parent was not excluded. Matching uses the nearest
repository's local `core.ignorecase` setting (false when unset), including the
shared configuration of linked worktrees. Policy and Git metadata reads retain
their descriptor cleanup under the import's resource owner; unreadable policy
is a typed failure, not an empty policy. Above-root `.gitignore` files, global
excludes, `.git/info/exclude`, config includes and worktree-specific config are
not loaded. The import also applies default exclusions for common generated and dependency paths such
tree matches that commit remains unknown. It honors the source tree's `.gitignore`
and applies default exclusions for common generated and dependency paths such
as `node_modules/`, `dist/`, and `*.log`. Pattern-based exclusions record the
matched pattern and whether it came from project, default, caller, or sensitive
path policy. Links into excluded paths retain their internal target metadata;
@@ -653,54 +340,11 @@ Entries that change during an import retain an unavailable-entry diagnostic
alongside readable siblings. If enumeration of an already admitted root fails,
the graph keeps its observed children and records the root failure as a
top-level limitation. That partial inventory does not establish absence among
unexamined children. POSIX directory-entry bytes that are not valid UTF-8 cannot
be represented as graph paths; the containing directory (or root) is marked
unsupported, and REA does not access the entry through a replacement-character
name. Symlink targets are read as bytes too: a target that is not valid UTF-8
is retained as an unreadable symlink with its bytes reported in the limitation,
and is never resolved through a replacement-character path. Readable siblings
remain inventoried.
unexamined children.
JavaScript and TypeScript import parsing requires valid UTF-8. Malformed source
bytes retain their original hashes and sizes with a decoding diagnostic; REA
does not infer module targets from replacement characters. Exact dynamic-import
strings, expression-free templates and runtime-transparent TypeScript wrappers
retain their targets; computed imports remain unknown. TypeScript import-equals
dependencies remain `requires` relationships whether or not they are exported.
Historical dependencies also include TypeScript import types (`import("./dep").T`
and `typeof import("./dep")`) and declarations inside ambient module bodies;
these source relationships do not assert runtime loading. Language labels retain
the conventional case-sensitive `.C` (C++) and `.M` (Objective-C++) suffixes.
REA derives historical CommonJS paths only when the selected loader and its
base are proven: this includes the exact
`createRequire(new URL("./file.cjs", import.meta.url))` form and
`require.resolve` with `{ paths: [__dirname] }`. Exact optional-call forms such
as `require.resolve?.("./dep.cjs")` use the same loader and mutation facts. The
nearest inventoried package manifest supplies `.js`/`.ts` package facts to the
shared parser; explicit filename modes take precedence. A known ES module has no
implicit CommonJS wrapper, including when a factory is derived from an unbound
`require`. An unreadable, excluded or malformed manifest, or package scope outside
the selected inventory, leaves otherwise unproven loader resolution unknown with
the requested specifier and a reason. A nearest manifest without a `type` field
uses the shared parser's JavaScript module-syntax facts. Unresolved TypeScript,
JSX compilation and declaration contexts do not establish runtime wrapper bases.
Other custom bases and search options remain partial with unknown resolution and
the literal requested specifier. Overwritten resolver methods retain that uncertainty; they are not
attributed to Node. `require.main` is a module object, so calling it does not
establish a loader reference. These relationships do not establish execution;
Node's [`require.resolve`](https://nodejs.org/docs/v24.22.1/api/modules.html#requireresolverequest-options)
performs lookup without loading the target. An excluded `package.json`
also leaves package-directory selection unknown rather than treating the
manifest as absent and applying an `index` fallback.
Import requests use URL pathname semantics: query and fragment remain request
identity, percent escapes decode exactly once for inventory lookup, and encoded
separators or malformed escapes retain an unresolved relationship and diagnostic.
URL-bearing relationships retain the authored `source_specifier` separately from
the inventoried `to` path; distinct query/fragment requests remain distinct even
when they name the same file. CommonJS requests retain literal filename semantics
for percent signs, query marks and fragments.
does not infer module targets from replacement characters.
Historical-source import requires safe no-follow file opens on Linux or
macOS. Native Windows returns `unsupported_host`; use Linux REA inside WSL
@@ -746,26 +390,14 @@ entry keep stdio.
## Serve an environment manifest
`rea environment-serve <manifest.json> --evidence <capture.json>
[--replay-only] [--host H] [--port N]` serves an environment manifest
projected by `inspect-analysis-view` as an agent environment: an MCP server at
`/mcp`, the environment card at `/.well-known/agent-env.json`, and a
`/agentenv` data plane. The manifest's `parent_evidence_id` must equal the
evidence file's `evidence_id`; otherwise the command refuses with
`invalid_request` before binding. Before binding it derives the
`environment-state` view of the capture with the manifest's `template_rules`,
as `inspect-analysis-view` would, and serves from it: read tools whose views
are state-backed answer from a live state, write tools are served only where
the capture's later reads confirmed their effect, and
`/agentenv` serves `data/reset`, `data/add` and `data/get`. When the state
cannot be derived, or its catalog exceeds the discovery reservation, it logs a
warning with the error code and message and serves replay; `--replay-only` serves replay without deriving. In replay every
manifest tool replays the retained capture records it cites and `/agentenv`
answers every method with JSON-RPC `-32601`. The stopped document's `state`
member is `{"served": true, "evidence_id": …}`,
`{"served": false, "reason": "replay-only"}` or
`{"served": false, "reason": "unavailable", "code": …, "message": …}`; see
[serve from the state](agent-environments.md#serve-from-the-state).
`rea environment-serve <manifest.json> --evidence <capture.json> [--host H]
[--port N]` serves an environment manifest projected by `inspect-analysis-view`
as an agent environment: an MCP server at `/mcp` with one tool per manifest
tool, each replaying the retained capture records it cites, the environment
card at `/.well-known/agent-env.json`, and a `/agentenv` data plane that
answers every method with JSON-RPC `-32601`. The manifest's
`parent_evidence_id` must equal the evidence file's `evidence_id`; otherwise
the command refuses with `invalid_request` before binding.
```sh
rea environment-serve manifest.json --evidence capture.json --port 8765
@@ -776,33 +408,8 @@ rea environment-serve manifest.json --evidence capture.json --port 8765
the served environment; set but blank, it is refused as `configuration_invalid`
like a blank `MCP_HOST`. The bound URL is written to stderr as one JSON log line
(`"msg":"MCP transport listening"`). See
[agent environments](agent-environments.md) for what the state and replay
return and do not establish.
## Build an environment bundle
`rea environment-build <capture> [dir] [--name <name>] [--template-rule <rule> ...]
[--format-rules-only] [--package-tarball <path>] [--capture-timeout-ms <ms>] [--dry-run]` builds the bundle
`environment-export` writes from one recorded capture: a `.har` file, which it
inspects as `inspect-web-network-capture` does, or capture Evidence JSON from
`inspect-web-network-capture` (HAR) or `capture-browser-scenario`. It projects
the environment manifest from the in-memory Evidence with the opt-in rules
`after-uid-literal`, `opaque-token`, `sibling-variance` and `prefix-variance`
unless `--template-rule` or `--format-rules-only` selects others, prepares the
state, and writes the bundle; every file is byte-identical to the three-step
path's when that path lists the same rules in the views' order and exports
under the build's name. A loopback or IP-literal capture is named after its file unless `--name`
is given. It prints one JSON report: the capture and manifest identities, the
state, a `coverage` summary of which tools answer from the state, which replay
and why each write is withheld with a hint, the files, warnings and the next
command. `--dry-run` writes nothing and may omit the directory.
```sh
rea environment-build grafana.har ./grafana --json
```
See [agent environments](agent-environments.md#build-a-bundle-in-one-step)
for the report, the hints and what the bundle ships.
[agent environments](agent-environments.md) for what replay returns and does
not establish.
## Export an environment bundle
@@ -810,11 +417,9 @@ for the report, the hints and what the bundle ships.
[--package-tarball <path>] [--name <override>]` writes the same manifest and
capture pair as a bundle folder the agentenv framework runs with
`agent-env run <dir>`. It reads each file once, authenticates and cross-checks
the pair with the same checks as `environment-serve`, checks the bundle name,
then prepares the state under that name as the bundle's server will at startup
to predict what the bundle serves (replay when the state is unavailable),
copies the bytes it authenticated, refuses to write into a directory that
exists and is not empty, and never overwrites a file.
the pair with the same checks as `environment-serve`, copies the bytes it
authenticated, refuses to write into a directory that exists and is not empty,
and never overwrites a file.
```sh
rea environment-export manifest.json --evidence capture.json ./grafana-example --json
@@ -824,87 +429,19 @@ The folder holds a `README.md` with the bundle's provenance, an env folder
`envs/<name>/` with a `Dockerfile` that installs this exact `rea-agents`
version and serves the copied `manifest.json` and `evidence.json` (written
with mode `0600` on POSIX; Windows retains inherited ACLs) with
`environment-serve` (from the state by default), a Python verifier under
`artifacts/check_<name>/` that checks the served tool count and one served
answer, and the smoke task `tasks/smoke.json`. `--package-tarball` copies a packed `rea-agents`
`environment-serve`, a Python verifier under `artifacts/check_<name>/` and the
smoke task `tasks/smoke.json`. `--package-tarball` copies a packed `rea-agents`
tarball into the env folder and installs it instead of the registry package,
which is how the hosted lane proves a checkout rather than a release. `--name`
replaces the manifest's kebab-case name as the env folder and env id; a device
name Windows reserves (`con`, `nul`, `com1` and the like) is refused. The
command prints one JSON document listing the written files with their sizes,
the pinned package source, the served `tool_count`, and `state` in the shape
of the serve command's stopped document. See
command prints one JSON document listing the written files with their sizes
and the pinned package source. See
[agent environments](agent-environments.md#export-a-bundle) for the folder
layout and what the smoke task checks.
## Compare an environment with its application
`rea environment-compare <manifest.json> --evidence <capture.json> --probe <probe.json> [--probe <probe.json> ...]`
compares the environment the pair serves with probe captures of the application it came from. Each probe is capture
Evidence of one call sequence recorded against the application after a reset; the environment's state is reset before
each probe, every request to the application's origin is answered as the served environment answers it, and the
answer is compared with the application's after ids are mapped and clocks and generated values are normalised. The command reads files only and
makes no network call. It prints the report and exits `0`, or prints a structured `invalid_request` error and exits `1`
for the same pair refusals as `environment-serve`, a probe that is not capture Evidence, or a browser probe captured
without response bodies, or without the request bodies its writes need. See
[agent environments](agent-environments.md#measure-fidelity-against-the-application) for the report.
```sh
rea environment-compare manifest.json --evidence capture.json --probe probe.json --json
```
## Report version and runtime
`rea --version` prints the package version. Add an output format to get the
version with the runtime and platform context that a support request needs:
Version reports do not initialize analysis providers. Invalid global settings
or output options still report their usual errors.
```bash
rea --version --json
```
```json
{
"name": "rea-agents",
"version": "6.7.0",
"runtime": { "id": "node", "version": "24.22.1" },
"platform": "linux",
"architecture": "x64",
"supported_runtime": {
"range": "^22.23.4 || ^24.22.1 || >=26.12.1",
"satisfied": true
}
}
```
| Field | Meaning |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
| `name`, `version` | Published package name and version; `version` matches plain `--version`. |
| `runtime.id` | `node` or `bun`. |
| `runtime.version` | Version of that runtime. |
| `runtime.node_compatibility_version` | Bun only: the Node.js version it reports compatibility with. |
| `platform`, `architecture` | Node.js `process.platform` and `process.arch`, such as `linux` and `x64`. |
| `supported_runtime.range` | Supported Node.js range. |
| `supported_runtime.satisfied` | Whether this runtime is supported, judged as `rea doctor` judges it; Bun by its Node.js compatibility version. |
The field names are stable. The report contains no paths, environment values,
user or host names, and it starts no provider and reads no target. `--format`
selects any CLI output format, such as `--format yaml`. JSON follows the usual
rule: indented on a terminal, compact when piped or redirected. `--help` still
takes precedence. With other output controls, `--version` keeps its existing
behavior: valid controls such as `--filter-output` or `--full-output` print the
plain version, and invalid combinations report the usual output-flag error,
such as `UNSUPPORTED_OUTPUT_COMBINATION` for `--json` with `--token-limit`. Use
`rea doctor` for provider and host readiness.
## Output and exit status
Commands reject surplus positional arguments before execution, including commands
that accept no positionals. Use `rea <command> --help` for the declared argument
shape; a final variadic argument continues to accept multiple values.
`rea mcp doctor --json` checks production MCP startup and reports child logs in
`diagnostics`. It drains stderr while retaining at most its first 64 KiB of raw
bytes as text, omitting a final UTF-8 code point if it crosses that byte limit.
@@ -916,36 +453,15 @@ truncation alone does not change the diagnostic health or exit status.
The default terminal format is TOON. Use `--json` when saving results for a
JSON consumer. Valued output flags (`--format`, `--filter-output`,
`--token-limit`, `--token-offset`) accept `--flag value` or `--flag=value`; a
missing value fails with `MISSING_OUTPUT_VALUE` before the command runs.
Malformed format values and token windows fail with `INVALID_OUTPUT_VALUE` in the
selected structured format. Token limits and offsets must be nonnegative safe
integers; zero selects an empty window. These checks run before command execution. `--json` is indented on a terminal and compact when piped or
missing value fails with `MISSING_OUTPUT_VALUE` before the command runs. `--json` is indented on a terminal and compact when piped or
redirected, which keeps large results smaller for agents and scripts; pipe
through `jq .` for indented files. Output selection and formatting do not change
operation status.
`--format md` escapes backslashes, pipes, and inline Markdown syntax in table
headers and cells so source text is displayed literally, and renders cell line
breaks as `<br>`. Underscores between letters or digits, as in `source_index`,
cannot form emphasis and stay unescaped. These are presentation changes; retained
Evidence and JSON output keep the original values. Nested objects and arrays
use JSON inside their owning Markdown cells, retaining the field names and
values instead of replacing them with an object label. Table widths are measured
iteratively, so an admitted row page is not limited by the JavaScript function
argument count.
`--filter-output` selects fields from successful command data. Classified failures
retain their complete typed diagnostic projection even when the selector matches
a field such as `code` or `message`, or names a missing field. `--full-output`
controls whether the outer `{ok, error, meta}` envelope is included.
Selected own JSON members, including `__proto__`, remain ordinary output data.
Sibling array slices that select different fields are combined by original row
index and emitted in source order, regardless of selector order. Selecting a
whole parent row keeps that row intact and takes precedence over narrower fields
selected from the same row. For example, `rows[0,2].name,rows[1,3].version`
combines both fields for overlapping rows, while `rows[0,2],rows[1,3].version`
retains rows 0 and 1 whole.
For `cutter-command`, `--json-output` requests command-specific JSON from
Cutter when available. This is separate from `--format json`, which selects the
@@ -972,33 +488,18 @@ SIGTERM, closes the listener, then prints one structured
`{"status":"stopped", …}` document naming the URL, environment and tool count
and exits `130` or `143` through the shared cancellation handler below.
Refusals before binding (unreadable files, non-Evidence JSON, capture Evidence
in a format other than HAR, a manifest or state whose parent is not the
supplied evidence, an unbindable port) print a structured
in a format other than HAR, a manifest whose parent is not the supplied
evidence, an unbindable port) print a structured
error and exit `1`, as does a listener that fails after binding.
If shutdown fails, the command reports `cleanup_incomplete` with the retained
server resources. It preserves a primary listener diagnostic, or a
`shutdown_observation` naming the requested shutdown and its URL, environment,
tool count and Evidence identifiers. The successful `stopped` document is
returned only after cleanup is confirmed. The same in-process server owner
retains failed stages for retry; the CLI exposes no separate retry command.
`rea environment-build` is one-shot too: it prints its report and exits `0`,
or prints the refusing stage's structured error and exits `1`, writing nothing
when the refusal comes before the bundle is written. SIGINT cancels it with a
`cancelled` error; a cancellation before the bundle stage writes nothing.
`rea environment-export` is one-shot: it prints the written files and exits
`0`, or prints a structured `invalid_request` error and exits `1` for the same
pair refusals, a non-empty or file-occupied directory, a missing package
tarball or a name that is not a kebab-case label or is a Windows device name,
writing nothing in each case. A file occupying the destination is reported as
requiring a directory; identity-change diagnostics are reserved for admitted
paths that fail later identity checks. Host permission failures identify whether
input reading or bundle writing was denied and retain the reported path.
A write that fails after the first file (a permission, disk-space or
pathname-length failure on a later path) propagates as the host's failure,
naming its cause and, when it failed on one bundle file or the package
tarball, that file. A failure while syncing the finished folder names only its
cause. Either way the partial folder stays in place for inspection; remove it
before retrying, since the next attempt refuses the non-empty directory.
writing nothing in each case.
A write that fails after the first file (a permission or disk-space failure
on a later path) propagates as the host's failure and leaves the partial
folder in place for inspection; remove it before retrying, since the next
attempt refuses the non-empty directory.
A `cancelled` result does not by itself determine the exit status; it depends on
the command's signal handling. Commands routed through the shared
@@ -1013,29 +514,19 @@ browser, web and Electron commands (including `analyze-web-bundle`,
for Android/ADB, Apktool/JEB/Flutter, firmware, PCAP and signal analysis,
WASM/EVM, Go/Rust and binary diagnostics, Frida instrumentation, the Cutter
command, objdump, Rizin and GDB, and the agent environment server
(`environment-serve`). The handler also wraps `doctor`,
`inspect-sqlite-database`, `recover-javascript-sources`,
`inspect-il2cpp-metadata`, and `inspect-desktop-application`. In these paths, a returned structured
(`environment-serve`). In these paths, a returned structured
`cancelled` result can be printed before the wrapper assigns the
signal-derived status. This describes the current source routing; it is not a
claim that every command or provider runtime has been independently exercised.
Commands with `--evidence_output` select their delivered view, then publish the
complete Evidence bundle atomically. Cancellation before publication leaves no
new file. A publication that has committed can win a later cancellation; the
`cancelled` result then keeps the written path and byte count under
`details.diagnostics.evidence_bundle`. If staging cleanup stays incomplete
after commit, the `cleanup_incomplete` error reports the same file as
`details.published_path` and `details.bytes`.
When the reader of piped output closes early, as `rea ... | head` does, the
CLI stops writing, prints no error and exits `141` (`128 + 13`, the status of a
SIGPIPE-terminated writer). The operation itself may have completed; only its
output was declined.
`inspect-artifact` also returns a structured cancelled result. Its logging
wrapper sits outside the cancellation handler and replaces the signal-derived
status with the analysis failure status, so it exits `1`. Setup has its own contract: cancellation exits `0`. Treat the
`inspect-artifact` also returns a structured cancelled result, but its direct
analysis cancellation path does not set a signal-derived exit status, so it
exits `1`. Setup has its own contract: cancellation exits `0`. Treat the
structured result and process status as separate signals when scripting
cancellation.
@@ -1051,35 +542,3 @@ REA's failure status:
set -o pipefail
rea inspect-artifact ./app.asar --json | jq . > inspection.json
```
`--token-count` reports a tokenx estimate for the selected emitted document,
including a requested full-output envelope and metadata. JSON uses the same
terminal indentation or compact piped representation as normal output; JSONL
uses its emitted line representation. The final newline is included (tokenx
assigns whitespace no tokens). This is an estimate, not a model tokenizer count.
Counting retains the existing bounded formatted-string allocation after capacity
admission; omit the flag to stream large JSON documents without materializing
the complete formatted text. Exhausted token windows display an empty range at
the total token count and have no next offset.
### `inspect-devicetree`
Inspect an explicit local FDT v16/v17 blob without execution or an external tool.
Returns header, reservations, node/property tree, derived standard values and
selected phandle references. See [devicetree inspection](devicetree.md) for
limits and retained-view pagination.
```bash
rea inspect-devicetree /artifacts/board.dtb --json
rea inspect-devicetree /artifacts/board.dtb --detail summary --evidence_output /artifacts/board-evidence.json --json
```
### PE TLS and load configuration
```sh
rea inspect-pe-load-config /absolute/path/to/application.exe --json
```
Reports TLS callbacks and size-covered load-config fields without execution.
Use `--max-callbacks` and `--max-file-bytes` to set read budgets. See
[PE TLS and load configuration](pe-load-config.md).
+14 -222
View File
@@ -6,34 +6,6 @@ for the reviewed setup transaction and [client IDs](installation.md#supported-ag
The versioned verification notes below describe their stated runs; live service
acceptance is verified only where explicitly recorded.
## Runtime settings and client deadlines
`update_analysis_settings` changes curated defaults inside the running REA
server. It does not edit the client registration, extend the client's tool-call
deadline, or recompute timeout values previously derived by `rea setup`. When
raising a REA startup or operation deadline, separately ensure the client allows
enough time for the full request. Use `inspect_analysis_settings` for the running
server's defaults; `rea doctor` checks launch configuration and cannot observe
another process's runtime overrides. See the
[settings design](design/limits-and-recovery.md) for ownership and lifetime.
## Client tool names and discovery
REA advertises canonical names such as `binary_session`. The MCP host decides
how to expose them to its model: a name such as `mcp__rea__binary_session` is a
client convention, not a prefix REA can enforce. Use the active client's tool
listing and server association to find the operation. A valid registration or
installed skill does not prove that those tools reached the model. Doctor reports
live-server identity as unknown; query `binary_session` through the active client
to observe the running server's identity.
If a client or fork exposes several servers under the same prefix, a wildcard
for that prefix does not isolate REA permissions. Inspect that client's actual
tool names and approval rules before selecting a permission scope. REA setup
does not repair a client's naming behavior or infer permission drift from an
unfamiliar prefix. The unavailable fork reported in
[#5512](https://github.com/morluto/rea/issues/5512) has not been reverified.
## Fiddler Everywhere alongside REA
Connect Fiddler Everywhere's MCP server separately for HTTP traffic debugging,
@@ -42,12 +14,6 @@ preserving your REA client entry. See the
## GitHub Copilot CLI
Setup preserves an existing `tools` selection in the REA registration, including
an empty list. New registrations default to `["*"]`. A restricted selection is
aligned configuration; doctor does not widen it to expose every REA tool. See
[Copilot's MCP configuration](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers)
for tool-selection settings.
For GitHub Copilot CLI 1.0.95, `gpt-4.1` model metadata can block ordinary
chat with REA's full input-schema profile before sending a model request
(`compaction_static_context_blocked`; [#1554](https://github.com/morluto/rea/issues/1554)).
@@ -107,17 +73,12 @@ to rerun it.
A process that the MSIX app started, such as a terminal in Claude Desktop's
Code tab, sees the package's private files at their `%APPDATA%` paths even
though it reports no package identity. Setup compares one of those files at
both paths: first one from the package's private `Claude` folder, and when
that folder has none (some installations keep writing the real
`%APPDATA%\Claude`), one from the package's other private roaming folders,
which the package redirects in the same way. When it finds that redirected
view, the app is running, so setup and uninstall refuse to change Claude
Desktop's configuration; rerun them from a terminal that Claude Desktop did not
start. If the package keeps no private roaming file at all, setup and
uninstall cannot rule that out and also refuse. With Claude Desktop fully
quit, add REA's entry by hand to the file setup names, which is the one the
app reads, or remove it from both the private copy and the `%APPDATA%` file.
Doctor reads the same file from either kind of process.
both paths. When it finds that redirected view, the app is running, so setup
and uninstall refuse to change Claude Desktop's configuration; rerun them from
a terminal that Claude Desktop did not start. If the package has no private
file to compare, setup and uninstall cannot rule that out and also refuse; open
Claude Desktop once if it has never run, quit it fully, then rerun. Doctor
reads the same file from either kind of process.
The same process also writes everything under `%APPDATA%` and
`%LOCALAPPDATA%` into the package's private storage, where other apps never
@@ -192,17 +153,9 @@ headroom. The default is 450000 ms, covering the default 330-second Ghidra
startup; an explicit Ghidra startup timeout of 2700000 ms produces a 2820000 ms
tool timeout. The startup allowance follows an explicitly selected Ghidra or
Hopper provider, and otherwise uses the larger known Ghidra/Hopper deadline.
An explicit `REA_JEB_MCP_TIMEOUT_MS` deadline, shared by catalog discovery and
each tools/call, also contributes to that maximum. An explicit `REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS` is added to the Ghidra startup allowance, because a single-decompile call can import the target and then decompile up to that limit; setup forwards it into the registration. For `ida` or `auto` selection, a valid IDA registration's parsed
An explicit `REA_JEB_MCP_TIMEOUT_MS` request budget also contributes to that
maximum. For `ida` or `auto` selection, a valid IDA registration's parsed
`timeoutMs` also contributes to that maximum; its default is 300000 ms.
The derived value is a single-decompile estimate, not a deadline for an
unbounded batch. For example, startup 330 seconds plus two 600-second
decompiles and 120 seconds of headroom needs an estimated 1650 seconds,
before any additional listing or output work. Select the client tool deadline
for the intended workload, split `batch_decompile`, use `decompile_all`
limit/resume, or reduce `trace_native_values` `max_functions`. The trace can
decompile several functions serially; its depth and graph budgets do not
make the single-decompile allowance a whole-traversal deadline. Existing positive caller deadlines are preserved.
Setup forwards these explicitly selected REA deadlines and the input schema
profile into the registration; unrelated process values are omitted. An
explicit `REA_JADX_OPERATION_TIMEOUT_MS` is forwarded with the JADX
@@ -284,82 +237,22 @@ and the selected `trustedFolders.json` (including
`GEMINI_CLI_TRUSTED_FOLDERS_PATH`). Settings in an untrusted workspace do not
participate. A workspace or system `mcpServers.rea` replaces the entire user
entry; setup stops if the winning definition conflicts with its planned entry.
Setup checks user policy against the file snapshot it is inspecting or about to
update, including confirmed absence; an earlier discovery snapshot does not veto
a repaired user policy. Independent system and workspace sources are still checked.
Review those files manually: setup never edits administrator/workspace settings
or grants workspace trust. Trust supplied only by a running IDE is outside this
standalone CLI check.
A model API's `400 INVALID_ARGUMENT` alone does not identify a rejected schema,
tool-count limit or skill defect. Preserve the response's parameter path and
diagnostic, the actual running REA version, Gemini CLI version and selected
model. For schema rejection, review the [input presentation profiles](mcp-contracts.md#compact-input-schema-profile)
and reconnect after changing REA's registration environment. Compact is the
current default; an explicitly stored `full` choice is preserved. The generic
error in [#4620](https://github.com/morluto/rea/issues/4620) does not establish
which constraint failed, and local schema checks do not prove live API acceptance.
## Codex and Devin
REA manages its launcher and selected provider settings in the client
registration. Codex's server and per-tool approval policies are user choices.
Setup patches REA's connection fields in the selected file and retains other
server settings, including `default_tools_approval_mode`, nested
`[mcp_servers.rea.tools.<name>]` tables, tool allow/deny lists, and unrecognized
client settings. Doctor accepts Codex's per-tool policy tables. Known malformed
policy values block setup before it writes the configuration or a backup, and
the diagnostic identifies the setting to repair. See the
[Codex configuration reference](https://developers.openai.com/codex/config-reference/)
for the supported policy values.
When direct launcher paths prove the same executable or Node entrypoint,
setup retains the existing caller-selected spelling. An otherwise aligned
registration therefore needs no rewrite or recovery backup merely because
REA was invoked through another symlink. Setup also retains that spelling when
another field, such as an invalid timeout, needs repair. Unresolved identity
does not prove equivalence.
| Settings | Setup ownership |
| ------------------------------------------- | --------------------------------------------------------------------------------------- |
| Launcher, transport and activation | Refreshes the REA stdio connection and enables it. |
| Explicit server environment | Preserves configured overrides and merges selected provider settings. |
| Codex timeouts | Retains positive values; repairs missing or invalid values to recommended REA defaults. |
| Codex approval policies and tool selections | Retains caller choices; refuses known malformed values. |
| Unrelated or future server settings | Retains their parsed values. |
Dry-run applies the same policy validation and writes no files. A successful
refresh keeps an exact recovery snapshot of the original configuration;
repeating setup with the same settings performs no write. Uninstall removes
the owned REA entry, including its policy, while retaining other servers and
the recovery snapshot. A green registration check establishes known
configuration alignment; it does not prove that a particular client or fork
successfully exposes every REA tool.
Codex treats an unset or empty `CODEX_HOME` as `~/.codex`. Its MCP server
registration supports `startup_timeout_sec` for connection initialization and
`tool_timeout_sec` for each tool call. New REA registrations use a 30-second
startup timeout and a tool timeout derived from the known provider
allowance plus 120 seconds of operation headroom. This yields 450 seconds for
the default Ghidra allowance; an explicit Ghidra startup timeout of 2700
seconds yields 2820 seconds. That 30-second Codex figure is client guidance.
REA's own phase budgets are `REA_MCP_INITIALIZE_BUDGET_MS` (default 15000),
`REA_MCP_TOOLS_LIST_BUDGET_MS` (default 5000),
`REA_MCP_FIRST_CATALOG_BUDGET_MS` (default 20000), and
`REA_MCP_DOCTOR_DEADLINE_MS` (default 30000). Each is a positive decimal
integer of milliseconds from 1 to 2147483647. Unset keeps the default.
Invalid values fail configuration and name the setting. Setup copies a
caller-selected value into the registration, and a change is read from the
environment that starts REA, so it needs a restart. Doctor and packaged
startup probes name the effective milliseconds, whether they came from the
default or the setting, the default, the floor, and the maximum. REA does
not enforce Codex's 30 seconds as the doctor deadline. The 120000 ms
tool-operation headroom and the 64 KiB doctor stderr cap stay fixed. Setup preserves positive caller-selected timeout
seconds yields 2820 seconds. Setup preserves positive caller-selected timeout
values in the REA entry, and doctor reports a non-blocking advisory when a tool
timeout is shorter than the known provider allowance. An explicitly selected
JEB request budget also contributes to the allowance, and an explicit
`REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS` is added to the Ghidra startup
allowance. A configured IDA
JEB request budget also contributes to the allowance. A configured IDA
registration's parsed `timeoutMs` contributes for `ida` or `auto` selection.
An explicit `REA_JADX_OPERATION_TIMEOUT_MS` is forwarded with the JADX
registration but does not contribute; raise `tool_timeout_sec` to cover a
@@ -368,44 +261,12 @@ These values are client-side deadlines, so increase them in `~/.codex/config.tom
workload needs more time. Devin honors `XDG_CONFIG_HOME` for its
`devin/mcp_config.json` on non-Windows systems.
### Codex with Moonshot or Kimi models
An older REA input containing acyclic local references was rejected by a
Responses-compatible Kimi gateway as “infinite recursion”; expanding those
references succeeded. A separate full-profile input failed the provider's
schema-size limit. These are distinct reported failures, not proof that the
canonical schema is recursive. Current REA inputs are already self-contained;
see [schema advertisement](mcp-contracts.md#schema-advertisement).
For this failure, inspect the active MCP launch command and refresh the REA
installation that command actually runs. A configured package selector or the
installed skill version alone does not identify a cached or still-running
server. Follow [installation and update guidance](installation.md#diagnose-update-and-remove).
Then merge this setting into the REA entry in `$CODEX_HOME/config.toml`
(`~/.codex/config.toml` by default), preserving its launcher and other settings:
```toml
[mcp_servers.rea.env]
REA_MCP_INPUT_SCHEMA_PROFILE = "compact"
```
If that table already exists, add or update the key there. Restart/reconnect
Codex so REA reads the new environment. Compact is now REA's default, but an
explicit `full` override remains effective until changed. It preserves the
tool inventory and canonical server validation while reducing advertised input
detail. The probes in [#5160](https://github.com/morluto/rea/issues/5160) covered
individual tools through one gateway, not the entire current catalog or a
model-driven analysis workflow.
## OMP
For OMP, setup writes a `type: "stdio"` entry to the user-level
`~/.omp/agent/mcp.json`. A relative `PI_CONFIG_DIR` selects a configuration root
under the home directory. For the default profile, `PI_CODING_AGENT_DIR`
overrides the agent directory; a relative value uses the setup working
directory. A named profile selected by `OMP_PROFILE` or `PI_PROFILE` instead
uses `<configuration-root>/profiles/<name>/agent/mcp.json` and ignores
`PI_CODING_AGENT_DIR`. Setup also removes
`~/.omp/agent/mcp.json`. It follows `PI_CONFIG_DIR` and
`PI_CODING_AGENT_DIR` (relative to the setup working directory), and the profile selected by `OMP_PROFILE` or
`PI_PROFILE` (`~/.omp/profiles/<name>/agent/mcp.json`). Setup also removes
`rea` from that file's `disabledServers` list, which would otherwise hide the
registration. Doctor treats an `enabled: false` entry as active when
`enabledServers` lists `rea`, as OMP does, unless `disabledServers` also lists
@@ -424,11 +285,7 @@ Setup, doctor and uninstall follow the root's `active_profile` selection, as
plain Hermes CLI commands do. An explicit `HERMES_HOME` pointing to
`profiles/<name>` selects that named profile directly. An invalid, missing or
deleted sticky profile is reported instead of writing into the default home;
it also remains an unresolved skill selection, so setup cannot report a
successful empty installation. Overrides nested beneath the native Hermes home
use that native home's sticky selection and profile root together, matching
[Hermes's root rules](https://github.com/NousResearch/hermes-agent/blob/748c79a1c6873124d84071cdabb4062a75913e1f/hermes_constants.py#L83).
Repair the profile or run `hermes profile use default` before retrying.
repair the profile or run `hermes profile use default` before retrying.
REA follows `HERMES_HOME` whitespace trimming, environment-variable expansion,
and home expansion before resolving that profile. Setup previews the resolved
configuration and skill destinations; doctor and uninstall use those same paths.
@@ -504,28 +361,6 @@ as stale, and rerunning setup removes the flag to repair it. Native configuratio
uses strict JSON; setup refuses comments, trailing commas and a BOM rather than
writing a registration ZCode cannot load.
## FreeBuff
For FreeBuff, the proposed integration writes `mcpServers.rea` into the user-scope
`~/.agents/mcp.json` shared-agent file. Entries carry `command`, `args`, and
`env`; stdio is the default transport. The proposed personal skill location is
`~/.agents/skills`, so setup installs its skill copy there. Because the same
file doubles as ZCode's user-scope fallback (see above), a `freebuff`
registration is also in the file ZCode uses until ZCode defines its own native server.
Setup and doctor detect FreeBuff from its own profile directory
(`~/.config/freebuff-desktop`), not from the shared `.agents` folder that
REA's skill installs create for every client.
The configuration write and personal skill locations are proposed integration
boundaries. FreeBuff MCP loading and skill discovery have not been verified
with an installed native FreeBuff client/version; filesystem tests cover REA's
editing, discovery, and ownership behavior only. Automatic applicability
requires the FreeBuff profile directory even when the shared MCP file exists.
FreeBuff is main-only and is not in a published release yet; npm 6.7.0 is the
latest. `rea setup --client freebuff` against `@latest` reports an unsupported
client until a release containing it is published.
## DeepSeek Harness
DeepSeek Harness registers MCP clients through a Cordis YAML patch sequence
@@ -611,46 +446,3 @@ npx -y rea-agents@<version> mcp
Do not put credentials in that command or its arguments. `rea doctor --client grok_bot`
reports this manual step. `rea uninstall` does not remove the account connector.
## Skill selection and maintenance
OpenCode's selected configuration layers determine the registration dialect.
If any loaded layer uses native `mcp.servers`, setup writes a native REA entry
in the selected upper file even when that file is absent or has no MCP table.
Legacy activation is normalized before layer merging, so an explicit enable
can override an inherited disable. Lower files and other server entries remain
unchanged; a legacy server named `servers` that conflicts with the native table
requires manual repair before setup can write.
When Hermes aliases refer to an anchored REA entry, refreshing or removing REA
retains the aliases' original resolved values. Setup does not redirect another
server's alias to REA's new launcher or policy.
With no client selected, the interactive skill choice targets the shared
`~/.agents/skills` copy. Selecting a client targets its personal destination
when supported. Use `rea setup --skill-client shared --skill` for the shared
copy, or `--skill-client claude_code --skill` for that client's copy. Setup
resolves the final selection before deciding which files need installation.
Doctor retains each selected stale or missing path and names the matching
repair command; a current copy elsewhere does not establish freshness.
Unresolved profile or directory selections remain diagnostics and never become
write targets. Update maintenance considers only known enabled, owned REA
registrations and owned skills. If identity or a selected skill root is
unresolved, it reports maintenance as unavailable with the known scope and
remaining diagnostics. An empty writable scope alone does not prove current.
When configuration publication fails, setup retains the affected path, operation
and available filesystem code in the client result. Owner/group and mode
preservation failures identify that operation rather than implying that the
configuration bytes or parent directory are unwritable. Setup preserves the
original and reports its recovery backup; review local ownership and group
membership before retrying.
For an installed POSIX CLI, setup prefers an executable absolute `rea` alias
from its selected `PATH` when filesystem identity proves that it names the
running REA entrypoint. This keeps package-manager aliases intact instead of
persisting an internal versioned script path. Unresolved aliases and wrappers
that are different files do not establish that proof; setup retains its selected
entrypoint. A manager-specific upgrade remains subject to that manager's alias
policy.
+8 -35
View File
@@ -1,10 +1,5 @@
# Client and agent verification
FreeBuff MCP loading and personal skill discovery are currently unverified with
an installed native client/version. Its temporary-filesystem coverage verifies
REA's configuration and applicability boundaries only; it does not establish
that FreeBuff consumes the written registration or skill.
Use these optional lanes to verify an installed client or evaluate an agent's
REA workflow. [Testing REA](testing.md) provides the normal development commands
and provider lanes. Each record below states its host, client version, model
@@ -22,8 +17,8 @@ shared form would leave later fields unknown instead. The existing
`REA_MCP_INPUT_SCHEMA_PROFILE=compact` profile lets callers stay below
its character limit by advertising reduced nested validation structure, while
REA continues to validate calls against the complete canonical input schema.
Compact is the default for the server's advertised inputs; an explicit `full`
choice is preserved. See the [compact input schema profile](mcp-contracts.md#compact-input-schema-profile)
Compact applies to the server's advertised inputs and is not an automatic
full-profile fallback; see the [compact input schema profile](mcp-contracts.md#compact-input-schema-profile)
for details of its reduced forms.
Custom renderer hosts can raise `inputMaxChars` through `renderToolSignature` or
@@ -154,12 +149,6 @@ REA_VERIFY_COPILOT_MODEL=gpt-4.1 REA_VERIFY_COPILOT_SCHEMA_PROFILE=compact npm r
REA_VERIFY_COPILOT_MODEL=gpt-4.1 REA_VERIFY_COPILOT_SCHEMA_PROFILE=compact npm run verify:copilot-client -- call
```
The verifier explicitly writes either selected profile into its disposable
registration and compares the input schemas forwarded by Copilot with that
profile's advertised schemas. A run must not infer `full` from an unset setting:
REA's runtime default is now `compact`. The recorded client results above remain
scoped to their executed revisions; this verifier correction is not a new client run.
Both native workflows passed with the complete tool inventory. The verifier selects the
profile only in its disposable Copilot registration and records it in the
receipt. Compact advertisements can reduce nested validation structure; the
@@ -463,11 +452,6 @@ real local Codex CLI with:
npm run verify:agent
```
The verifier's scenarios, transcript scoring, fixture rubrics, and report gates
are development verification code under `tests/evaluation/model-evals/`. The
source-owned evaluator runs alongside the compiled REA tools; it is excluded
from the shipped runtime package.
The lane runs scenarios sequentially in standalone Codex (`--no-daemon`) with a
disposable HOME and CODEX_HOME under `~/.cache/rea-agent-evaluations/`. Set
`REA_AGENT_EVAL_ROOT` to choose another parent directory outside the OS temporary
@@ -491,16 +475,10 @@ is removed after confirmed process cleanup even with
`REA_AGENT_EVAL_KEEP_FIXTURES=true`. No user configuration,
rules, plugins or history is copied; shell snapshots and additional agents are
disabled. Existing API-key environment authentication remains available.
The native provider scenario preapproves only `open_binary` and
The native missing-provider scenario preapproves only `open_binary` and
`close_binary` in the disposable client so it reaches REA's actual provider
boundary instead of stopping at Codex approval. Its scenario contract accepts
one `provider_unavailable` open refusal without a close, or one successful open
followed by a successful close. Other errors, an unclosed session, and a close
without acquisition fail. These outcomes and the prompt are owned together in
`tests/evaluation/model-evals/AgentEvaluationScenarios.ts`; generic successful-tool sequences
still apply to scenarios that require them. The provider scenario checks routing,
reported limitations, and cleanup; free-form answer correctness remains
unassessed. It does not validate a real native engine or authorize runtime execution.
boundary instead of stopping at Codex approval. This does not validate a real
native engine or authorize arbitrary runtime execution.
Select a smaller run with `REA_AGENT_EVAL_SCENARIOS`, a comma-separated list of
scenario IDs. `asar-zh` exercises the packaged desktop rubric from a Chinese
@@ -513,10 +491,8 @@ limit for the whole process tree when testing on a constrained host; a Node
heap limit alone does not bound client and child-process memory.
`missing-target` leaves desktop fixtures visible without selecting one in the
request. It requires zero REA calls and a JSON-only request for an app name or
artifact path, with no preceding prose or Markdown fences. Its prompt and
whole-answer validator are defined together in `AgentEvaluationScenarios.ts`.
Its `expectedFirstTool` is `null`, and
request. It requires zero REA calls and a structured request for an app name or
artifact path. Its `expectedFirstTool` is `null`, and
`targetClarificationPassed` grades that limited routing outcome independently
of analysis prose heuristics. Analyzing a nearby example cannot pass this case.
@@ -578,10 +554,7 @@ tool results and final answers for review.
Report schema version 4 adds negative routing scenarios with nullable
`expectedFirstTool` and a `targetClarificationPassed` outcome. Consumers must
allow an expected absence of REA calls; this does not make an analysis scenario
pass without its required tools. Native provider records additionally report
`scenarioOutcome` (`provider-unavailable`, `opened-and-closed`, or `failed`) and
`scenarioOutcomePassed`; a failed outcome fails the gate independently of prose
heuristics. Version 3 changed the top-level
pass without its required tools. Version 3 changed the top-level
`factualCorrectness` from a constant
string to an assessment summary with status, scope, and assessed/passed/failed/
not-assessed scenario counts. Scenario records include the factual assessment and
-380
View File
@@ -1,380 +0,0 @@
# Analysis settings, limits, and recovery
This guide explains the runtime settings design for maintainers and developers:
who owns each value, when an update takes effect, how providers enforce it, and
how to extend the system. The public tool contract is in
[MCP contracts](../mcp-contracts.md#editable-analysis-settings); recorded runtime
checks and their limitations are in the
[verification report](../verification/runtime-analysis-settings.md).
## Problem and design boundary
A valid workload can exceed a conservative default. An oversized ASAR header
may prevent analysis of the entire archive; a slow Ghidra function may exceed
its decompile deadline. Requiring a new REA process to change either budget
interrupts the investigation and can discard reusable provider state.
REA exposes a curated set of analysis defaults through MCP so callers can
adjust a reported limit and retry the same workload in the running runtime.
Each setting has a typed value, resource bounds, an enforcement owner, and a
defined application point. The design applies across archive admission,
provider startup, operation deadlines, and worker memory requests.
The setter changes in-memory defaults. It does not edit arbitrary environment
variables, configuration files, MCP registrations, or provider installations.
Those mechanisms have different owners and lifetimes. It also cannot change a
client deadline, extend accepted work, release retained resources, or remove a
fixed representation limit. Exposing a setting requires a working enforcement
path, not just an entry in the tool schema.
Further ceilings that should become settings are tracked in
[#6176](https://github.com/morluto/rea/issues/6176). Naming honesty for limit
errors is [#5910](https://github.com/morluto/rea/issues/5910).
## Ownership and data flow
| Owner | Responsibility |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Domain definitions](https://github.com/morluto/rea/blob/main/src/domain/analysisSettingDefinitions.ts) and [types](https://github.com/morluto/rea/blob/main/src/domain/analysisSettings.ts) | Exact setting IDs, value types, shared bounds/defaults, units, provider/operation associations, application points, and structured limit metadata. Numeric constants stay with their domain owner. |
| [Launch parser](https://github.com/morluto/rea/blob/main/src/config/analysisSettings.ts) | Parse selected launch values once, preserving an invalid value as a typed configuration failure rather than substituting a default. |
| [AnalysisSettings](https://github.com/morluto/rea/blob/main/src/application/AnalysisSettings.ts) | Own one runtime's launch baseline, immutable override revisions, atomic updates, inspection, and accepted-operation snapshots. It does not own provider lifecycle or cleanup. |
| [Composition](https://github.com/morluto/rea/blob/main/src/main/eagerRuntime.ts) and [typed configuration views](https://github.com/morluto/rea/blob/main/src/config/runtimeAnalysisSettings.ts) | Inject the same settings reader into providers and workflows; translate overrides into the configuration each consumer understands. |
| Provider or resource owner | Resolve the effective budget, enforce it at the applicable boundary, and attach the actual value and known source to failures. |
| [MCP registration boundary](https://github.com/morluto/rea/blob/main/src/server/EvidenceMcpServerCore.ts) | Enter the operation's settings scope before invoking its handler. |
| [Settings contracts](https://github.com/morluto/rea/blob/main/src/contracts/analysisSettingsContracts.ts) and [tool registration](https://github.com/morluto/rea/blob/main/src/server/registerAnalysisSettingsTools.ts) | Validate named requests and deliver inspection/update results without requiring an open target or healthy analysis provider. |
The normal path is: composition creates the store, an MCP handler captures its
current revision, and providers read that snapshot when resolving budgets.
If an owner rejects work, it reports the effective limit through the shared
error projection. A subsequent settings update publishes a new revision for
later handlers. It does not reach into the failed operation or retry it.
### Accepted-operation snapshots
`AnalysisSettings.run()` installs the current immutable snapshot in
`AsyncLocalStorage`. Its `read` function returns that snapshot inside the
operation and the latest revision outside it. Providers depend on the reader
contract rather than on the mutable store or an ambient environment lookup.
Acceptance here is entry into the registered handler's settings scope, not
arrival of bytes at the MCP transport. Work queued or awaited within that scope
keeps its snapshot. Deferred server initialization must still dispatch through
the canonical scoped handler.
| Event | Revision used |
| --------------------------------------------------------- | ----------------------------------- |
| Operation A enters its handler, then waits for a provider | Current revision 0 |
| A settings update publishes revision 1 | Future handlers can read revision 1 |
| Operation A resumes and starts its worker | Its accepted revision 0 |
| Operation B enters its handler | Revision 1 |
This gives each operation stable budget semantics even if another connection
updates settings while it waits. `next_worker` and `next_startup` describe where
an accepted value is applied; they do not cause workers or startups belonging
to an older operation to adopt a newer revision.
Inspection deliberately reads the store's current defaults, rather than the
calling operation's snapshot. It reports `active_values.status: "not_observed"`:
an inspection result cannot establish the budget or remaining time of an
already-running worker.
### Composition and embedding
The MCP runtime creates one store and supplies its reader to provider factories.
`createServer` uses a supplied store or creates one, and wires its default
providers to that store. An embedder supplying a custom binary session or other
provider must also inject the same reader into that provider's composition and
pass the store to `createServer`. Merely exposing the settings tools cannot
make a custom provider consume updates.
Keep default factories, explicitly supplied providers, transport composition,
and deferred handler dispatch covered separately where their wiring differs.
Resolve settings at the operation boundary; copying a runtime value into a
provider constructor would freeze it at launch. For entrypoints outside the
MCP wrapper, an embedder needing the same snapshot guarantee must establish
the operation scope with `AnalysisSettings.run()`.
## Name a default, then a setting when operators need headroom
Put the number in a named constant with a unit. Do not leave it as a magic
number at the call site.
When a workload may need more room, add a setting whose unset value is that
constant. Pure numeric contracts live in `src/domain/analysisResourceLimits.ts`
or their existing domain owner. `analysisSettingDefinitions.ts` binds the
curated runtime IDs to those same bounds, defaults and application points.
Environment parsers and recovery text stay under `src/config/`; register launch
schemas on `environmentSchema` so startup and doctor agree. Add launch settings
to `SELECTED_REGISTRATION_SETTINGS` when setup should forward them. Unset keeps
today's behavior. An invalid launch value remains a configuration failure,
never a silent fallback; its validation boundary determines whether MCP can
start (see [invalid launch configuration](#invalid-launch-configuration)). An
invalid runtime write is `invalid_request` and changes nothing.
For example, `regex.matching_deadline_ms` and its launch seed
`REA_REGEX_MATCHING_DEADLINE_MS` share a 5000 ms default, 1 ms floor and
2147483647 ms maximum. Node schedules a longer `setTimeout` delay as 1 ms.
Changing the runtime setting affects the next accepted search; changing the
launch environment affects the next REA process.
[`REA_MCP_MAX_RESPONSE_BYTES`](https://github.com/morluto/rea/blob/main/src/config/mcpResponseBudget.ts)
is the byte-budget shape: a decimal safe integer with a 10 MiB floor
(`MINIMUM_RESPONSE_BYTES`), the same shared schema, and no silent fallback. A
change needs a REA restart and a matching client receive buffer. It does not
raise REA's reserved string limit or Node's string ceiling. The setup
allowlist does not copy this setting. A new headroom setting should, following
the regex deadline.
A per-call budget can have a runtime default without having an environment
setting. JavaScript `max_heap_mb`
(CLI `--max-heap-mb`) is an integer from 128 to 16384 MiB, default 1024. It
applies on the next analysis, which starts a new worker, and does not restart
REA. It does not raise the parent result budget, transfer ceilings, or fixed
parser limits.
Host memory and `NODE_OPTIONS` do not raise a REA admission unless that
admission's recovery says so. The JavaScript parent result budget is one that
does: its recovery says to set `NODE_OPTIONS` (for example
`--max-old-space-size=<MiB>`) on the process that starts REA, then restart
that process. `max_heap_mb` does not raise it.
## Runtime settings through MCP
`inspect_analysis_settings` lists the curated settings, their current and launch
values, source, bounds, application point and relevant operation names. Filters
by exact setting IDs, provider or operation intersect. Active worker values are explicitly `not_observed`; a configured default is
not a measurement of active work. Inspection also separates known server limits from unknown client deadlines and receive buffers.
`update_analysis_settings` accepts an atomic `changes` array. Each entry names
an exact `setting` and either `action: "set"` with a schema-validated `value`, or
`action: "reset"`. Reset restores the launch value. `expected_revision` is an
optional compare-and-set guard; conflicts and duplicate keys change nothing.
An unchanged write does not advance the revision.
The revision tracks override state, not only effective numeric values. Setting
an explicit override equal to the launch value still changes its source to
`runtime` and advances the revision. Reset removes that override, even if the
effective number stays the same. Validation, revision checks, and duplicate-key
checks all happen before publishing the replacement snapshot; a rejected batch
cannot partially apply. Clients coordinating writes can supply
`expected_revision`; without it, valid updates apply to the current state.
| Runtime setting | Application point |
| ------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `asar.max_header_bytes` | Next archive operation, including full JavaScript application analysis |
| `ghidra.startup_timeout_ms`, `hopper.startup_timeout_ms` | Next provider startup attempt |
| `ghidra.decompile_timeout_seconds` | Next operation on the existing Ghidra Program; `null` means unbounded |
| `jadx.operation_timeout_ms` | Next JADX operation |
| `regex.matching_deadline_ms`, `regex.worker_heap_request_mib` | Next search / new regex worker |
| `capture.decoder_old_generation_mib` | Next owned capture decoder |
| `javascript.analysis_timeout_ms`, `javascript.max_heap_mb` | Next source analysis / new JavaScript worker |
| `wasm.command_timeout_ms` | Next WABT operation |
| `native.matching_representation_bytes` | Next `match_native_functions` request |
### Precedence and lifetime
Where a setting supports an explicit per-call argument, precedence is:
1. The caller's explicit argument.
2. The accepted operation's runtime override.
3. Valid launch configuration, where that setting has an environment seed.
4. The built-in default when launch configuration is absent.
An invalid launch value does not fall through to the built-in default. Preserve
omitted caller arguments in Evidence: resolving an internal default must not
rewrite the request as though the caller supplied it. `null` is the explicit
unbounded value for `ghidra.decompile_timeout_seconds`; it is not a substitute
for an unknown or omitted value elsewhere.
A write does not extend active work, relaunch a provider, clear retained
cleanup, or invalidate successful Evidence.
Overrides belong to one REA runtime, survive target close/open, and disappear
when REA exits. Connections sharing that runtime share its overrides. No files,
ambient environment, registrations or upstream software are edited. Overrides
are runtime-wide, not scoped to a target or an individual connection.
### Failure and retry
An ASAR admission error supplies `details.limit` with the exact setting,
effective value, units, bounds, scope, application point, restart requirement
and, when known, required bytes and value source. The agent can immediately call:
```json
{
"changes": [
{ "setting": "asar.max_header_bytes", "action": "set", "value": 33554432 }
]
}
```
Then retry the same full-archive request once work and cleanup have settled.
No preliminary inspection or source subset is required. The setter does not
retry automatically. If an explicit per-call value caused the failure, correct
that argument; raising a runtime default cannot override it.
The resource owner constructs `details.limit` from the registry plus the budget
it actually enforced. Metadata includes `agent_editable: true`,
`scope: "rea-runtime"`, and `rea_restart_required: false`. Optional
`required_value`, `source`, and `parameter` fields are supplied only where known
or applicable. Do not infer a source because a value happens to equal a default.
Error copying, attribution, redaction, and delivery must preserve these facts.
Human-readable remediation explains the same failure; clients should not need
to parse that sentence to identify an editable setting.
Cache policy is part of enforcement. Successful reusable analysis can survive
a budget update. Transient Ghidra decompile failures remain uncached so a raised
deadline can recover on the same Program. Admission checks still apply to reused
data: lowering an ASAR limit must reject a cached oversized header. Neither
raising a limit nor clearing a transient analysis failure authorizes skipping
cleanup or changing the identity of the provider session.
### Invalid launch configuration
Launch validation has two relevant boundaries. Settings validated by global
configuration parsing can prevent REA from starting; an MCP setter cannot
repair a server that is not running. Correct the launch environment or
registration, use `rea doctor` for its configuration checks, and start REA again.
Provider-local validation can leave MCP available. For example, an invalid
`REA_JADX_OPERATION_TIMEOUT_MS` is inspectable with `source: "invalid"`, a
`configuration_error`, and omitted `value` and `launch_value`. A valid runtime
override repairs the effective JADX timeout without editing that launch seed.
Inspection then reports the runtime value while retaining the launch error.
Reset exposes the invalid launch state again; it does not repair or replace it
with the built-in default. Provider availability and execution must resolve the
same effective setting so readiness changes along with repair and reset.
`REA_NATIVE_MATCHING_REPRESENTATION_BYTES` is validated the same way, by
`match_native_functions` alone.
## Keep a fixed ceiling honest
Inspection's separate
[constraints](https://github.com/morluto/rea/blob/main/src/server/analysisSettingsConstraints.ts)
describe operator-owned, external, and fixed controls. REA can report its
configured response budget and parent heap limit; client deadlines and receive
buffers remain explicitly unknown. These are not entries accepted by the
runtime setter. Raising a worker heap request cannot raise the parent heap,
and raising a provider deadline cannot make the client wait longer.
If the ceiling must stay fixed, say so in the failure. Name the limit and the
effective value, say this release has no runtime override, and name what does
not raise it. Do not invent a setting.
JSON values stop at `MAX_JSON_DEPTH` (1000) in
[`jsonValue.ts`](https://github.com/morluto/rea/blob/main/src/domain/jsonValue.ts):
"JSON value exceeds maximum nesting depth of 1000. This release has no runtime
override."
## Recovery text
The string the caller sees includes:
- the limit or setting name
- the effective value and unit
- the source when known (`default`, `setting`, `runtime`, or `request`)
- the default, the maximum, and the floor when the setting has one
- the next action, including where to set it ("in the environment that starts REA")
- whether a restart is required
- what the change does not raise
Distinguish an invalid configuration from a valid budget exhausted by work.
Preserve the configuration failure and its setting identity; apply the
[launch validation boundary](#invalid-launch-configuration) when describing
whether live repair is possible.
## What agents do
Use `details.limit` for a direct runtime update when it names an editable
setting. Follow `remediation.action` for per-call, fixed and external controls.
Inspect settings only when bounds or application details are missing. Restart only when it says the value is
read at startup. A sentence that a control does not raise the limit is not a
request to change it.
- Dropped MCP connection: retry the 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. Do not
start with `rea doctor`
([#6353](https://github.com/morluto/rea/pull/6353)).
- REA could not start its connection: retry once. If it fails again, tell the
user and ask them to restart the MCP client, then run `rea doctor`. Do not
retry a startup that returns the same unchanged result: tell the user REA
could not start, ask them to restart the MCP client, then run `rea doctor`.
- Unexpected process-command failure: do not repeat the command. Tell the user
to check the input files, then run `rea doctor`.
- `cleanup_incomplete`: the operation is not retryable. Retry that exact
cleanup owner, named in `details.resources`, and wait until it confirms
release before admitting new work on those resources
([#6357](https://github.com/morluto/rea/pull/6357)).
- `resource_constraint` for capacity, when `retryable` is true: wait for the
active admission to finish, then retry within the reported limits. Do not
restart REA to free the slot.
- `configuration_invalid`: when MCP remains available and the failure belongs
to a curated provider-local setting, inspect and set a valid runtime override.
Otherwise correct the named setting in the environment that starts REA, then
start REA again. Run `rea doctor` for the
`runtime-configuration:` check. Re-run `rea setup` for that client when the
setting is on the registration allowlist, then reconnect so the new process
sees it.
- `invalid_request`: correct the reported fields from the current schema or
CLI help. Do not repeat the unchanged call.
A restart is required to adopt a changed launch environment or registration.
The curated runtime settings above require no REA restart. It is not the first step for a transient
MCP drop, a capacity wait, or retained cleanup.
## Adding a runtime setting
1. Identify the enforcing owner and the real resource constraint. Decide whether
callers need a per-call argument, a runtime default, a launch-only control,
or a fixed limit. A registry entry must not advertise authority REA lacks.
2. Define the value type and named numeric bounds with the domain owner. Add the
ID and metadata to `analysisSettings.ts` and `analysisSettingDefinitions.ts`.
Reuse those bounds in runtime validation, launch parsing, and error metadata.
Choose the application point and provider/operation associations from actual
consumers; a shared control need not apply to every provider.
3. If there is a launch seed, add its parser and appropriate configuration/doctor
validation. Decide whether an invalid value blocks the runtime or only the
provider. Add it to setup's `SELECTED_REGISTRATION_SETTINGS` when it should
be forwarded; runtime editability does not automatically propagate it.
4. Thread the shared reader through every applicable composition path. Resolve
the effective value within the accepted operation, honor explicit arguments,
and preserve their omission in Evidence. Keep availability checks consistent
with execution. A worker heap request must not be described as a measured
process memory limit.
5. Enforce the value at its resource boundary and construct structured limit
facts there. Preserve those facts through shared error projection and copies.
Check cache reuse, lowered admission, transient failures, and retained cleanup
against the same ownership rules.
6. Update the public contracts and affected guides, then regenerate catalogs
and packaged skill metadata using the repository commands. Keep externally
owned and fixed controls in the separate inspection constraints, not in the
editable registry. Validate both store semantics and a public recovery path.
## Tests and verification
Cover the unset default, accepted overrides, rejected floor/maximum values,
and invalid syntax through the owning parser and configuration boundary.
[`regexMatchingDeadline.test.ts`](https://github.com/morluto/rea/blob/main/src/config/regexMatchingDeadline.test.ts)
is one example. Assert error codes, setting IDs, values, units, provenance,
application points, and resulting behavior. Check that adapters deliver the
canonical recovery guidance, with independent assertions for its underlying
facts. Avoid freezing prose or deriving all expected values from the same
registry used by the implementation; either can hide the wrong failure.
See [test design](../testing.md#test-design-and-pruning) for the assertion policy.
Runtime checks cover atomic validation, stale writes, reset, runtime isolation,
accepted-operation snapshots, and explicit argument precedence. Public MCP
acceptance must reject an oversized ASAR, raise admission, and successfully
retry the same complete archive on the same connection. Lowering admission must
also refuse previously cached headers. The real Ghidra timeout verifier checks
recovery after a live update without replacing its session. Also cover invalid
provider-local launch configuration, repair/reset availability, default and
custom composition paths, and preservation of structured limits through error
copies. Gates used to overlap work and updates need bounded acknowledgements
and release/join in `finally`.
The [verification report](../verification/runtime-analysis-settings.md) separates
public MCP checks, simulated provider coverage, real Ghidra execution, and
measured task costs. It does not establish real-provider behavior for every
editable setting or active worker telemetry.
+6 -7
View File
@@ -97,18 +97,17 @@ same bundle. Export writes only the caller-selected output file.
| Unity Mono | A managed assembly below `*_Data/Managed` or `Contents/Resources/Data/Managed` has an exact UnityEngine assembly reference. |
| Unity IL2CPP | A recognized IL2CPP metadata magic (with the raw version when complete) coexists with a conventionally named native `GameAssembly.dll`, `GameAssembly.so`, `GameAssembly.dylib` or `libil2cpp.so` image. Build correspondence remains unknown. |
| Godot | A `GDPC` header at file start or a validated executable trailer offset. |
| GameMaker | A bounded `FORM` container starts with a `GEN8` chunk. The scan probes conventional `data.win`, `game.unx`, and `game.ios` data files for that header. |
| GameMaker | A bounded `FORM` container starts with a `GEN8` chunk. |
| Unreal | `Content/Paks` path conventions or an IoStore TOC signature; arbitrary `.pak` filenames are insufficient. |
| Electron | A `resources/app.asar` or `resources/default_app.asar` path convention; archive validity is unverified. |
When the four-byte metadata magic is present but version bytes are incomplete,
the fact retains `raw_version: null`; it does not offer an IL2CPP metadata
inspection probe. A complete magic/version prefix with supported raw version
24, 27, 29, 31 or 39 offers an `inspect_il2cpp_metadata` follow-up at its
admitted file path, including when no native library was found. This probe does
not establish full metadata validity or correspondence to a native binary;
version 24 inspection still applies only to supported header layouts. See
[IL2CPP metadata](il2cpp-metadata.md).
inspection probe. A complete magic/version prefix with supported raw version 29,
31 or 39 offers an
`inspect_il2cpp_metadata` follow-up at its admitted file path, including when no
native library was found. This probe does not establish full metadata validity
or correspondence to a native binary. See [IL2CPP metadata](il2cpp-metadata.md).
Candidates are derived or inferred from these facts and do not prove loading or
execution. BepInEx, MelonLoader, Easy Anti-Cheat and BattlEye labels identify
-105
View File
@@ -1,105 +0,0 @@
# Flattened devicetree inspection
`inspect_devicetree` / `rea inspect-devicetree` reads a local DTB without an
engine, target execution or external tools. It supports FDT versions 16 and 17;
other versions return `unsupported_target`. Corrupt recognized blobs return
`invalid_request`, and exhausted budgets return `resource_constraint` without
partial success.
```bash
rea inspect-devicetree /artifacts/board.dtb --json
rea inspect-devicetree /artifacts/board.dtb --detail summary --evidence_output /artifacts/board-evidence.json --json
```
The complete Evidence includes the artifact SHA-256, validated header fields,
64-bit memory reservations as exact hexadecimal strings, and a flat node tree
with explicit parent paths. Each property retains its file offset, byte length,
and up to 256 raw bytes as hex. `truncated` qualifies the preview only; parsing
coverage includes the entire declared tree. Version 16 has no declared structure
size, so `header.size_dt_struct` is null.
Derived fields carry `confidence: derived`: standard ASCII string lists, scalar
phandles and cell counts, and `reg` tuples using the parent's `#address-cells`
and `#size-cells` (defaults 2 and 1). Zero-cell components are null (omitted), distinct from an encoded zero. Cell
counts above four, invalid counts and incomplete tuples remain undecoded with a
limitation. Addresses are bus-local; no `ranges` translation is attempted.
Invalid or ambiguous strings remain raw, rather than being guessed.
The summary is labeled derived and includes root compatible/model, chosen
bootargs/stdout-path and aliases. An undecodable alias retains its name with a
null path; it is not silently treated as absent. Single-cell `interrupt-parent`, `remote-endpoint` and `msi-parent`
references resolve through phandle declarations. Missing or duplicate targets
have null target paths. A node whose `phandle` and `linux,phandle` disagree
adds a limitation, and references to either value stay unresolved. A present
`phandle` that is not one cell also adds a limitation; `linux,phandle` cannot
resolve that node when the canonical declaration is malformed. Binding-specific
reference arrays and overlays are not resolved; an empty reference list does not
prove absence of hardware links.
MCP `detail: summary` retains complete Evidence in the session. Follow its
`parent_evidence_id` with `inspect_analysis_view`:
```json
{
"source": { "kind": "retained-evidence", "evidence_id": "ev:<digest>" },
"view": {
"kind": "devicetree",
"collection": "properties",
"path": "/serial@1000",
"offset": 0,
"limit": 32
}
}
```
Collections are `nodes` (identity and property counts), `properties` (requires an
exact node path), `reservations`, and `references`. Pages use retained parsed
facts and do not reopen the file. Each page carries fixed-size context
(artifact, header, coverage and counts) rather than the summary strings, so a
large `bootargs` value does not repeat on every page. Source indices are scoped to the selected
collection and parent Evidence; property indices are scoped to their node.
| Input budget | Default | Ceiling |
| ---------------- | ------: | ------: |
| `max_file_bytes` | 4 MiB | 16 MiB |
| `max_nodes` | 8,192 | 65,536 |
| `max_properties` | 32,768 | 262,144 |
| `max_depth` | 64 | 256 |
Decoded names, paths, values and row overhead share a fixed 32 MiB representation
allowance, charged before expansion. It has no runtime override.
The reservation count also uses `max_properties`. CLI flags use kebab case,
for example `--max-nodes 16384`. View pages contain at most 256 rows.
## Verification
Synthetic tests cover v16/v17, malformed headers/blocks/names/tokens, resource
limits, derived values, and CLI/MCP delivery including retained pages after
removing the input. The optional real lane requires **dtc 1.8.1**:
```bash
npm run build:cached
node scripts/verify/firmware/devicetree.mjs
```
It compiles a DTS with reserved-memory, chosen, aliases and phandles, compares
REA's node paths against `dtc -I dtb -O dts`, and checks the derived report.
`REA_DTC_PATH` selects the verification executable; it is not runtime setup.
The dtc 1.8.1 lane ran on macOS arm64 with Node 26.5.0. Its seven node paths
matched the decompiled tree. The report included `model: "REA DTB verification"`,
`bootargs: "console=ttyS0"`, alias `serial0 -> /serial@1000`, reservation
`0x80000000 + 0x1000`, and a resolved reference from `/consumer` to
`/serial@1000`. The deliberately minimal remote-endpoint fixture produces dtc
graph-binding warnings; this lane verifies FDT structure and selected property
derivations, not hardware-binding validity.
A repeat run of that complete compile/decompile/CLI verification took 2.13 s
elapsed, 1.52 s user CPU and 0.27 s system CPU under `/usr/bin/time -l`. This is a
small fixture with warm filesystem caches, not a cold-start or large-board
benchmark. Peak process-family memory and whole-workflow token usage were not
measured; the sandbox denied the memory-reporting system query. No pre-feature
equivalent REA workflow exists for comparison. The pinned Node 24.22.1/npm
11.16.0 toolchain was unavailable; dependency installation used the local cache
with Node 26.5.0/npm 12.2.0 instead.
+11 -82
View File
@@ -29,20 +29,15 @@ files, including pages a packaged application serves from inside `app.asar`.
For those, the first `.asar` path component that is a regular file is the
archive, the remainder must name a regular file in its header, and the reported
path is the canonical archive path joined with that member. Remote hosts,
encoded separators in the pathname, ASAR directories or links, and nonexistent paths are
encoded path separators, ASAR directories or links, and nonexistent paths are
rejected. The endpoint exposes every eligible target and its local path
metadata. Query strings and fragments do not participate in filesystem path
admission, including their encoded separator text. Listing and inspection
Evidence both retain the observed browser product/revision; host platform and
architecture remain explicitly unknown.
metadata.
Cancellation stops further file authorization after an already-started filesystem
operation settles. ASAR descriptor-close failures remain typed incomplete cleanup
with an exact retryable owner; they are not treated as a missing or unauthorized
page. A later authorization and provider shutdown retry retained descriptors
before reporting their release. Failed listing retains its already-authorized
target prefix and explicit remaining-target uncertainty through cleanup errors
and exact-owner retries.
before reporting their release.
## Workflow
@@ -75,40 +70,10 @@ DOM node indices retain their snapshot positions. Invalid rows are omitted with
completeness diagnostics, so `total_nodes` can exceed the retained node count
and a parent index can refer to an omitted source node.
Script metadata retains its execution-context frame ID when CDP supplies one and
records admission separately in `frame_scope`. An `authorized` scope refers to
the observed document generation; `unknown` retains the known frame ID without
claiming authorization and makes script coverage attach-limited. Historical
captures may omit this field, which leaves that admission fact unknown.
Scripts from document generations whose frames were explicitly excluded by
local-file policy are omitted before script-source reads. Frame identity can
come from the script event's own auxiliary data or its observed execution
context; contradictory identities are not aligned.
`frame_claims.script_event_frame_id` and
`frame_claims.execution_context_frame_id` preserve the actual observed claims
from those two sources. Both remain available when their disagreement leaves
the resolved `frame_id` null and `frame_scope` unknown. An omitted claim is
unobserved, not proof that a frame was absent.
Frames describe the current inventory; script observations can retain earlier
authorized frames that have since detached.
`has_source_url` preserves CDP's sourceURL declaration flag as true, false, or
unknown (`null`, or omitted in historical captures). A script's canonical
`file_path` is the admitted local-file URL or supplied sourceURL label. It does
not verify that the runtime bytes came from that disk file; captured source
bytes have their own digest. `reported_url` retains the original URL or label
before canonical path admission. Labels remain distinct from filesystem provenance.
The capture also
Script metadata retains its execution-context frame ID when CDP supplies one. The capture also
inventories worker, service-worker, and shared-worker targets with
validated opener-target and parent-frame IDs. Worker discovery uses passive
target metadata; REA does not attach to or execute code in those targets.
Script metadata uses the shared 8 MiB JSON-byte allowance for each retained
representation: incoming drafts and normalized capture metadata. Admission
counts escaped strings, frame claims, and provenance fields before retaining
them or serializing metadata for a digest. Exhaustion preserves earlier records
and reports counted `resource_budget_exhausted` script omissions; a later
smaller record may still fit. This allowance adds no script-count cap and is
separate from script-source bytes.
Script-source collection retains at most 32 MiB of UTF-8 text across scripts
and shares a 20-second collection deadline. Omitted sources retain their script
metadata and an explicit reason; later smaller sources may still fit after an
@@ -119,11 +84,6 @@ target, frame, script, and resource, a worker URL must resolve to a local
file before it is retained. Relationship IDs improve
attribution but do not prove which static module started a worker or that its
work completed.
Missing opener and parent relationships leave worker ownership unknown. Such
targets are withheld with an `unattributed_target` exclusion and unavailable
`workers` coverage, even when their URL names a local file. Explicit foreign
relationships remain `out_of_target_scope`; malformed supplied relationship IDs
are `invalid_protocol_value`.
Inspection does not retain DOM values, execute renderer code, navigate, click,
invoke Electron IPC, close a target, or terminate the application.
@@ -140,8 +100,6 @@ window-targeted renderer reload/crash actions, and synthetic `open-url` or
WebContents identities, process metrics, and IPC channel and value-shape metadata.
Payload values are not retained.
`window_index` selects from Playwright's current Page list at the start of each action. REA resolves that Page to its corresponding BrowserWindow once; the action's `target` uses the BrowserWindow's capture-scoped `window_id`, matching the window inventory. Reordering either enumeration after selection does not retarget the action. Handle cleanup failures retain the completed action records and remain owned for provider cleanup retry.
Run the real fixture verifier with an operator-provided Electron runtime:
```bash
@@ -158,18 +116,14 @@ The CLI and MCP surfaces accept the same schema. For a JSON request file:
rea capture-electron-scenario scenario.json --json
```
The result records one status and target per declared action; actions after a
failed or cancelled action are not run and are recorded as `cancelled` with a
not-executed error. It also records correlated app/window/WebContents,
The result records action status and targets, correlated app/window/WebContents,
preload, session, navigation, shell, permission, popup, download, protocol,
native-addon, process, and IPC timeline events. IPC channels and argument-shape
metadata retain their observed lengths; payload values are never retained.
Renderer crash/restart and deep-link actions are synthetic
scenario controls; their attempted and observed outcomes remain in the
timeline. The active hook blocks and records the shell methods listed below
and selected navigation, permission, download, popup, updater, and
OS-integration APIs. These method patches do not contain direct filesystem,
network, native-addon, or other unpatched target effects. The timeline
timeline. The active hook blocks and records external shell/navigation,
permission, download, popup, updater, and OS-integration effects. The timeline
is explicitly partial when attachment starts after application activity. The
startup, including the initial state snapshot, has a 60-second deadline.
Actions wait until completion or request cancellation; a click whose target
@@ -178,14 +132,11 @@ window, remains pending until cancelled. After cancellation, the final state
read has 5 seconds before REA keeps the initial snapshot instead; the
cancellation keeps the caller's reason and records that expiry in its
diagnostics.
The shell policy is owned by `blockedShellResults` in
`scripts/electron-active-hook-boundaries.cjs`. Its synchronous blocks are:
Blocked synchronous shell APIs keep their return contracts:
`showItemInFolder` returns `undefined` and `writeShortcutLink` returns `false`.
Their `shell-attempt` events record the blocked phase and argument shapes;
neither result means the requested host operation occurred. The existing
`openExternal`, `openPath`, and `trashItem` blocks reject their promises.
The `trashItem` block prevents that call from reaching the OS trash operation;
it does not intercept other ways the application can remove files.
`openExternal` and `openPath` blocks continue to reject their promises.
CLI SIGINT/SIGTERM requests cancellation and awaits owned-process cleanup.
Window IDs, URLs, and cached titles come from one main-process Electron snapshot,
so a crashed renderer does not require a successful renderer-side title query.
@@ -199,15 +150,6 @@ If cleanup cannot be verified, the error retains collected observations in
`details.partial_observation` and marks their application cleanup `unverified`.
Cleanup uncertainty does not authorize signaling unrelated processes.
On Linux, Chromium-based processes can overwrite the initial environment memory
exposed through `/proc/<pid>/environ`. The environment may be readable while
`REA_PROCESS_RUN_ID` is absent, even if the application still sees it through
`process.env`. REA reports the missing marker and leaves lineage ownership and
cleanup unverified; the PID, executable, or parent relationship cannot replace
that ownership proof. This limitation can affect owned Electron captures on
native Linux as well as WSL. It does not establish that all Electron versions or
Linux environments behave the same way.
Main-process IPC recording keeps original-function removal for `on` and `once`
registrations and records a fired `once` callback once. Native Node.js owns
`once` registration, firing and callable dispatch as well as removal order and
@@ -274,9 +216,7 @@ initialization, the hook leaves listener callbacks unwrapped and records a
callback and receiver; callback execution remains unobserved. This comparison
assumes Node's EventEmitter prototype has not itself been globally decorated
before hook initialization. The provider loads the hook before the application
and excludes `NODE_OPTIONS` from the child environment. It preserves selected
`DISPLAY`, `XAUTHORITY` and `WAYLAND_DISPLAY` values, including the caller-selected
X11 authorization-file path.
and excludes `NODE_OPTIONS` from the child environment.
An opaque later listener or unsupported emitter state can prevent proof of
removal order. The hook then delegates removal
@@ -303,15 +243,4 @@ application path and capture outcome to the static graph, while frames, scripts,
workers, and execution claims remain unavailable until a separate passive runtime
capture provides them.
ASAR observation reads the header through one open file handle and validates its
declared lengths against the archive size. Header/index admission is the same
budget used by static ASAR reads: 16 MiB / 16777216 bytes by default, seeded by
`REA_ASAR_MAX_HEADER_BYTES` (at most 256 MiB). In MCP,
`update_analysis_settings` can override `asar.max_header_bytes` for new
operations without restarting REA. Cached headers are checked against each
operation's admission, including after a decrease. See the
[settings design](design/limits-and-recovery.md#failure-and-retry).
An oversized header is
not parsed; selecting a member path does not bypass admission, and the page
stays unauthorized. The header cache separately retains at most that many bytes
of already-admitted encoded headers across eight archives. Evicting that cache does not raise admission.
ASAR observation reads the header through one open file handle, validates its declared lengths against the archive size, and limits encoded JSON headers to 16 MiB before parsing. The header cache also retains at most 16 MiB of encoded headers across eight archives. Oversized headers produce a scope refusal.
-78
View File
@@ -1,78 +0,0 @@
# ELF note tables
`inspect_elf_notes` / `inspect-elf-notes` answers whether one selected local
ELF32/ELF64 image declares `PT_NOTE` program headers or `SHT_NOTE` sections,
which note owners and types those tables store, and which GNU build-id or
ABI-tag fields those notes carry. It reads the file without executing it and
does not require a disassembler, debug package, or package catalog.
```bash
rea inspect-elf-notes /absolute/path/to/program --json
```
The CLI resolves relative paths against the caller's working directory. MCP
requires an absolute filesystem path and preserves that exact selection:
```json
{
"name": "inspect_elf_notes",
"arguments": { "path": "/artifacts/program" }
}
```
## Observations
The inline Evidence result contains the artifact path, SHA-256, and byte length.
`tables` lists each declared note program header or section, including the
section name when a string table is present. Zlib- and Zstandard-compressed
section-name tables are decompressed within the selected file-size budget. An unsupported
compression type leaves section names unknown and adds a limitation; note bytes
remain independently examined. Each `entries[]` item reports owner, type,
descriptor SHA-256, and a hex preview for descriptors of 64 bytes or less.
A section name also stays null when its offset is outside the declared string
table or its bytes lack a NUL terminator. A limitation explains that missing
name. Note coverage remains complete when all declared note tables and entries
were examined; an unavailable section name does not erase those note facts.
For GNU notes, `gnu.build_id_hex`, `gnu.abi_tag`, or `gnu.gold_version` are
filled when the descriptor matches that type. Other owners keep `gnu` null.
`PT_NOTE` and `SHT_NOTE` are examined independently. The same file range may
appear twice when both tables declare it.
A missing table list is observed absence of those declarations. It is not proof
the image never carried notes in another container or overlay.
## Limits and failures
The selected file is limited to 64 MiB by default (512 MiB maximum). At most
32 note entries are examined by default across every table. Retained note
names, section names, and GNU descriptor text share a scan budget based on
the source bytes plus any decoded section-name table, capped at
`max_file_bytes`. The raw source and decoded table must also fit together
within that ceiling. If admission fails, the error reports the required
`max_file_bytes`; raising it can admit the decoded table up to the operation's
512 MiB maximum. Once admitted, raising that setting does not increase the
scan budget for the same source and decoded names. Repeated table ranges count again. Each descriptor range is hashed once,
and each GNU version's trailing NUL run is scanned once; neither walk may
exceed that byte ceiling. Malformed ELF headers, truncated header
tables, declared note tables that extend past the file, and truncated
note entries fail without partial success. ELF extended
numbering (`PN_XNUM`, including a section count stored when `e_shnum` is 0) is
unsupported and returns an unsupported-target outcome rather than invalid-input
recovery.
Zstandard section-name tables also use a 128 MiB decoder window limit.
`SHF_COMPRESSED` note sections return `unsupported_target` before their payload
is interpreted or decompressed. Their compression header is storage metadata,
not a note header. The current result requires physical file offsets for each
note and descriptor, which decoded compressed notes cannot provide. This refusal
does not label compressed note storage as malformed or allocate its declared
decoded size. Compressed section-name tables remain supported within the limits
above. Inspect a separately decompressed copy when these notes are needed,
retaining the original artifact for provenance.
This workflow does not execute the image or verify GNU build-id uniqueness
against package catalogs.
-133
View File
@@ -1,133 +0,0 @@
# Erlang BEAM files
`inspect_erlang_beam` / `inspect-erlang-beam` answers whether one selected
local file is a `FOR1`/`BEAM` IFF container, which chunks that form stores, and
which atom, export, import, lambda, and Code-header fields those chunks carry.
It reads the file without executing bytecode and does not require an Erlang
installation or BEAM emulator.
```bash
rea inspect-erlang-beam /absolute/path/to/app.beam --json
```
The CLI resolves relative paths against the caller's working directory. MCP
requires an absolute filesystem path and preserves that exact selection:
```json
{
"name": "inspect_erlang_beam",
"arguments": { "path": "/artifacts/app.beam" }
}
```
## Observations
The inline Evidence result contains the artifact path, SHA-256, and byte length.
`chunks` lists every IFF chunk name, file range, and payload SHA-256.
Repeated names retain separate chunk occurrences. Decoded tables and the Code
header use the last occurrence of each name, matching OTP's loader; the chunk
inventory still exposes earlier payloads and their source ranges.
The BEAM form size bounds the parsed chunks. Bytes after the declared form are
ignored by the parser but remain part of the selected artifact's byte length
and SHA-256.
When a selected `AtU8` or `Atom` table is decoded, `atoms` is the 1-based name table and
`module` is atom 1. Both the pre-OTP 28 table and the OTP 28+ compact table
(negated count, compact-encoded name lengths) are read. `exports`, `imports`, and `lambdas` resolve atom indices to
those names when the table is present; unresolved indices keep `function` or
`module` null.
`code` reports the Code chunk instruction-set, opcode maximum, label count,
function count, and the instruction stream's file range. Missing optional
tables are null, not empty-everywhere.
A zero-byte `AtU8` falls back to an available legacy `Atom` table, following
[OTP 20.3's atom selection](https://github.com/erlang/otp/blob/a113f6117fd696ea6f84ed754055b4ec97a7ccb2/erts/emulator/beam/beam_load.c).
A nonempty `AtU8` retains UTF-8 precedence, including an explicit zero atom
count; malformed nonempty data fails instead of falling back. An empty atom
chunk without an alternate table still fails its required count header.
Absent and zero-byte optional `FunT` chunks produce `lambdas: null`, while a
four-byte zero row count produces `lambdas: []`. This follows
[OTP 29's positive-size lambda parsing rule](https://github.com/erlang/otp/blob/550d7b7898706c7822362c42e7b93120c1d1f29a/erts/emulator/beam/beam_file.c).
Empty payloads retain their ranges and SHA-256 in the chunk inventory. These
rules apply to the last occurrence of each chunk name; an empty final occurrence
does not revive an earlier payload of that name. Nonempty truncated tables remain
format failures.
## Select retained facts
Complete output remains the default. Request a summary with a `beam` view to
read a few facts in the first call while retaining the complete inspection:
```json
{
"name": "inspect_erlang_beam",
"arguments": {
"path": "/artifacts/app.beam",
"detail": "summary",
"view": {
"kind": "beam",
"collection": "exports",
"names": ["module_info"],
"fields": ["function_index", "function", "arity", "label"],
"limit": 20
}
}
}
```
`normalized_result.page.items` contains the matching rows. `source_index` is
zero-based in the original collection and scoped to `parent_evidence_id` and
that collection. It does not replace the original one-based atom `index` or a
lambda's own `index`. Every matching function name survives, including different
arities. Names are exact retained display strings; raw indexes remain available
when names are unresolved or displayed names collide.
Collections are `atoms`, `exports`, `imports`, `lambdas`, and `chunks`. `names`
selects atom/chunk names or function names; imports additionally support exact
`modules`. Filters intersect. `indices` selects original collection indexes;
`fields` selects that collection's own fields. Empty selector arrays match
nothing, and `fields: []` returns only `source_index`. Offset/limit page matching
rows in original order (defaults 0/64). Unfiltered pages slice the retained
collection; queries do not decode the file again.
For `chunks`, `names` matches the four-character IFF chunk tag (for example
`"AtU8"`, `"Code"`, `"ExpT"`); available `fields` are `index`, `name`,
`location`, and `payload`. For `lambdas`, `names` matches the resolved function
name from the atom table; `null` names (unresolved atom indices) are never
selected by a name filter. For `atoms`, `names` matches the decoded atom name
string.
`beam` keeps the module, form/Code bounds, original source coverage, and table
counts. A null count means no table was decoded; zero means a decoded empty
table. Missing tables and unresolved atom names remain explicit, and an empty
page does not establish runtime absence. Page coverage is separate from the
source's examined chunks and parser budgets.
CLI summary delivery requires a destination for the complete Evidence bundle:
```sh
rea inspect-erlang-beam /artifacts/app.beam --detail summary \
--view '{"kind":"beam","collection":"exports","names":["module_info"],"fields":["function","arity"]}' \
--evidence_output /reports/app-beam.evidence.json --json
rea inspect-analysis-view '{"source":{"kind":"evidence-file","path":"/reports/app-beam.evidence.json"},"view":{"kind":"beam","collection":"imports","modules":["erlang"]}}' --json
```
The bundle retains the complete report. Subsequent MCP queries may use
`source: {kind: "retained-evidence", evidence_id: parent_evidence_id}` or an
Evidence file. Retained and offline views still work after the original BEAM
file is deleted. Supplying `view` requires `detail: "summary"`; omitting it
returns only the structural summary. Selection changes delivery, not parsing or
coverage, and executes no bytecode.
## Limits and failures
The selected file is limited to 64 MiB by default (512 MiB maximum). At most
64 chunks and 4096 atoms are examined by default, and at most 65536 export,
import, and lambda rows together. Malformed `FOR1`/`BEAM` headers, truncated
chunks, and `AtU8` names that are not valid UTF-8 fail without partial success.
This workflow does not execute bytecode, inflate `LitT` literals, or decode
`Attr`/`CInf` external-term payloads. Complete coverage describes this static
chunk and table report; it does not establish that an Erlang runtime can load
or execute the module.
+6 -19
View File
@@ -19,10 +19,8 @@ rea inspect-evm-interface ./runtime.bin raw --json
Initial real verification covers flat legacy bytecode on Linux x64 and unchanged
bundled EVMole 0.9.3. EOF-style EF00 containers return `unsupported_target`,
with the selected path and format reason. Selected-path resolution errors
`ENOENT`, `ENOTDIR`, `ELOOP`, and `ENAMETOOLONG` are `invalid_request` path
issues. Permission, resource, cancellation, provider, and cleanup failures retain
their distinct classifications.
with the selected path and format reason. Provider/configuration failures retain
their distinct unavailable-provider or capability classification.
The host must already provide util-linux `prlimit`, normally `/usr/bin/prlimit`;
`REA_EVM_PRLIMIT_COMMAND` can select another absolute executable. REA installs
no system tools and changes no user configuration. [Upstream provenance and
@@ -45,8 +43,7 @@ license](https://github.com/morluto/rea/blob/main/third_party/evmole/README.md)
map from the selected bytes. Integers use decimal strings; byte strings use hex,
and scalar keys retain their types. The recorded offset/length addresses the
original bytecode, so summary delivery does not copy an opaque CBOR payload.
Nested containers have partial scalar coverage; map keys are still checked for
duplicates throughout nested maps and arrays. Unsupported or malformed
Nested containers have partial scalar coverage; unsupported or malformed
candidates report `not-decoded` with a diagnostic. Decoding is bounded to 64
container levels and the terminal payload's encoded size.
- An unavailable trailer does not prove metadata absence or Solidity origin.
@@ -108,19 +105,9 @@ CLI keeps its existing complete result.
## Resource and lifecycle limits
Inputs are limited to 4 MiB, complete replies to 16 MiB and combined diagnostic
output to 1 MiB. An owned worker has a 256 MiB JavaScript heap; its profile
maxima are 3 GiB virtual address space and 30 CPU seconds, with tighter inherited
limits retained. The worker command deadline is
`timeout_ms` (default 30000 ms, integer 1 through 2147483647) on the next call,
or `--timeout-ms` on `rea inspect-evm-interface`. Omission keeps 30000 ms.
Those byte, address-space, and CPU ceilings have no runtime override, and
`timeout_ms` does not raise them. An oversized selected carrier returns a
non-retryable `resource_constraint` with resource `file-size` and its fixed
input allowance in `reported_limits.maximum_input_bytes`.
Complete-reply allowance refusals use the same non-retryable resource family,
with `reported_limits.maximum_reply_bytes`; malformed JSON remains unreadable
output. This refusal leaves no complete report to retrieve through another
delivery channel. Virtual address space differs from resident memory. The worker retains inherited tighter
output to 1 MiB. An owned worker has a 256 MiB JavaScript heap, 3 GiB virtual
address-space limit, 30 CPU seconds and a 30-second command deadline. Virtual
address space differs from resident memory. The worker retains inherited tighter
soft/hard limits, reports its configured soft limits inline and leaves hard limits
unchanged. WASM trap reservations are disabled
so the engine can run within that virtual limit; actual memory is not equated to
+6 -75
View File
@@ -27,10 +27,7 @@ checks the tool's `--version` banner when executing a request. Binwalk must
print `binwalk <version>` and Unblob must print the version alone. Inspection and
extraction have independent prerequisites; one does not require the other.
Both require util-linux `prlimit`, normally `/usr/bin/prlimit`; override its
absolute path with `REA_FIRMWARE_PRLIMIT_COMMAND` if needed. `rea doctor` does
not currently inspect firmware readiness. Engine selection/version checks run
when the firmware operation is requested; successful general doctor checks do
not establish firmware tool or external-extractor availability.
absolute path with `REA_FIRMWARE_PRLIMIT_COMMAND` if needed.
REA retains at most 8 MiB of combined stdout and stderr per command. Exceeding
that diagnostic budget stops analysis with an explicit truncated-output error;
@@ -53,36 +50,6 @@ git submodule update --init third_party/binwalk third_party/unblob
Normal REA builds and npm packages do not include or build these sources or
engine binaries. See [upstream provenance](https://github.com/morluto/rea/blob/main/third_party/README.md).
### Windows submodule checkout
On Windows, the Unblob checkout stops with `error: invalid path`. Several
upstream test fixture names under `tests/integration/` contain characters,
trailing dots, or trailing spaces that NTFS does not allow; REA does not use
those fixtures. Keep the clone, exclude that directory, and retry from the
repository root in PowerShell:
```powershell
git -C third_party/unblob config core.protectNTFS false
git -C third_party/unblob sparse-checkout set --no-cone '/*' '!/tests/integration/'
git submodule update third_party/unblob
```
Both settings are required. While `core.protectNTFS` is enabled, Git for
Windows also rejects index paths that the sparse pattern excludes; the pattern
keeps the invalid names out of the worktree. The setting applies only to the
Unblob submodule. In Git Bash, prefix the `sparse-checkout` command with
`MSYS_NO_PATHCONV=1`; otherwise `!/tests/integration/` is rewritten as a
Windows path.
The extraction destination must be absent with an existing writable parent.
REA checks that selection before starting an engine and still acquires it
exclusively during publication. Invalid sources and destinations identify the
caller argument; failures inside an acquired workspace keep their own cause.
Published file `relative_path` preserves producer spelling for lineage; `path`
uses the writer's actual normalized filename. An extracted filename that cannot
be represented losslessly as UTF-8 fails publication explicitly with its raw
name bytes in hexadecimal instead of silently omitting the entry.
## CLI and MCP
```sh
@@ -135,19 +102,14 @@ build's source revision, so that field remains unknown.
selected input have a known `root_file_range`, adjusted by the selected offset.
- Decompressed children have unknown original byte ranges and runtime addresses.
Task lineage does not establish a byte-for-byte mapping through decompression.
- `partial` marks unknown/encrypted chunks, selected input bytes outside all
reported chunk intervals, reported extraction problems,
- `partial` marks unknown/encrypted chunks, reported extraction problems,
unpublished links/special files or reached depth limits. `complete` means the
selected input is covered by reported chunk intervals and the engine finished
its configured extraction without those reported conditions;
engine finished its configured extraction without those reported conditions;
default skip rules and format coverage still apply. It does not establish
complete firmware reconstruction.
- `extraction_gaps` makes those conditions recoverable: a row identifies an
unextracted region or a failed task/stage. A chunk retains its identity and
byte range, plus its original-file range when it belongs to the selected input.
`unclassified-range` rows identify selected input intervals with no reported
chunk; their provider identity and handler stay null. An empty chunk inventory
therefore cannot establish complete extraction of a nonempty selected input.
A multi-file stage retains its reported ID and handler with unknown byte ranges;
a standalone task failure keeps unavailable stage identity null. Rows retain exact dependency names from
the producer's missing-dependency reports; an accepted report with no names
@@ -156,11 +118,6 @@ build's source revision, so that field remains unknown.
share recovery prose without losing any region identity. Instructions use
already configured capabilities where one exists. Every extraction rerun
requires a fresh absent `output_directory`, preserving the prior evidence.
Unblob's declared `ERROR` severity establishes an extraction failure,
including standalone `UnknownError` task exceptions. Warnings and resolved
extraction problems remain diagnostics with partial coverage; they do not
establish failed extractor stages. Standalone errors do not acquire a nearby
chunk's handler or byte range.
Nested unknown chunks use `inspect_firmware_regions` on the actual published
child rather than repeating the same extraction with unchanged handlers.
Inspection requires separately configured Binwalk (`REA_BINWALK_COMMAND`);
@@ -201,13 +158,7 @@ tree semantics, vulnerability scanning, emulation or hardware runtime capture.
Supply a stable copy within the allowance. No in-place source modification.
- One queued heavy operation per provider instance; Binwalk one thread and
Unblob one worker. No automatic parallel extraction engines.
- Analysis defaults to **120 seconds**, from tool resolution and input snapshot
through inspection and its final private-workspace cleanup, or extraction
through report consumption and inventory, excluding queue waiting and retained
cleanup admission. Inspection has no publication phase. Select `analysis_timeout_ms` in either MCP operation or
`--analysis-timeout-ms` in either CLI command for slower analysis. Current
results retain the actual budget; an absent historical value means unknown.
Extraction publication and its final
- **120-second** analysis deadline through inventory. Publication and final
cleanup start a separate **120-second** budget; select
`publication_timeout_ms` in MCP or `--publication-timeout-ms` in CLI for a slow
destination. Valid budgets are integers from 1 through 2,147,483,647 ms (the
@@ -217,30 +168,17 @@ tree semantics, vulnerability scanning, emulation or hardware runtime capture.
both phases and final cleanup. Phase handoff, publication commit and final
completion also check monotonic elapsed time, so delayed timer callbacks
cannot revive an expired budget.
- Analysis timeout recovery names the selected budget and the analysis control,
not the later publication control. Keep the client call timeout above analysis,
publication and cleanup headroom; keep the input/range/question unchanged.
Resolve any reported incomplete cleanup before retrying. Raising wall time
does not raise fixed CPU, memory, report, diagnostic or input limits.
- Inherited **1 GiB per-process address-space** limit,
120 CPU seconds and zero core-dump allowance through `prlimit`.
- JSON reports: at most **8 MiB** and valid UTF-8; malformed encoding and
oversized reports fail without repairing or truncating producer data.
Exit status 0 is not an empty result: a missing, empty, malformed or
oversized report fails with `unreadable_output`, its `report_state`, the
command, exit status and captured output. REA does not retry automatically.
Diagnostic output is bounded with exact captured byte counts.
- Default staging budget: **256 MiB** and **10,000 entries**, configurable using
`max_output_bytes` / `max_output_files` (CLI `--max-output-bytes` /
`--max-output-files`). Exceeding either selected staging budget returns a
nonretryable resource constraint naming the selected limit. Depth defaults to
3, allowed range 1–10.
`--max-output-files`). Depth defaults to 3, allowed range 1–10.
Staging budgets are polled and rechecked after exit; short-lived overshoot is
possible. The per-file process limit is the larger of the selected payload budget
and the fixed report/diagnostic file allowances, so a small payload selection
still permits the mandatory report. A tighter inherited host limit still applies.
The inherited file-size limit bounds each file, not total storage;
possible. The inherited file-size limit bounds each file, not total storage;
address-space limits are per process, not an aggregate memory quota. Worker
count is not CPU affinity for every external extractor. These limits do not
claim cgroup/container containment. Cancellation cleans the owned process group
@@ -285,10 +223,3 @@ REA_FIRMWARE_VERIFY_GHIDRA=1 npm run verify:firmware
`scripts/rea.mjs` to verify the packaged CLI/MCP workflow. Real coverage here is
generated gzip/USTAR/ext4 with a Linux x64 ELF child; SquashFS, UBIFS, proprietary
firmware, other hosts and architectures remain unverified.
## Inspect extracted devicetrees
Use [`inspect_devicetree`](devicetree.md) / `rea inspect-devicetree` on an
extracted DTB for its node/property tree, reservations, hardware summary and
selected phandle relationships. FDT v16/v17 inspection is portable and requires
no external tool or disassembler.
+3 -24
View File
@@ -62,12 +62,6 @@ server; older servers keep the complete request after schema discovery.
[Dart 2.14 layout](https://github.com/dart-lang/sdk/blob/2.14.0/runtime/vm/snapshot.h).
Kind numbers vary across Dart releases; other values are preserved with
partial coverage, without guessing their interpretation.
Unified `_kDartSnapshotData` / `_kDartSnapshotText` exports are a different
unsupported profile. The [upstream VM-isolate removal](https://github.com/dart-lang/sdk/commit/d36adbacafee01d9852cf8e960cf2d8654d91e7e)
changes both the source layout and the FullAOT kind value. REA reports the
observed export names and library identity with a typed unsupported-profile
refusal when only unified exports are available; it does not alias one header
into two independent sources.
Address resolution examines every overlapping `PT_LOAD` interpretation,
including partial overlaps. Equivalent mappings are coalesced; conflicting
mappings leave affected symbols unresolved and qualify coverage. Dynamic
@@ -100,8 +94,6 @@ server; older servers keep the complete request after schema discovery.
operation, and when they **disagree** the distinct values are
kept in `snapshot_hash_candidates` with `snapshot_hash: null` and
`coverage: "partial"` rather than silently picking one.
Identification can retain a single byte-scanned hash from a unified payload;
its partial status does not establish truncation or support for that profile.
- **`elf_header_valid`** checks the supported little-endian ELF32/ELF64
header, table entry widths, and full table extents against the library's
actual byte length. Truncated, malformed, or unsupported headers report
@@ -116,12 +108,7 @@ server; older servers keep the complete request after schema discovery.
`build_id_candidates` preserves distinct IDs. `build_id` is populated only
when all declared note ranges were examined and one ID was found;
conflicting IDs or malformed/bounded note metadata make coverage partial.
Nonempty GNU build-ID descriptors up to 64 bytes are retained as lowercase
hexadecimal, including IDs shorter than four bytes.
Matching bytes outside declared note ranges cannot supply a build ID.
Compressed `SHT_NOTE` sections are skipped with an unsupported-compression
limitation and partial coverage; IDs from other note ranges remain candidates.
Encoded note storage is never interpreted as note headers or inflated.
- Labeled strings must fit their complete grammar. `dart_version_capped`
and `toolchain_capped` independently report labels omitted by grammar or
projection bounds; either makes identification coverage partial. Values
@@ -147,9 +134,8 @@ server; older servers keep the complete request after schema discovery.
- **Bounded reads.** APK snapshots are bounded at 2 GiB, including while
copying. Identification permits 768 MiB of cumulative library extraction;
declared sizes are checked before extraction. Library reads are bounded at 256 MiB each and ZIP
metadata at 8 MiB. Before reading, the AOT image buffer reserves the
admitted declared size plus one byte; the reported byte count is the number
actually read. Exceeded budgets report
metadata at 8 MiB; the AOT image buffer is allocated at exactly the
declared size before reading. Exceeded budgets report
`resource_constraint`.
- **Engine notes.** Engine libraries are spooled into the owned root. ELF
headers, note tables and note payloads share an 8 MiB metadata budget;
@@ -166,16 +152,9 @@ server; older servers keep the complete request after schema discovery.
- **Selected ABI.** AOT inspection applies the library budget to the selected
ABI after selection; an oversized unselected library does not block it.
Undefined dynamic-symbol imports are not treated as snapshot exports.
A retained section directory without `SHT_DYNSYM` uses the same bounded
`PT_DYNAMIC` symbol description as a section-stripped image. Malformed
present symbol-table sections remain failures.
Section-stripped images require a bounded symbol count from `DT_HASH` or
`DT_GNU_HASH`; layouts without either are refused. Symbol names and their
terminators must fit their linked string table. Only dynamic entries used for
symbol discovery are interpreted as numeric bounds. ABI-ignored payloads,
including [`DT_BIND_NOW`](https://gabi.xinuos.com/elf/08-dynamic.html#dynamic-section),
do not change discovery; their stored bytes still contribute to the selected
artifact's digest.
terminators must fit their linked string table.
- REA performs **no hash-to-SDK lookup**: mapping the snapshot hash to a
Dart SDK release requires an external dataset and is deliberately out of
scope for this family.
+1 -3
View File
@@ -6,8 +6,6 @@ For remote devices, pass a host with an optional port (`host[:port]`; bracket IP
MCP exposes `list_frida_devices`, `list_frida_processes`, `start_frida_session`, `load_frida_script`, `resume_frida_session`, `unload_frida_script`, `frida_session_status`, and `close_frida_session`. A spawned process starts paused so scripts can load first. Closing unloads session scripts and detaches. If a spawn remains paused, REA resumes it only after script cleanup or session detach confirms the instrumentation is released; if both cleanup paths fail, the target stays paused and the cleanup error is retained. REA never kills the target. MCP sessions end with the server process. The one-shot CLI command `instrument-with-frida` accepts one inline script or script file, observes messages for a bounded duration, and returns Evidence with the source hash and captured messages.
`instrument-with-frida --json` returns the Evidence record directly, matching MCP. Consumers of the earlier `{evidence, cleanup_error}` CLI wrapper should read the record at the JSON root and the session cleanup observation at `normalized_result.cleanup_error`. If final provider cleanup fails or cancellation arrives during it, the typed error retains completed Evidence in `details.partial_observation`. Discovery commands retain their device or process report in the same field.
For state and capture-coverage checks, pass `{"session_id":"SESSION_ID","detail":"summary"}` to `frida_session_status`. Each call reads the observed session state and retains the complete observation. The reply is an `inspect_analysis_view` Evidence summary with the session identity, state, scripts, `messages_retained`, and `messages_truncated`; message payloads remain in its `parent_evidence_id`. The count covers retained queued messages, not every target event. Omitted `detail` or `detail=complete` preserves the existing complete reply.
A summary's selected fields look like this (excerpt):
@@ -26,6 +24,6 @@ Use `get_evidence_bundle` with `detail="summary"` and `filters={"evidence_id":"P
Failed startup cleanup retains acquired Frida handles, including a paused spawn that has not attached, for later cleanup retries. Its remote connection stays leased until session detach and target resume complete. CLI commands await provider cleanup before returning; MCP retains these resources for cleanup when its server closes. Server shutdown stops admitting new Frida operations and waits for admitted startup, discovery, and queued work before releasing their resources. A failed cleanup remains reported and can be retried by closing the server again, rather than treating an unregistered session as fully released.
Frida scripts execute inside the selected target with that process's memory, filesystem, and network authority. Local device lookup waits 1000 ms. This release has no runtime override for that lookup; another REA timeout does not raise it. Remote endpoints do not use that lookup. Message capture is bounded to 16 MiB and reports truncation. Metadata, type, and text field caps, and the JSON node cap, are also fixed. Evidence names a cap when that observation actually dropped data. This release has no runtime override for those capture caps. Script source is not stored in Evidence; its selected path and SHA-256 digest are. For a file, `source_sha256` identifies the original selected bytes. Invalid UTF-8 continues to use replacement decoding; Evidence then records that limitation and `raw_result.source_representation` with the encoding, decoding mode, and `loaded_source_sha256` of the UTF-8 string passed to Frida. Valid UTF-8 files and inline scripts do not need this additional representation fact. Evidence retains non-authentication target selection such as device or endpoint, mode, program or PID, and spawn arguments. Authentication values remain excluded. Results report the observed session and messages, while unsupported platform metadata remains unknown. A request cancelled before a Frida operation starts has no provider effects. If cancellation arrives during a native Frida call, REA waits for that call to return, then closes any session it created and returns the observed session as a partial observation. A cancelled script load on an existing session releases only its new script before queued operations can run; the session and previously loaded scripts remain available. If cancellation is observed when the native call returns, partial Evidence retains the source identity and captured messages even if that call rejects, including messages emitted during awaited cleanup. The caller's cancellation reason remains primary and a native rejection is retained as a sanitized diagnostic. A failure already observed before cleanup stays primary if cancellation arrives during cleanup. The cancellation cutoff is REA's observation of native completion, rather than a claim about unobservable native event order. `script_id` is REA's load-attempt identity. When creation rejects without returning a handle, `script_cleanup: "no_handle"` means REA acquired no handle to unload; it does not prove that the engine performed no work. Once the provider commits a successful load, later request cancellation does not unload that script; use `unload_frida_script` to release it. If script cleanup fails, the script remains owned and a paused target cannot resume until the pending instrumentation is released. Queued script loads, resumes, and unloads check cancellation before dispatching native effects. Cancellation Evidence separately reports whether a target resume was observed, whether instrumentation cleanup completed, and that final target liveness remains unknown. REA does not kill the target. Remote discovery retains its observations and reports a separate cleanup error if the temporary connection cannot be removed.
Frida scripts execute inside the selected target with that process's memory, filesystem, and network authority. Message capture is bounded to 16 MiB and reports truncation. Script source is not stored in Evidence; its selected path and SHA-256 digest are. For a file, `source_sha256` identifies the original selected bytes. Invalid UTF-8 continues to use replacement decoding; Evidence then records that limitation and `raw_result.source_representation` with the encoding, decoding mode, and `loaded_source_sha256` of the UTF-8 string passed to Frida. Valid UTF-8 files and inline scripts do not need this additional representation fact. Evidence retains non-authentication target selection such as device or endpoint, mode, program or PID, and spawn arguments. Authentication values remain excluded. Results report the observed session and messages, while unsupported platform metadata remains unknown. A request cancelled before a Frida operation starts has no provider effects. If cancellation arrives during a native Frida call, REA waits for that call to return, then closes any session it created and returns the observed session as a partial observation. A cancelled script load on an existing session releases only its new script before queued operations can run; the session and previously loaded scripts remain available. If cancellation is observed when the native call returns, partial Evidence retains the source identity and captured messages even if that call rejects, including messages emitted during awaited cleanup. The caller's cancellation reason remains primary and a native rejection is retained as a sanitized diagnostic. A failure already observed before cleanup stays primary if cancellation arrives during cleanup. The cancellation cutoff is REA's observation of native completion, rather than a claim about unobservable native event order. `script_id` is REA's load-attempt identity. When creation rejects without returning a handle, `script_cleanup: "no_handle"` means REA acquired no handle to unload; it does not prove that the engine performed no work. Once the provider commits a successful load, later request cancellation does not unload that script; use `unload_frida_script` to release it. If script cleanup fails, the script remains owned and a paused target cannot resume until the pending instrumentation is released. Queued script loads, resumes, and unloads check cancellation before dispatching native effects. Cancellation Evidence separately reports whether a target resume was observed, whether instrumentation cleanup completed, and that final target liveness remains unknown. REA does not kill the target. Remote discovery retains its observations and reports a separate cleanup error if the temporary connection cannot be removed.
The [Frida Core API](https://frida.re/docs/frida-core-api/) documents device management, sessions, and script loading; the [JavaScript API](https://frida.re/docs/javascript-api/) documents target-side instrumentation. [Luma](https://luma.frida.re/) is Frida's standalone GUI; REA does not automate or control it. `npm run verify:frida` requires a Linux host with the optional Frida binding and uses a short-lived local Node.js fixture created by the verifier. Add `-- --address HOST:PORT --pid PID` to run the separate remote attach lane against a caller-selected target. Mock tests do not establish real device/server compatibility.
-129
View File
@@ -1,129 +0,0 @@
# GameCube DOL analysis through Ghidra
This lane imports 32-bit big-endian PowerPC GameCube/Wii DOL images through
Ghidra. It does not execute the image. DOL is a headerless format with no magic
number, so REA never detects it: the caller must select it explicitly.
Only the cached MEM1 window `0x80000000..0x81800000` is admitted. Wii MEM2
and uncached aliases are outside this lane.
Not covered: small-data anchors (`r2`/`r13`), REL, RPX, and IDA or Hopper
support. See [Not supported yet](#not-supported-yet).
## Select the format
CLI:
```bash
rea inspect /absolute/path/to/main.dol --target-format gamecube-dol --provider ghidra --json
rea function /absolute/path/to/main.dol 0x80003130 --target-format gamecube-dol --provider ghidra --json
```
MCP, with the existing `open_binary` tool:
```json
{
"path": "/absolute/path/to/main.dol",
"provider_id": "ghidra",
"format": "gamecube-dol"
}
```
The architecture comes from the DOL header (`powerpc`). Do not pass
`architecture` with `gamecube-dol`; it is rejected. `entry_point` is optional,
and if given it must equal the header entry.
Omitting `format` never imports a file as a DOL. Detection does not run for DOL.
## What the import does
REA reads the 256-byte DOL header at admission. It checks every section's file
range and load address. It records BSS/data overlaps and computes the exact
uninitialized BSS gaps once. Malformed headers are refused before Ghidra starts.
Ghidra then runs in two steps:
1. The standard `BinaryLoader` maps the whole file at base `0x0`, with
`-processor` and `-cspec` taken from the language selection below.
2. The pre-script `bridge/ghidra/ReaGhidraPrepareDol.java` reads a section file
that REA wrote from the parsed header, with versioned collection counts. It creates one initialized block per
used text and data section at its load address, with file bytes from the
section's file offset. The whole-file block is removed before the section
blocks are created. Text is read-execute and data is read-write. It sets the
entry point and labels the entry
`__start`.
Sections that the header marks unused are not mapped. The 256-byte header and
any bytes between sections are not mapped either.
## BSS
A DOL has one BSS range, and it is not always disjoint from the data sections.
The domain parser computes the parts of BSS that no data section covers. Both
the pre-script and load-image attestation consume those parsed ranges. The
pre-script creates uninitialized read-write blocks only in those gaps. Where BSS overlaps a data slot, that slot
keeps its file bytes. Overlapping slot indexes are reported by the parser.
## Bound language
The default language is `PowerPC:BE:32:default` with compiler spec `default`,
which ships with Ghidra. The load-image attestation checks the language Ghidra
reports, not only the requested one.
### Optional Gekko/Broadway override
`REA_GHIDRA_LANGUAGE_ID=PowerPC:BE:32:Gekko_Broadway` selects the Gekko/Broadway
variant. The language is not in stock Ghidra. It comes from the GameCubeLoader
extension, by Cuyler, licensed under Apache-2.0. The extension ships
`ppc_gekko_broadway.ldefs` under its `data/languages` directory and must be
installed in the Ghidra installation's `Ghidra/Extensions` directory. The stock
lane never requires it.
Any other language id must start with `PowerPC:BE:32:`. Other languages are
refused with a specific constraint.
## Paired-single limitation
The stock `PowerPC:BE:32:default` language does not decode the paired-single
instructions that Gekko and Broadway add. Those words decode as bad or undefined
instructions. Use the Gekko override to decode them.
## Verification
The source-owned fixture is built from `tests/conformance/c/ppc.c`,
`tests/conformance/c/dol.c`, `tests/conformance/ghidra/ppc-probe.S` and the
linker script `tests/conformance/ghidra/dol-fixture.ld`. It is converted to a DOL
by `tests/conformance/ghidra/dol-fixture.mjs`. Its text is at `0x80003100`, and
its single BSS range overlaps data slot 1 and leaves a gap. The fixture is
never executed. The real GameCube or Wii game images are never used.
```bash
GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra npm run verify:ghidra:dol
```
The lane needs the same toolchain as the PowerPC ELF lane: `REA_PPC_CLANG`,
`REA_PPC_READELF` and LLD. An independent DOL header reader and GNU readelf
(run on the source ELF) check the section table, file bytes, entry and BSS gap
before Ghidra starts. The checks never use REA's parser.
After import, the lane checks the load-image attestation (`verified`), the
bound language, the `__start` entry procedure, a direct call to the leaf
function, decompilation, and bytes at the data, tail, marker and BSS-gap
addresses. It also checks CLI identity.
The Gekko pass runs only when `PowerPC:BE:32:Gekko_Broadway` is in the Ghidra
installation's language catalog, the same catalog REA uses to admit
`REA_GHIDRA_LANGUAGE_ID`. A language that exists only in a user extension under
`~/Library/ghidra` is not admitted, so the lane prints
`skipped: PowerPC:BE:32:Gekko_Broadway (not in the Ghidra installation catalog that REA admits); stock lane still verified`
and still succeeds on the stock checks.
The ELF lane (`npm run verify:ghidra:ppc`) is separate. See
[PowerPC ELF analysis through Ghidra](/ghidra-ppc).
## Not supported yet
- Small-data anchors: `r2` and `r13` are not seeded from the DOL header.
- REL (relocatable modules) and RPX (Wii U) are not admitted.
- IDA and Hopper refuse `gamecube-dol`. Ghidra is the only provider.
- Disc images (ISO/GCM) are not supported. Pass an extracted DOL file.
+6 -17
View File
@@ -23,24 +23,13 @@ not name its entry function `entry`. MCP uses `open_binary`, `list_procedures`,
`procedure_info`, `read_function_instructions`, and `analyze_function` with the
same admitted target and provider semantics.
The DOS MZ parser validates page counts, initialized module length, header and
The classifier validates MZ page counts, initialized module length, header and
relocation-table bounds, relocation destinations, and the initialized entry
point using the available structural bytes and actual file length. It reports
those DOS facts separately from Windows-header evidence. A recognized PE
signature remains PE; recognized NE, LE, and LX headers are explicitly
unsupported. A nonzero Windows-header pointer without a recognized signature
usually remains an unresolved candidate, even when the DOS structure validates:
automatic target selection fails closed and reports both observations. When a
nonempty relocation table ends inside a complete DOS header, the remaining
header bytes belong to DOS padding; a nonzero dword there is not a Windows
declaration. An unread signature beyond the current probe is requested before
that padding rule applies. This avoids manufacturing a Windows header from
original DOS padding while preserving rejection of damaged Windows
declarations.
Inventory classification is a bounded-prefix format hint; it returns `unknown`
for an unresolved candidate or when its prefix cannot establish the format.
That result is distinct from complete DOS validation and does not decide target
admission.
point. It distinguishes DOS load-module and relocation bytes from the Windows
new-header field. PE remains PE; NE, LE, and LX declarations are explicitly
unsupported. A damaged declared Windows header does not silently become DOS.
Inventory classification returns `unknown` when its bounded prefix cannot
establish the format; this is distinct from the complete target admission check.
Ghidra imports a private target snapshot with `MzLoader`, language
`x86:LE:16:Real Mode`, and compiler specification `default`. These settings,
+4 -13
View File
@@ -24,8 +24,7 @@ specific unsupported constraint rather than guessing a compatible profile.
A second admission covers recordless legacy firmware images
([#1620](https://github.com/morluto/rea/issues/1620)): ELF32 `ET_EXEC` headers
declaring `EF_MIPS_ARCH_1` (MIPS-I, a zero ISA field), `EF_MIPS_ARCH_32` or
`EF_MIPS_ARCH_32R2` with an unspecified ABI
declaring `EF_MIPS_ARCH_32` or `EF_MIPS_ARCH_32R2` with an unspecified ABI
field, `EF_MIPS_ABI2` clear, and an _inspected_ absence of any ABI flags
record (a target whose record was merely not observed stays refused). For
these images o32 is an analysis assumption derived from the ELF32/ET_EXEC
@@ -41,17 +40,9 @@ refusal. A header that declares an ABI field but carries no record is still
refused. Two further boundaries apply: an inspection that could not examine
every ABI declaration source (for example a carved ELF whose section table
extends past the captured bytes without a program-header record) does not
establish the recordless admission, and an `EF_MIPS_ARCH_1` or
`EF_MIPS_ARCH_32` header that _declares_ an ABI is refused rather than
relabeled, because the declared-ABI lane is verified for
`EF_MIPS_ARCH_32R2` headers only. The recordless MIPS-I lane pins the same
generic 32-bit language, which also decodes later MIPS32 instructions; the
session states that conformance to the MIPS-I subset is not established from
the header alone, and names the committed language when the caller selected a
variant instead of the pin. A committed profile is bound to the current
target's lane and captured ELF metadata before startup; reusing a profile
resolved for the other recordless lane is refused rather than exporting
attribution for a target it did not describe.
establish the recordless admission, and an `EF_MIPS_ARCH_32` header that
_declares_ an ABI is refused rather than relabeled, because the declared-ABI
lane is verified for `EF_MIPS_ARCH_32R2` headers only.
Explicit `EF_MIPS_FP64` and `EF_MIPS_NAN2008` declarations are refused. Their
absence is insufficient: Clang can emit identical `e_flags` for FP32, FP64 and
+1 -21
View File
@@ -109,7 +109,7 @@ bytes live only in a parser-owned overlay, identified by virtual address, size,
and SHA-256; the caller's source Buffer remains unchanged. Limits derive from
captured section extents and format field bounds. Separate resource policies
admit up to 128 MiB of captured PE bytes, 64 MiB-equivalent parser work, 64 MiB
of exact parser-owned overlay/index storage, and 4 MiB of serialized NativeAOT
of exact parser-owned overlay/index Buffers, and 4 MiB of serialized NativeAOT
report JSON. These are local parser limits, independent of MCP delivery; the
buffer counter does not claim to cap V8 heap or total process memory. Rows that
do not fit the report budget are omitted with explicit partial status and
@@ -120,26 +120,6 @@ before and after parsing; the synchronous parser call itself is bounded but
cannot be interrupted mid-call. Parser output never claims that a Ghidra
database type was defined.
RTR signature omission counts include every eligible section before the work
budget is spent, including later sections that were never visited. MethodTable
closure indexes each validated table's related pointer once and visits a work
queue for inheritance and interface reachability. Its temporary index and queue
consume the existing working-memory budget; the parser does not increase its
work limit. Candidate headers/counts are parsed once; unmapped table bodies are
rejected before charging table entries, and validation charges only each slot or
interface pointer actually examined. Declared counts and unvisited tails do not
become examined coverage. Discovery still must finish before claiming a unique System.Object
root, so a discovery budget stop can return zero recovered tables without
establishing their absence. Performance on the large bflat report has not been
measured. Indexed closure and candidate validation avoid separate repeated or
unperformed work, but do not establish that the fixed discovery budget admits
that artifact. Budget stops remain partial results with unknown type absence.
If captured snapshot bytes no longer match their admitted digest, the parser
retains both digests and the Ghidra owner reports `artifact_changed`, with the
captured byte count and admitted identity still available. Reopen acquisition
against a stable artifact; this is distinct from corrupted retained Evidence.
The optional adapter performs its annotations in an ephemeral Program
transaction. That workflow may change analysis-memory bytes, never the original
executable file. It is unavailable on Windows P0, which has no database
+1 -2
View File
@@ -3,8 +3,7 @@
This lane extends native ELF analysis to 32-bit big-endian PowerPC; it does
not execute PowerPC code. It is the first milestone of
[#1741](https://github.com/morluto/rea/issues/1741). GameCube/Wii DOL
executables are covered by [GameCube DOL analysis](ghidra-dol.md). PPC64 and other
PowerPC variants remain separate follow-ups.
executables, PPC64 and other PowerPC variants are separate follow-ups.
## Admission boundary
+2 -40
View File
@@ -15,20 +15,7 @@ snapshot parser and a separate optional annotation extension. Check the
[release boundary](installation.md#released-package-and-main) when using npm.
See [import language and analysis seeds](installation.md#ghidra-import-language-and-analysis-seeds)
to override the header-detected language or seed analysis. An override that conflicts with a fixed raw, DOS, PSP, MIPS, RISC-V or GameCube DOL import
is a configuration error naming the conflicting settings. It does not make an
otherwise admitted target unsupported; REA preserves the requested settings.
An inaccessible language
catalog remains a read failure; it does not prove that a configured language is
undefined. Installation `application.properties` has a fixed 1 MiB read allowance
with no runtime override. The failure retains that allowance and directs recovery
at the selected installation metadata.
Retained process diagnostics can report termination by `SIGKILL`; the signal alone
cannot establish an OOM kill. REA gives neutral recovery guidance and preserves the
signal. Confirm resource causes from process output or host/container records;
operation-scoped JVM `OutOfMemoryError` observations retain their separate resource
classification. Automatic kernel or cgroup OOM attribution is not implemented.
to override the header-detected language or seed analysis.
Provider selection and profile identity follow
[MCP contracts](mcp-contracts.md#identity-and-discovery). One deep
@@ -36,18 +23,6 @@ provider stays bound to the target; runtime failure never selects another
provider automatically. The contracts below describe Ghidra observations,
not equivalence with another engine's pseudocode or analysis database.
## Request payload ceiling
Ghidra requests use `REA_BRIDGE_MAX_REQUEST_BYTES`: default 16 MiB, with a
configurable range of 7,464–536,870,887 bytes. REA measures UTF-8 JSON, including
escaping, before allocating the serialized request or sending it. The framing
newline is excluded. An oversized request returns `resource_constraint` with
the effective setting, its source, and the exact payload byte count.
Set the value in the environment that starts REA, then restart REA to apply it.
This setting is enforced by the REA client; the Java bridge has no corresponding
request ceiling.
## Admitted inventory semantics
| Concern | Ghidra contract |
@@ -187,7 +162,7 @@ profile stages before retrying their retained descriptors.
| Concern | Ghidra contract |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Decompiler lifetime | One persistent `DecompInterface` is opened for the imported Program and disposed during bridge shutdown. Native decompilation has no fixed per-function deadline unless the launch setting `REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS` or runtime override `ghidra.decompile_timeout_seconds` sets one; a decompile that reaches it fails only that request with `remote_code` `decompile_timed_out`, the Program and session stay open, and the next decompile uses a fresh decompiler process. External functions or functions without bodies return `null`; cancellation and native failure remain distinct. |
| Decompiler lifetime | One persistent `DecompInterface` is opened for the imported Program and disposed during bridge shutdown. Native decompilation has no fixed per-function deadline. External functions or functions without bodies return `null`; cancellation and native failure remain distinct. |
| Serialization | A FIFO sends one Program request at a time. Caller cancellation removes queued work promptly and is passed to active socket waits. This is an adapter safety commitment, not a claim that every Ghidra API is thread-safe. |
| Function identity | Every function result carries the entry address and Ghidra FunctionManager classification for external, thunk, and resolved thunk target. These are observations, not proof that unresolved targetless calls have been recovered. |
| Assembly and pseudocode | Assembly is complete Ghidra Listing text; pseudocode is Ghidra decompiler output. Neither is original source, and cross-provider comparison never treats Hopper and Ghidra text as equal or unequal semantic facts. |
@@ -197,13 +172,6 @@ profile stages before retrying their retained descriptors.
| P-code value flow | Function dossiers include up to 3,000 high-p-code operations, at most 64 inputs per operation, and 12,000 def-use edges. Results identify memory reads/writes, branches, and calls by p-code opcode, and mark decompiler-dead operations. Large results report truncation, an omitted-operation lower bound, and known omitted-input/edge counts. The dossier is an intra-function graph; `trace_native_values` composes derived parameter/argument and return/output dependencies across resolved calls. It does not resolve memory aliasing, call side effects, runtime behavior, persistent state, or business meaning. |
| Result extent | Function instruction scans and native API boundary observations remain complete. P-code flow is the explicit bounded exception; its count fields distinguish exact retained counts from lower-bound or known-omitted counts. Caller cancellation and provider failures remain distinct. |
Runtime updates apply to newly accepted operations on the existing Program.
They preserve accepted budgets and do not clear cleanup blockers or change the
client deadline. Transient decompile timeout failures remain uncached so the
same function can be retried after raising the limit. See the
[settings design](design/limits-and-recovery.md) for snapshots, precedence,
and provider integration.
Function extents and instruction completeness refer to Ghidra's analyzed body.
An inferred no-return call can terminate that body before subsequent bytes.
Pseudocode, assembly, procedure info, instruction windows, incoming and outgoing
@@ -289,9 +257,3 @@ next match. Coverage can overlap between pages; do not sum it as unique memory.
page. These blocks are never searched. Cancellation returns a typed error,
discards the active Ghidra database, and does not return a successful partial page.
Hopper byte search remains unavailable until its implementation is verified.
MIPS16 shared-image admission requires examined dynamic REL declarations.
RELR dynamic tags or nonempty `SHT_RELR` sections leave relocation coverage
unavailable and the profile unsupported; REA does not infer an empty relocation
set from the absence of REL declarations. RELR loader semantics have not been
verified for this lane.
+11 -60
View File
@@ -5,12 +5,6 @@ modules, replacements and build settings are embedded in one selected local
executable or library. It reads the file without running it and requires no
Go installation, active binary target, Hopper, Ghidra or IDA.
If a stable read completes but descriptor finalization fails or receives
cancellation, the error retains the selected path, SHA-256 and byte length as
a `file-snapshot-hash` partial observation. It does not claim that Go metadata
was decoded. Failed cleanup stays incomplete until its exact owner confirms
release; cancellation keeps the caller's reason.
```bash
rea inspect-go-binary /absolute/path/to/application --json
```
@@ -67,7 +61,7 @@ rea inspect-go-binary ./application --detail summary --evidence_output ./go-evid
`--evidence_output` is required for CLI summary delivery and never overwrites an
existing file. The default CLI/MCP reply remains complete.
[Measured workflows](https://github.com/morluto/rea/blob/42e1a17d6e6c0487f17be0bae248553c4caba7f7/experiments/analysis-efficiency/go-build-views.md) include
[Measured workflows](../experiments/analysis-efficiency/go-build-views.md) include
catalog repetition and extra calls. Identity/count questions benefit from a
single summary. In the tested Codex full-catalog configuration, a compound
question about identity and one dependency uses fewer tokens in captured request JSON with
@@ -115,53 +109,17 @@ module record and no dependency records has an observed empty dependency list.
## Supported boundary
The built-in TypeScript reader supports ELF, PE and thin Mach-O images, and one
selected slice of a universal Mach-O, with Go's inline or historical pointer
build-info encoding. Source ranges refer to the original stable file snapshot,
including pointer-backed strings. The shared native container owns address
mapping, including file-backed PE headers. A descriptor or string can cross
adjacent mappings when their virtual addresses map to physically contiguous
file bytes with the same translation. Gaps, conflicting aliases, zero-fill
aliases and noncontiguous string source bytes cannot establish a single source
range and are refused. Native export C strings use the same mapping checks but
can join multiple source chunks because their result has no single-span source
contract. Legacy pointer-backed fields are decoded at
the width declared by Go's build-info record, independently of the image's ELF
class. It does not scan arbitrary byte strings as compiler metadata.
Legacy string descriptors and contents may use the declared file-backed PE
headers. A descriptor or string may also cross adjacent loader mappings when
their virtual ranges cover every requested byte and identify one contiguous
file range. Gaps, conflicting file aliases and nonempty zero-fill overlap remain
invalid. Discovery policy and the aggregate 1 MiB string-content limit are
unchanged. These are static reads over REA's native mapping facts; they do not
establish Windows/Linux loading or parity with Go's standard reader, whose
[Go 1.24.0 ELF/PE readers](https://github.com/golang/go/blob/go1.24.0/src/debug/buildinfo/buildinfo.go#L445)
read within individual segments or sections. The historical two-word string
descriptor follows the
[Go 1.17.13 reader](https://github.com/golang/go/blob/go1.17.13/src/cmd/go/internal/version/version.go#L212).
A universal (FAT) Mach-O needs `architecture` (`x86`, `x86_64`, `arm`
or `arm64`; CLI `--architecture`). REA reads its own FAT table, without `lipo`,
and validates the selected slice's range, alignment and Mach-O header CPU
against the table. The result keeps the universal file as `artifact` (its
SHA-256 and size), adds `selected_slice` with the slice offset, length, raw CPU
type/subtype and the FAT table digest, and reports every source range as a
file offset inside that slice. Without `architecture`, or when the file has no
such slice or several, the request fails on `architecture` and lists the slices
present. `architecture` on any other image is rejected rather than ignored.
The built-in TypeScript reader supports ELF, PE and thin Mach-O images with Go's
inline or historical pointer build-info encoding. Source ranges refer to the
original stable file snapshot, including pointer-backed strings. It does not
scan arbitrary byte strings as compiler metadata or interpret a fat Mach-O
container as one selected architecture.
For ELF images without a named build-info section, discovery checks each
nonempty writable, non-executable load segment in program-header order. This
includes sectionless PIE images with metadata in a later data segment. Named
sections retain priority, and alignment, file mapping and record validation
remain required. When ELF section names are unreadable, limitations report that
coverage gap; the fallback search policy is unchanged.
A named ELF or Mach-O build-info section selects the record's discovery range.
For inline encoding, the following version and module strings may continue past
that section into its file-backed load mapping; their bytes are still checked
against the mapping and the existing aggregate string limit.
remain required.
Historical moduleless Go binaries can store their empty module-string header in
declared zero-initialized virtual memory rather than file-backed bytes. The reader
@@ -188,12 +146,10 @@ and `reported_limits.maximum_input_bytes`. Select a suitable input within that
fixed allowance; changing an output-write or transport budget does not admit it.
Resource guards bound the selected file and cumulative header searches to 256 MiB
each, aggregate embedded string contents to 1 MiB (varint length prefixes are
framing and do not count toward this allowance), structural table decoding to
16 MiB and the complete normalized metadata report to 8 MiB. Inline strings must
also fit within their actual mapped source range. Identical search spans with
the same virtual alignment are searched once; partially overlapping spans
consume the cumulative search budget
each, aggregate embedded strings
to 1 MiB, structural table decoding to 16 MiB and the complete normalized metadata
report to 8 MiB. Identical search spans with the same virtual alignment are
searched once; partially overlapping spans consume the cumulative search budget
and fail with a CPU resource constraint when it is exhausted. Oversized results
fail without partial success. MCP also applies
its [response transport budget](mcp-contracts.md), which counts both text and
@@ -212,8 +168,3 @@ those questions require a disassembler.
`npm run verify:go:binary` exercises compiler-built fixtures through the public
CLI and MCP, comparing their metadata with Go's standard `debug/buildinfo`
reader. The verification lane requires Go; normal inspection does not.
Reader architecture names remain verbatim in the report. Evidence subjects use
the recognized canonical family: `386` maps to `x86`, `amd64` to `x86_64`, and
`ppc` to `powerpc`. Unknown families such as `ppc64` remain unknown rather than
being inferred from a related spelling.
-8
View File
@@ -7,9 +7,6 @@ releases have not been verified. Format 1 is unsupported.
Inspection retains the original header and directory facts, including engine
version, flags, declared file count, source paths, offsets and declared MD5.
For a member declared as `res://scenes/main.gd`, the occurrence's
`source_path` retains that exact declaration while `logical_path` is
`scenes/main.gd`, the relative path used for lookup and extraction.
Observed plaintext bytes receive content-addressed SHA-256 identities. MD5 is a
container integrity claim, separate from the observed SHA-256. A mismatch fails
by default; `--integrity-policy record-and-continue` retains the observed bytes
@@ -37,11 +34,6 @@ The same CLI commands locate a PCK at the beginning of a file or through its
payloads or discover packs behind additional trailing signatures. The root
content SHA-256 covers the whole carrier; its content classification remains
native, while the inventory records the PCK role and exact pack bounds.
An EOF marker is only a candidate: an invalid size or a pointed-to location
without a `GDPC` header leaves native classification in place. Once the
trailer points to a pack header, malformed or unsupported PCK content remains
an error. This recognition boundary follows the Godot loader at pinned source
revision [06d1e7a](https://github.com/godotengine/godot/blob/06d1e7a34ccde4029eed53d018cfde77dfe771d5/core/io/file_access_pack.cpp#L2099-L2146).
Ordinary native `open_binary` detection remains unchanged.
## MCP and selected facts
-224
View File
@@ -1,224 +0,0 @@
# Static CUDA and HIP code objects
`inspect_gpu_code_objects` answers which GPU targets and kernel declarations a
local artifact carries. It reads ELF/PE sections, standalone CUDA fatbins,
CUBIN/PTX and Clang HIP bundles, including CCOB v1/v2/v3. It neither executes the
artifact nor invokes CUDA/ROCm tools. GPU execution, SASS disassembly and host
launch correlation remain outside coverage.
Start with a target inventory:
```json
{ "path": "/artifacts/library.so", "view": { "collection": "targets" } }
```
Target rows aggregate inventoried payloads and distinguish an unrequested kernel
analysis from a complete or partial one. `objects` pages expose exact object
IDs, compression declarations and encoded/header locations. Fields, exact
filters, batch IDs and stable pagination select the facts delivered to the
caller. IDs are positional within the recorded artifact SHA-256. Selecting `object_ids`
in a new inspection requires `expected_sha256` from that inventory; a changed
file is rejected before parsing selected payloads.
`target` is the inventory declaration used for selection; `payload_target` is
the independently observed inner declaration when available. Object pages keep
both values, their kind, `target_consistency` and inspection coverage even with
selected fields.
Conflicting base architectures retain both values, mark object/report/target
coverage partial and annotate the affected kernels. CUBIN flags establish a base
architecture: `base-only` explicitly leaves feature-profile agreement unknown,
including `a` and `f` variants. A standalone declaration is `single-source`, and
unexamined payloads remain `unexamined`. An exact AMDGPU target is normalized at
its note owner for comparison with a Clang bundle declaration; raw values remain
intact. Unresolved feature comparisons stay `unknown`.
A HIP target ID identifies a toolchain target, not its payload representation.
Recognized raw or wrapped LLVM bitcode stays `unsupported` for kernel metadata
inspection, with the bundle ID, range and selected payload digest retained.
Bitcode validity and inner targets remain unexamined; malformed recognized ELF
still reports `malformed`. Standalone bitcode is outside GPU container coverage.
HIP bundle and metadata note recognition accept both the legacy `amdgcn` triple
and LLVM's `amdgpu` subarchitecture triples, including declared generic processors
such as `gfx11-generic` and `gfx9-4-generic`. Notes with the empty or explicit
`unknown` environment use the same processor and feature comparison while
retaining their raw triple spelling. CUDA's legacy fatbinary compression flag is
retained as an unsupported codec with its encoded and decoded extents;
compressed bytes are not treated as PTX text. CCOB is recognized by the exact
four signature bytes `CCOB`; high-bit-corrupted bytes are not decoded as ASCII
and accepted as a bundle. CCOB v1 has no stored total size: the completed zlib
or Zstd stream determines its encoded extent. Embedded `CCOB` text is ordinary
compressed data; zero-byte alignment after the stream belongs to neither
payload. If decoding fails or its codec is unsupported, a later `CCOB` marker
is only an uncertain recovery candidate. The affected row retains that
limitation and discovery remains partial.
The declared decoded length bounds decompression. An understated length can
stop decoding before codec completion, so any subsequent recovery extent stays
unknown; the decoder does not expand its allowance to prove the bad declaration.
Interrupted observations omit unfinished v1 rows. A terminal capacity row
qualifies its retained range as the containing tail and does not infer siblings.
`cuda_target_header` retains the OS ABI, normalized raw `e_flags` and exact
header location. CUDA OS ABI `0x33` and the observed `0x41` flags layout use
different architecture fields; they are decoded at one ABI-aware owner.
Unrecognized layouts keep the header and an unknown target instead of
inventing an SM label. Header provenance is validated against its CUBIN object.
Target filters select inventory declarations. An unselected payload's inner
target remains unexamined; an empty filtered result does not establish that
no enclosed code declares that target. Discovering every inner declaration
requires decoding those payloads.
HIP bundle IDs retain the declared processor and v2-v3 `+feature` suffixes for
exact selection. If a recognized AMDGPU bundle ID does not expose a target, the same
bounded note-discovery owner used for standalone AMDGPU files establishes it
before filtering. Those directory/note reads share the inspection work budget;
successive queries can select the target returned by inventory.
Decode selected targets and return a kernel page in the same call:
```json
{
"path": "/artifacts/library.so",
"analysis": "kernels",
"targets": ["sm_100"],
"view": {
"collection": "kernels",
"names": ["kernel_name"],
"fields": [
"name",
"parameter_count",
"parameter_bytes",
"parameters",
"registers",
"shared_bytes"
]
}
}
```
Only selected payloads are decoded. Other inventory entries remain present with
`not-selected` states, and overall payload coverage remains partial. An empty
kernel page proves no matching retained fact; it does not establish that an
unexamined payload contains no kernel. Missing fields remain null.
Kernel representations have different meanings:
- PTX reports parameter declarations and virtual registers. Shared bytes cover
supported static declarations inside that entry; they do not establish module
shared memory, dynamic allocation or physical device resources.
PTX 1.0–1.3 declares entry parameters in the body; PTX 1.4 and later uses the
entry parameter list. Entry-scoped `.pragma` statements before the body do
not end the declaration or suppress its body resources. A `.target
compute_xx` spelling is retained as written;
NVIDIA PTX defines it as a synonym for `.target sm_xx` when comparing its
target identity with the fatbin declaration. `.callprototype` formal
parameters are excluded from entry virtual-register counts. Parameter names
remain available when pointer qualifiers are understood or an unsupported
qualifier prevents layout decoding. A pointer's `.align` describes the
pointed-to memory and does not change parameter layout alignment. Spaced,
joined and partly joined pointer qualifiers have the same meaning. Parameter
alignment before or after the scalar type retains its layout and name.
Parameter names and following declarations are retained when an array
extent uses a parenthesized expression or when dimensions are
multidimensional, even though those forms do not currently yield a layout.
PTX target selection uses the highest-numbered module-scope `.target`
directive, including directives after entry declarations; unsupported or
conflicting target profiles leave the effective target unknown.
Scalar register declarations count each bare or percent-prefixed name and
each parameterized range, including compact keywords such as `.reg.b32`.
Unsupported declaration forms or overflowing totals
retain an unknown count and partial decoding coverage. Unsupported parameter
layouts and unknown shared-memory declarations likewise retain their known
facts, explicit limitations and partial coverage through retained pages.
- CUBIN identifies CUDA entry symbols and reads numeric `nv.info` records,
including V2 kernel-parameter sizes, and native section declarations. Global
register declarations also cover compiler
versions that leave the older text-section register field empty.
Conflicting register declarations keep the physical count unknown, preserve the
kernel's other facts, and mark object/report/target decoding coverage partial.
- AMDGPU reads v3+ MessagePack notes. V3 processor identity comes from the
`EF_AMDGPU_MACH` field in ELF `e_flags`; known processor values are selectable
even when the note omits `amdhsa.target`. This inventory reports the processor
only; it does not interpret version-specific feature flags. Unknown processor values remain unknown. A known
processor conflict between ELF flags and note metadata leaves the processor
unresolved. Argument counts include hidden ABI arguments
when present; SGPR/VGPR counts and fixed group/private segment sizes retain
their declared meanings. Legacy YAML metadata remains unsupported.
Each selected PTX payload becomes a retained UTF-8 text artifact with a SHA-256,
byte length and source coordinates. It is omitted from compact replies. Read it
with `inspect_analysis_view` and the parent Evidence ID returned by the compact
receipt:
```json
{
"source": { "kind": "retained-evidence", "evidence_id": "ev_..." },
"view": {
"kind": "gpu-code-objects",
"collection": "ptx",
"object_id": "...",
"offset": 0,
"limit": 4096
}
}
```
Text offsets use UTF-16 code units and must fall on Unicode scalar boundaries.
Complete Evidence retains the selected text; `export_evidence_bundle` provides
its portable source record. Historical pages never reopen the input file.
A location with `container_id: null` uses original file offsets. Other locations
use that inventoried object's decoded byte coordinates. Follow its parent,
encoded location and digest to trace decompressed metadata back to a native
section and the original file. Decoded offsets are never presented as physical
file offsets. Reads from an ELF or PE GPU section stay within that declared
section extent, so neighboring native section bytes cannot complete a
truncated embedded bundle header.
The CLI uses the same service and contracts:
```sh
rea inspect-gpu-code-objects /artifacts/library.so --analysis kernels \
--targets '["sm_100"]' --detail summary \
--view '{"collection":"kernels","names":["kernel_name"]}' \
--evidence_output /analysis/library-gpu.evidence.json --json
```
A full-file SHA-256 always streams every byte. Inventory then reads bounded
native directories and bundle headers; CCOB requires decompression to discover
its inner directory. Kernel analysis reads selected payloads. `bytes_read`
includes hashing and subsequent descriptor reads, so this is not a header-only
hashing workflow.
CUDA raw LZ4 blocks and Zstd, and CCOB zlib/Zstd are supported. Unknown versions
or codecs retain declarations with explicit limitations. Lengths are checked
before decoding, and CCOB's truncated MD5 is verified against decoded bytes.
Resource limits are one active inspection, 128 MiB per input/output buffer,
512 MiB of payload input, decoded output and AMD note-read work per inspection,
and 16 MiB retained facts including selected PTX. Payload work is reserved before
reading or allocating; a capacity refusal does not reread an aliased payload.
Native tables and decoded section names each have a 16 MiB structural budget;
AMDGPU ELF note discovery admits at most 8 MiB of source note bytes, with
bounded depth and container/string allocations. Standalone AMD targets are read
from those notes before target selection; selected kernel analysis reuses the
same parsed declarations. Pages have a
1 MiB response budget. Resource refusals and interrupted observations preserve
known facts and explicit uncertainty.
CUDA ELF parsing additionally shares a 262,144 record/symbol visit limit,
128 MiB of cumulative metadata section reads, and a 16 MiB serialized-size
accounting budget for temporary index facts. These limits are checked before
index growth; they are not claims about JavaScript heap size. Equivalent scoped
metadata byte ranges and repeated equal register declarations are coalesced;
conflicting values remain unknown. Parameter sections are parsed once per ELF.
Missing CUBIN symbol tables or unresolved section names produce `partial`
object, report and target coverage even when some kernel facts survive. AMD note
failures preserve earlier successfully parsed declarations and the original
failure reason. Standalone PTX recognition tries a small header first, then
continues within the existing 16 MiB text budget when leading comments or
whitespace obscure its directives; admitted full-file probe bytes are reused.
After those comments and whitespace, `.version` must be the first PTX module
token; later PTX-looking text does not identify an arbitrary binary as PTX.
+2 -12
View File
@@ -36,9 +36,8 @@ The MCP tool `project_harmony_application_graph` takes the same
- exact component paths, artifact IDs, and hashes (manifests, Ark bytecode,
resources, native libraries, JavaScript, signing)
- a packaging-model hint: Stage is claimed only from a `module.json` entry
outside a classified resource path, FA only from `config.json` outside a
classified resource path, otherwise `unknown`
- a packaging-model hint: Stage is claimed only from a `module.json` entry,
FA only from `config.json`, otherwise `unknown`
- FA resources under `assets/<module>/resources/` as well as Stage `resources/`
- unavailable component paths with unknown IDs and hashes, including encrypted entries; these make projection coverage partial
- App Pack child package paths without recursive inventory
@@ -84,12 +83,3 @@ not been independently established. This lane does not complete the pinned
toolchain acceptance in #1431 and does not verify Ark decoding or signatures.
Set `REA_HARMONY_ENTRYPOINT` to an installed package's `scripts/rea.mjs` to run
the same checks against that package; the default lane uses the repository build.
Packaging-model and App Pack child presence use inventoried file/slice paths even
when encrypted content cannot be authenticated. Unavailable App Pack children
retain their path, format, encryption and limitations with null artifact ID and
digest; they do not establish decoded package semantics. Missing source nodes
outside supplied pages retain the inventory limitation and partial coverage.
The signing list includes inventoried META-INF RSA, DSA and EC certificate-block
paths alongside the existing suffixes; opaque content does not establish a valid
signature or certificate.
+3 -53
View File
@@ -41,15 +41,6 @@ rea function /absolute/path/to/program main --json
rea decompile /absolute/path/to/program main --json
```
If the registration cannot be read or validated, provider diagnostics retain its
selected `registration_path` and the configuration owner's setting/constraint
facts in `configuration_details.settings`, plus `configuration_remediation`.
`configured` means a path was selected, not that it is valid or an IDA connection
was established; failed registrations remain unavailable. Correct
`REA_IDA_MCP_CONFIG` in the next CLI invocation's environment or the MCP server's
launch environment. Restart/reconnect that server after changing its environment;
an export in another shell does not update a running server.
Open the **original input binary** in REA, not an `.idb` or `.i64` database. Its SHA-256 must match IDA's recorded input hash. REA checks the attached identity and image base around each analysis request. A mismatch or target switch fails with recovery guidance. The original input must remain readable by REA; WSL can use its mounted alias while the GUI runs on Windows. Attached profiles, including native mode, refuse universal Mach-O inputs at admission: every slice shares the recorded whole-file hash, so REA cannot verify which slice the GUI loaded. Open a thin file extracted with `lipo -thin` in both IDA and REA, or use headless mode, which analyzes the selected slice.
REA's close operation releases its MCP connection or proxy. It never saves or closes the existing GUI database. External edits remain visible because results are live and are not replayed from snapshots. These checks do not lock the GUI or prove that the database is unpatched.
@@ -108,14 +99,7 @@ Headless REA and the configured supervisor must share the host filesystem and na
REA copies the admitted binary into a private temporary directory, checks its digest, and requests an owned worker with `force_headless`. POSIX workspaces are created atomically with owner-only permissions, respecting the process umask; Windows uses the matching package's native private-DACL boundary on a local NTFS temporary directory. It injects the returned database ID into analysis requests. It verifies the active owned worker and input path rather than adopting a user database. On close or failed startup, it releases its session with `save: false`, verifies that it is absent from upstream session discovery, and removes its private input and sidecars. An unverified release retains the workspace and reports incomplete cleanup.
One REA session owns one database. Stdio registrations default `IDA_MCP_MAX_WORKERS` to `1`; the explicit upstream `--max-workers 1` also bounds a supervisor. Database size and engine memory depend on the target and upstream; REA does not impose an engine memory limit. The original binary is never executed or overwritten. Optional `workspaceRoot` selects an existing absolute temporary-directory parent on the REA host. The selected parent is resolved by the filesystem before deriving its canonical identity, including POSIX symlink/parent components. Ensure it has space for the input copy and IDA working files.
Readiness and allocation check the selected spelling with native
[`stat`](https://github.com/nodejs/node/blob/v24.22.1/doc/api/fs.md#fspromisesstatpath-options)
before canonicalizing it. This retains missing and non-directory intermediate
lookup failures: [macOS `realpath`](https://github.com/apple-oss-distributions/Libc/blob/71bbe350ab79eef58113991d817ccc6165061a64/stdlib/FreeBSD/realpath.c)
alone can collapse `file/..`. Diagnostics retain the selected spelling; private
input paths use the canonical parent for upstream identity checks.
One REA session owns one database. Stdio registrations default `IDA_MCP_MAX_WORKERS` to `1`; the explicit upstream `--max-workers 1` also bounds a supervisor. Database size and engine memory depend on the target and upstream; REA does not impose an engine memory limit. The original binary is never executed or overwritten. Optional `workspaceRoot` selects an existing absolute temporary-directory parent on the REA host. Ensure it has space for the input copy and IDA working files.
When a completed `idb_open` response reports success or is otherwise ambiguous, but no active owned private worker can be verified, REA retains the private workspace even if `idb_list` appears empty. A later close can retry verification if the original owned worker becomes discoverable. A completed explicit `idb_open` rejection retains the existing no-worker cleanup path. This is a local admission/ownership safeguard and **does not** provide a process-termination receipt or change the legacy reaped-worker protocol.
@@ -140,7 +124,7 @@ Add the registration path to the **REA** MCP entry's environment, keeping the up
}
```
Use a package version that actually includes this adapter; repository main can be ahead of the npm release. After reconnecting, call the sequence below; the default `analyze_function` reply is a summary that retains the dossier, so add `"detail":"complete"` when the dossier itself must come back inline:
Use a package version that actually includes this adapter; repository main can be ahead of the npm release. After reconnecting, call:
```json
{"name":"open_binary","arguments":{"path":"/absolute/path/to/program","provider_id":"ida"}}
@@ -149,7 +133,7 @@ Use a package version that actually includes this adapter; repository main can b
{"name":"close_binary","arguments":{}}
```
Opening an MCP target starts its selected provider session; headless opening waits for auto-analysis. Set the agent's MCP request deadline long enough for the target. The registration JSON named by `REA_IDA_MCP_CONFIG` has an optional `timeoutMs` integer from 1 to 2147483647. Omitted means 300000. REA applies an edited value on the next IDA connection after it re-reads that file; an already open session keeps the timeout it connected with. Changing the `REA_IDA_MCP_CONFIG` path itself is read at process start. An upstream SDK request that reaches this limit returns `provider_timeout` naming the effective milliseconds and `timeoutMs`. A shorter inner request is the time remaining inside that ceiling, and the error names both when they differ. A freshly opened headless worker can briefly report `status: "busy"` from `server_health`; REA re-reads health every 100 ms until the worker answers, the caller cancels, or `timeoutMs` has elapsed since the first read. Each health request is capped by the time remaining in that overall wait, so a stalled read cannot extend the readiness deadline by another full request timeout. Expiry while the worker is still busy produces a retryable `provider_timeout`; no further read is started after the deadline. That error names the busy tool from the last observation, or `unknown` when the worker reported none or a value that is not tool-name-shaped. If a health request itself consumes the remaining wait budget, the same last-busy timeout is returned with the stalled request's captured output. Cancellation during the wait produces `cancelled`. Any other unexpected health payload, including a busy payload with an invalid shape, remains `unreadable_output`. Cancellation drains the current upstream request before cleanup; it does not force-stop GUI analysis. An open request that times out without a completed response retains the private workspace because worker inventory alone cannot prove the request has stopped. `binary_session` reports actual tool availability. Repeated doctor/setup calls are unnecessary when the registration already works.
Opening an MCP target starts its selected provider session; headless opening waits for auto-analysis. Set the agent's MCP request deadline long enough for the target. The registration's optional `timeoutMs` defaults to 300000 for upstream requests. An upstream SDK request that reaches this limit returns `provider_timeout` with its operation and actual request budget. A freshly opened headless worker can briefly report `status: "busy"` from `server_health`; REA re-reads health every 100 ms until the worker answers, the caller cancels, or `timeoutMs` has elapsed since the first read. Each health request is capped by the time remaining in that overall wait, so a stalled read cannot extend the readiness deadline by another full request timeout. Expiry while the worker is still busy produces a retryable `provider_timeout`; no further read is started after the deadline. That error names the busy tool from the last observation, or `unknown` when the worker reported none or a value that is not tool-name-shaped. If a health request itself consumes the remaining wait budget, the same last-busy timeout is returned with the stalled request's captured output. Cancellation during the wait produces `cancelled`. Any other unexpected health payload, including a busy payload with an invalid shape, remains `unreadable_output`. Cancellation drains the current upstream request before cleanup; it does not force-stop GUI analysis. An open request that times out without a completed response retains the private workspace because worker inventory alone cannot prove the request has stopped. `binary_session` reports actual tool availability. Repeated doctor/setup calls are unnecessary when the registration already works.
For guided registration, set `REA_IDA_MCP_CONFIG` before `rea setup --client <client>`. Setup includes that file reference in its reviewed plan and preserves it after approval; it does not copy upstream credentials into the agent configuration. For `ida` or `auto` selection, Codex/Kimi setup and doctor use the effective client registration's parsed `timeoutMs` (default 300000 ms) when recommending the agent's per-tool deadline. Existing positive client deadlines remain caller choices. `rea doctor --provider ida --json` validates the registration without starting IDA and explicitly leaves runtime compatibility and target binding for analysis.
@@ -176,37 +160,3 @@ The verifier requires the resolved procedure in both inventory and procedure sea
The initial real workflows use stdio registrations for the legacy Windows GUI profile and a Windows x64 headless supervisor with IDA 9.3. The headless workflow has also passed on macOS arm64 with IDA Professional 9.1 (build 9.1.250226) against the same upstream commit `c133c3853faa111a9b00ee615c013b720d0c4acd`, using a source-built arm64 Mach-O fixture; Mach-O C symbols carry a leading underscore, so the procedure name is `_rea_fixture_add`. Streamable HTTP is tested against a loopback MCP fixture with the pinned client SDK; a real IDA HTTP-supervisor workflow remains unverified. Linux headless engine operation, other IDA releases, architectures, and modern attached GUI tools require their corresponding real workflows before support is claimed.
Native Windows verification uses IDA Pro 9.3 with the public two-function fixture and Windows `whoami.exe`, using upstream `c9d15d98e8a143fc74f6e14037c48f63889b6d6c` with a loopback-listener change. It covers production CLI/MCP parity, distinct-function switching, identity and connection cleanup. See the [correctness and performance measurements](https://github.com/morluto/rea/blob/main/experiments/analysis-efficiency/ida-native-windows.md). This follow-up did not run Windows IDA Free or native macOS.
Headless readiness distinguishes an explicit `auto_analysis_ready: false` from
unknown readiness (`null`). The former returns a retryable capability refusal
with wait-and-retry guidance; the latter asks for upstream readiness inspection
without claiming that analysis is running. Neither is malformed protocol output.
An unusable `workspaceRoot` reports its directory constraint as a configuration
error before opening an IDA worker. Native JSON metadata resources accept omitted
media types and normal case/parameter variants of `application/json`; their
resource URI, JSON structure and recorded target identity remain checked.
The public profile's registration digest excludes HTTP authentication header
values (`Authorization`, `Proxy-Authorization`, and `Cookie`). Mark any other
credential fields explicitly with `"sensitive": { "headers": ["X-Api-Key"],
"env": ["PRIVATE_VALUE"] }` in the registration. Header names match without
case; environment names match exactly. These values are also redacted from IDA
diagnostics. Ordinary fields such as `IDADIR` and noncredential headers remain
part of analysis identity. Credential rotation does not change that public
identity; each newly resolved client factory still captures its own complete
transport registration and creates a fresh connection.
Caller cancellation is classified at the MCP request boundary using the installed
SDK's exact abort outcome. A later abort during error unwinding does not replace
an established provider failure. Cancellation retains the caller's original reason
and collected diagnostics; metadata timeouts name the requested analysis operation
while retaining the resource-read failure as their cause. This request cancellation
does not authorize saving or closing an attached GUI database.
Registration refusals retain schema-authored constraints, including the
native/attached lifecycle requirement, local credential-free endpoint requirement
and absolute workspace root. Configuration diagnostics omit registration values.
Target and private-worker identity refusals retain the failed check, its reason
and recovery guidance in the public error before any analysis replies exist.
These diagnostics do not relax identity admission or authorize closing an attached
GUI database.
+44 -216
View File
@@ -58,22 +58,15 @@ the original rows without reopening or decoding the file:
Collections are `sections`, `images`, `types`, `methods`, `fields`,
`properties`, `events`, `parameters`, `string_literals`, `method_addresses`,
`return_types`, `field_types`, `parameter_types`, `code_gen_modules`,
`generic_method_instances` and `generic_method_issues`. Every returned row includes
`return_types`, `field_types`, `parameter_types` and `code_gen_modules`. Every returned row includes
its source `index`, scoped by artifact SHA-256 and collection. All collections
accept batch `indices` and `fields`. Exact `names` filtering is supported for
`sections`, `images`, `types`, `methods`, `fields`, `properties`, `events`,
`parameters`, `return_types`, `field_types`, `parameter_types` and
`generic_method_instances`. Native type names match the retained rendered display value exactly; every same-name row
keeps its source index, and an unavailable (`null`) name never matches a string.
Types also accept `image_indices`, `namespaces` and
accept batch `indices` and `fields`; collections whose rows have a `name` also
accept exact `names`. Types also accept `image_indices`, `namespaces` and
`declaring_types`; methods, fields, properties and events accept
`declaring_types`. Parameters accept exact `method_indices`, so a batch of
method indices retrieves their parameters without reading the entire parameter
table. `parameter_types` accepts the same `method_indices`, resolved through the
retained `parameters` owners; Evidence without `parameters` refuses that filter. Generic method instances
accept `method_indices` (definition indices) and `states`; generic method
issues accept `rows` (the instance rows they leave unknown). Filters intersect, duplicate indices are returned once, and
retained `parameters` owners; Evidence without `parameters` refuses that filter. Filters intersect, duplicate indices are returned once, and
rows retain metadata order. An empty filter selects no rows; `fields: []`
returns indices only. Omitted fields return the complete selected rows.
@@ -117,9 +110,7 @@ Historical Evidence that predates these member collections remains queryable.
A missing collection has an unknown (`null`) summary count, and requesting it
reports that it was not collected; it is never presented as an empty inventory. The same holds for Evidence that predates binary linking: its summary
omits `binary`, and `method_addresses`, `method_address_states`,
`return_types`, `field_types` and `parameter_types` counts are `null`. A link retained before
generic instances existed omits `binary.generic_methods`, and the
`generic_method_instances` and `generic_method_issues` counts are `null`.
`return_types`, `field_types` and `parameter_types` counts are `null`.
Existing empty collections retain a zero count. Full Evidence is preserved.
`offset` counts matching rows, not source indices. `limit` defaults to 64;
@@ -152,18 +143,8 @@ in [CLI and Evidence](cli.md#run-a-command). Compact delivery requires
`--evidence_output`; existing files are never overwritten. The full export
preserves every decoded row for subsequent queries without the original file.
`--binary PATH` links method addresses, `--architecture arm64|amd64` selects
exactly one slice of a universal Mach-O and `--generic-instances` adds generic
method instances. The report retains the native reader's
`amd64` spelling for x86-64; loader CPU checks use the corresponding `x86_64`
identity. Multiple slices with the same architecture label are refused as
`ambiguous-slice-selection`, including
distinct ARM subtypes that both map to `arm64`. On macOS, use `lipo -info`
to enumerate exact subtypes, then extract the intended subtype into a separate
file, for example `lipo universal -thin arm64e -output thin-arm64e`. Inspect
that thin file without `--architecture`. An exact `arm64e` slice is not
selected by `lipo -thin arm64`. Thinning resolves only selection ambiguity;
the metadata must still match and unsupported pointer encodings remain unsupported.
`--binary PATH` links method addresses and `--architecture arm64|amd64` selects
the slice of a universal Mach-O.
## Observations
@@ -223,25 +204,12 @@ assembly image. Each module record starts with its module name, its method
pointer count and its method pointer array. The reader finds, for every metadata
image, the single record whose name pointer reaches that image's name string and
whose count equals the image's method count. It then requires exactly one
`codeGenModulesCount`/`codeGenModules` pair that lists exactly those records.
`codeGenModulesCount` and module method counts own four bytes, followed by
alignment padding before their eight-byte pointers. Unresolved loader writes
overlapping those four bytes prevent the registration from being established;
padding outside the field is neither required nor interpreted as part of its
count. No symbols are needed, so stripped builds work. A method's token row id
selects its slot in the module's method pointer array. Within one image, each method must
claim a distinct MethodDef token row; duplicate rows are refused before any
linked method table or seed output is published. Row ids may repeat across
different images, and distinct rows may still resolve to the same address when
the binary uses identical-code folding.
For ELF, a repeated load mapping that resolves a complete module record to the
same virtual address and file bytes contributes one record. Distinct virtual
record addresses remain distinct candidates, and conflicting file mappings
remain malformed.
`codeGenModulesCount`/`codeGenModules` pair that lists exactly those records. No
symbols are needed, so stripped builds work. A method's token row id selects its
slot in the module's method pointer array.
Module records establish names through their declared NUL-terminated string pointers. NUL/0xFF padding, spaces, Unicode and suffix-shared storage are supported. A pointer to a longer string does not match just its suffix; a pointer directly into a shared suffix may name it. Exact reversed UTF-8 matching avoids rescanning the file for every name and retains duplicate-name owners until module records resolve them. A contributor investigated a Linux
`GameAssembly.so` from Unity 2023.2.13f1 whose static registration was not located. When no static pair is resolved on x86-64 or arm64, the reader searches executable ranges for bytes consistent with a reference to a module-array candidate: RIP-relative LEA/MOV on x86-64, an adjacent `adrp`/`add` pair on arm64. Exactly one referenced array candidate must remain. `code_gen_modules_field` is then `null`, and Evidence is **inferred**: instruction boundaries, the absence of a static registration elsewhere, and runtime use were not verified. The module records and slots themselves remain file observations. Static-table associations are **derived**. Otherwise the result is `code-registration-not-found`. Cpp2IL's StrippedCodeRegSupport plugin also recovers the module list of an inlined CodeRegistration from the module names; this fallback additionally requires the exact located module set and a single code reference. A Unity 2022.3.62f3 macOS Master build checked by a contributor keeps neither registration symbol; its type table is then inferred as described below.
`GameAssembly.so` from Unity 2023.2.13f1 whose static registration was not located. When no static pair is resolved on x86-64, the reader searches executable ranges for bytes consistent with RIP-relative LEA/MOV references to module-array candidates. Exactly one referenced array candidate must remain. `code_gen_modules_field` is then `null`, and Evidence is **inferred**: instruction boundaries, the absence of a static registration elsewhere, and runtime use were not verified. The module records and slots themselves remain file observations. Static-table associations are **derived**. Otherwise the result is `code-registration-not-found`.
The binary/metadata pair is caller-selected. Name/count/token agreement is structural evidence, not a cryptographic same-build proof. File-offset locations refer to the primary metadata artifact; linked virtual addresses refer to `binary.artifact`. Both SHA-256 identities remain available in compact native pages as `artifact` and `binary_source`.
@@ -271,9 +239,7 @@ The result adds:
`rva` (address minus `image_base`), both hexadecimal strings. `state` is
`code` when the module slot holds an unambiguously mapped executable address, `no-body` when an observed slot is zero,
as for abstract, extern and most generic methods, and `unknown` when no type
range in any image holds the method, a symbol relocation leaves its slot
unresolved, or dyld will bind a Mach-O slot. The raw word in a dyld-bound
slot is not treated as the resolved address.
range in any image holds the method or a symbol relocation leaves its slot unresolved.
`address` and `rva` are `null` unless `state` is `code`. The summary counts
each state in `counts.method_address_states`. Ghidra seeds are written only
for `code` rows.
@@ -282,148 +248,19 @@ The result adds:
field and parameter index with `type`, the index into the binary's
`Il2CppType` table, and `name`, rendered from that table: C# keywords for
primitives (`int`, `string`, `object`), `Namespace.Outer+Inner` for type
definitions, `T[]` for vectors, `T[*]` for rank-one multidimensional arrays,
`T[,]` for rank-two arrays and `T*`, generic instances as
definitions, `T[]`, `T[,]` and `T*`, generic instances as
`System.Collections.Generic.List<string>` and generic parameters by their
metadata name (`TKey`). Nested generic arguments stay with the component
that declares them, for example `Game.Outer<int>+Inner<string>` or
`Game.Outer<int>+Leaf` when the nested type adds no parameters. A trailing
`&` marks a by-reference type. `name` is `null` for type index `-1`, metadata
version 27 as described below, or an unresolved type-table slot. A dyld bind
or unresolved ELF relocation retains the type index without interpreting its
stored word as a resolved pointer. If an unresolved loader value occurs
inside a type graph, linking is refused with `unsupported-pointer-encoding`;
a concrete name is not synthesized from that placeholder.
metadata name (`TKey`). A trailing `&` marks a by-reference type. `name` is
`null` only for type index `-1`.
`binary` also records `types_field` and `types_count`: the address of
`Il2CppMetadataRegistration.typesCount` and its value. That record is found as
the single place whose `fieldOffsetsCount` and `typeDefinitionsSizesCount` both
equal the metadata type-definition count and whose type table is file mapped.
Linking reads only that table. When no record on x86-64 or arm64 has both of
those counts, the reader collects maximal runs of pointers to Il2CppType-shaped
records (an assigned Il2CppTypeEnum kind from 0x01 to 0x1e; class and
value-type data below the type-definition count) that are long enough for every
type index the metadata references. Exactly one such run may exist in the
image, it must start and end inside its mapping (a run touching either end may
continue in a neighbouring mapping), and its start must be loaded by the same
instruction forms as the module-array fallback. `types_field` is then `null`,
`types_count` is that run's length rather than an observed `typesCount`, and
Evidence is **inferred**. The run length is at least every referenced type
index but can differ from `typesCount`: a type-shaped pointer after the table
would lengthen it, and an unreadable slot (such as an unresolved relocation)
would end it early. Only the type indices the metadata references are read. A
second long-enough run anywhere, a run that a type-shaped neighbour extends
before the loaded address, a run touching a mapping end or a slot the loader
binds or leaves unresolved (its stored word is only a placeholder), and a covered
128-byte record whose two counts match but whose other fields are unusable all
leave `metadata-registration-not-found` rather than a guess; the search stops
at the second run. The length requirement only separates the type table from
other pointer runs when the metadata references most of it: on the one Master
build checked, the highest referenced index is 6,031 of 6,576 entries. A
metadata referencing few type indices usually
leaves several short runs and fails closed.
Counts use their declared 32-bit width independently of alignment padding.
The complete 128-byte registration must have unambiguous loader coverage. Its
used fields through byte 112 must be file-backed; a trailing declared zero-fill
range can cover the final unused fields. An unmapped or conflicting tail is
refused. These layouts follow the versioned [Unity 2022.3.8f1 producer header](https://github.com/MlgmXyysd/libil2cpp/blob/master/libil2cpp/Unity_2022.3/2022.3.8f1/il2cpp-class-internals.h#L596-L637)
(redistributed source) and the [AArch64 aggregate alignment rules](https://github.com/ARM-software/abi-aa/blob/2025Q1/aapcs64/aapcs64.rst#aggregates).
Dyld pointer ownership comes from the parsed bind stream, as in Apple's
[dyld analyzer](https://github.com/apple-oss-distributions/dyld/blob/dyld-1165.3/common/MachOAnalyzer.cpp#L1508-L1511), rather than the slot's placeholder word.
The padding, zero-fill-tail and unresolved-type cases have authored regression
fixtures; they do not add real Unity-build verification to the checks above.
Without `binary_path`, `binary` is `null` and the four binary collections are
empty in the complete producer report to indicate unrequested observations. Their summary counts, including state counts, are `null`, and native page requests report unavailable observations. This does not establish an empty native inventory. Missing native fields in historical Evidence remain unknown too.
### Generic method instances
`generic_instances: true` (CLI `--generic-instances`, needs `binary_path`) also
links the instances in `Il2CppMetadataRegistration.genericMethodTable`, as
Il2CppDumper does: each row's `genericMethodIndex` selects an `Il2CppMethodSpec`
(generic method definition plus class and method `Il2CppGenericInst`), and its
`methodIndex` selects a `genericMethodPointers` slot. Without the option both
fields below are `null` (not linked, so unknown), as are the summary counts
`generic_method_instances` and `generic_method_instance_states` and the seed
counts. The result adds:
- `binary.generic_methods`: `generic_method_pointers_field` (the address of
`genericMethodPointersCount`), the pointer, table, method spec and generic
instance counts, `generic_method_table_stride` and `issues` (each with the
`row` it leaves unknown, the `address` of the contradicting row, method spec
or pointer slot record, and a `message`);
- `generic_method_instances`: one row per table entry (row position is the
table index) with `method_spec`, `method` (the recorded definition index,
including invalid values, or `null` when the index cannot be read),
`name`, and `state`, `address` and `rva` as in `method_addresses`: `code`,
`no-body` for a zero pointer slot, or `unknown` exactly when an issue names
the row.
Names follow Il2CppDumper's `GetMethodSpecName`, for example
`System.Collections.Generic.Dictionary<int, string>$$TryGetValue` or
`System.HashCode$$Add<int>`: namespaces only on the declaring type, nested types
joined with `.`, the backtick arity cut, `IntPtr`/`UIntPtr`/`TypedReference`
and no byref mark. These names do not depend on the byref bit, so version 27 is
covered.
Header versions do not separate the CodeRegistration layouts (27.0, 27.1 to
29 and 29.1/31 differ in the fields before `codeGenModulesCount`). Every layout for the header version is tried, and
exactly one must have a `genericMethodPointers` count and an invoker count that
bound every table row; the layout also fixes the row size (12 bytes, or 16 with
an adjustor thunk index). No layout agreeing fails as
`generic-method-registration-not-found`, more than one as
`ambiguous-generic-method-registration`. An empty table selects no layout:
the result has no rows, and `generic_method_pointers_field`,
`generic_method_pointers_count` and `generic_method_table_stride` are `null`.
A table of a few rows can still leave two layouts agreeing, which fails as
ambiguous rather than picking one. Metadata versions 24 and 39 and builds
without a static CodeRegistration (`code_gen_modules_field` `null`) or
MetadataRegistration (`types_field` `null`) fail as
`generic-methods-unsupported`: for version 24 the type-definition generic
container offset and the generic class layout have not been checked against a
real build, so it is refused rather than guessed.
After the layout is fixed, rows that contradict another table do not fail the
whole link: a method spec naming a method or generic instance out of range,
class or method arguments whose count differs from the generic container the
owner declares (none for a non-generic type or method), an owner whose generic
container index is out of range, a name that cannot be rendered, or a pointer slot that is unmapped, has an
unresolved relocation or points outside unambiguous executable file data adds
an `issues` entry, and that row is `unknown` with no address. A method spec
issue also leaves `name` `null`, even when the pointer slot is valid: the slot
would otherwise be reported under a definition nothing confirms. Capacity
refusals are never turned into issues. Rows that share one method spec
(Il2CppDumper refuses such a table) each get an issue at their row address that
names one other sharing row, and all of them lose `name` and become `unknown`.
A zero pointer slot is a `no-body` row and has no issue. Before any row is
resolved, the smallest row for every table entry is checked against the
remaining report budget; each row and issue is then admitted as it is retained.
Compact summaries omit `binary.generic_methods.issues` and keep its count in
`counts.generic_method_issues`; query `collection: "generic_method_issues"`
with `rows`, `indices`, `fields` and pagination. Select only the instance and
issue facts needed for the question. For a small report where both collections
are needed, `detail: "complete"` can cost less than a summary followed by two
pages. Paging saves irrelevant row text but adds calls and schema overhead; it
does not imply lower total latency or memory. See the
[completed-query measurements](verification/il2cpp-generic-instances-2026-10-24.md).
Like method addresses, these rows are derived from static tables; layout agreement
is structural evidence, not proof of runtime use.
Contributor-reported verification against Il2CppDumper's `script.json` (same
`Address` and `Name`):
macOS arm64 and x86_64 slices from Unity 2020.3.49f1, 2021.1.28f1 (version 27),
2021.3.45f2, 2022.3.30f1 (29), 2022.3.62f3 and 6000.0.84f1 (31); Android arm64
from 2022.3.62f3; and the Windows x64 build from 2023.2.13f1 named above
(`GameAssembly.dll` SHA-256
`a3ae7f6ecd1f671297b7d98d12713ef024062d93cd7a4205298102656c93b67a`). On every
build each linked instance matches an Il2CppDumper entry by address and name,
and Il2CppDumper lists no address-name pair that REA leaves out (Windows: 63,750
linked rows, 142 without code; Android: 5,152 and 44). This does not establish
names for versions 24 or 39, for type arguments that are open generic type
definitions, or anything about instances created at runtime. The Linux build
from 2023.2.13f1 keeps no static CodeRegistration and is refused.
For metadata version 27, type names are `null` and only type indices are
reported: versions 27.0 to 27.2 share that header number but place the
Il2CppType byref flag in different bits, so a rendered name could gain or lose
@@ -431,35 +268,29 @@ Il2CppType byref flag in different bits, so a rendered name could gain or lose
The builds checked are listed after the module search rules above.
| Reason | Category | Meaning |
| --------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------ |
| `unsupported-container` | unsupported | Not a 64-bit little-endian Mach-O, PE or ELF image (ELF: AArch64 or x86-64 with dynamic section). |
| `slice-selection-required` | unsupported | A universal Mach-O was given without `binary_architecture`. |
| `ambiguous-slice-selection` | unsupported | More than one universal slice matches the selected architecture label; select an exact subtype externally. |
| `slice-not-found` | unsupported | The requested slice is absent, or a thin binary was given an architecture. |
| `unsupported-pointer-encoding` | unsupported | Mach-O chained fixups or Android packed (APS2) ELF relocations; pointers not decoded. |
| `malformed-container` | input error | The container headers are damaged. |
| `code-gen-modules-not-found` | input error | An image has no module record; often a binary from a different build. |
| `ambiguous-code-gen-module` | input error | More than one module record matches an image. |
| `code-registration-not-found` | input error | No unique module table or executable-byte array candidate was resolved. |
| `invalid-method-token` | input error | A token is outside its module's method-pointer rows, or a MethodDef row is reused within one image. |
| `invalid-method-pointer` | input error | A slot is unavailable or its nonzero address lacks unambiguous executable file coverage. |
| `metadata-registration-not-found` | input error | No single MetadataRegistration or code-loaded type-table candidate was resolved. |
| `invalid-type` | input error | A type index or `Il2CppType` record is out of range, unmapped or malformed. |
| `unsupported-type-kind` | unsupported | A valid type kind has no implemented rendering. |
| `generic-methods-unsupported` | unsupported | Generic instances were requested for metadata version 24 or 39, or a build lacks either static registration. |
| `generic-method-registration-not-found` | input error | No CodeRegistration layout bounds every genericMethodTable row, or the generic tables are unreadable. |
| `ambiguous-generic-method-registration` | input error | More than one CodeRegistration layout bounds every genericMethodTable row. |
| Reason | Category | Meaning |
| --------------------------------- | ----------- | ------------------------------------------------------------------------------------------------- |
| `unsupported-container` | unsupported | Not a 64-bit little-endian Mach-O, PE or ELF image (ELF: AArch64 or x86-64 with dynamic section). |
| `slice-selection-required` | unsupported | A universal Mach-O was given without `binary_architecture`. |
| `slice-not-found` | unsupported | The requested slice is absent, or a thin binary was given an architecture. |
| `unsupported-pointer-encoding` | unsupported | Mach-O chained fixups or Android packed (APS2) ELF relocations; pointers not decoded. |
| `malformed-container` | input error | The container headers are damaged. |
| `code-gen-modules-not-found` | input error | An image has no module record; often a binary from a different build. |
| `ambiguous-code-gen-module` | input error | More than one module record matches an image. |
| `code-registration-not-found` | input error | No unique module table or executable-byte array candidate was resolved. |
| `invalid-method-token` | input error | A method token is not a method row inside its module's pointer count. |
| `invalid-method-pointer` | input error | A slot is unavailable or its nonzero address lacks unambiguous executable file coverage. |
| `metadata-registration-not-found` | input error | No single MetadataRegistration matches the type-definition count. |
| `invalid-type` | input error | A type index or `Il2CppType` record is out of range, unmapped or malformed. |
| `unsupported-type-kind` | unsupported | A valid type kind has no implemented rendering. |
Format and selection failures name `binary_path`; capacity refusals remain typed resource errors. Failed linking preserves the metadata source identity, validated counts, selected binary path/architecture and its SHA-256 when observed, plus prior producer observations. It never publishes an incomplete native table as complete. The binary input is bounded to 1 GiB.
Format and selection failures name `binary_path`; capacity refusals remain typed resource errors. Failed linking preserves the metadata source identity, validated counts, selected binary path/architecture and its SHA-256 when observed, plus prior producer observations. It never publishes an incomplete native table as complete. The binary input is bounded to1GiB.
Scans yield cooperatively in bounded batches. Name-address retention and reverse-trie construction are each limited to 65,536 entries/nodes and fallback module-array candidates to 64 (`native-index-capacity`); exceeding capacity does not prove absence. Rendered type text has a 64 MiB UTF-8 cache-workspace guard, separate from the 64 MiB encoded-report admission, and 64 nested rendering frames (`native-type-text-capacity` / `type-nesting-capacity`). Genuine cycles are malformed; unsupported type kinds are explicit coverage failures. Native row/text admission runs before allocation or retention. Unused generic parameter names are read on demand from the same stable metadata snapshot. There is no cross-inspection file cache; validated immutable Evidence is reused for session views.
Scans yield cooperatively in bounded batches. Name-address retention and reverse-trie construction are each limited to65,536 entries/nodes and fallback module-array candidates to64 (`native-index-capacity`); exceeding capacity does not prove absence. Rendered type text has a64MiB UTF-8 cache-workspace guard, separate from the64MiB encoded-report admission, and64 nested rendering frames (`native-type-text-capacity` / `type-nesting-capacity`). Genuine cycles are malformed; unsupported type kinds are explicit coverage failures. Native row/text admission runs before allocation or retention. Unused generic parameter names are read on demand from the same stable metadata snapshot. There is no cross-inspection file cache; validated immutable Evidence is reused for session views.
ELF RELATIVE writes into zero-fill are kept as a private virtual-pointer overlay. Unresolved supported-width symbol relocations, including RELA PLT entries, remain unknown only for the bytes their ELF ABI relocation type covers; an adjacent 32-bit relocation does not make a separate 64-bit method slot unknown. Stored zeros never establish no-body. COPY, TLS descriptor, REL, packed APS2 and other unsupported relocation encodings fail closed. Input bytes are unchanged.
ELF RELATIVE writes into zero-fill are kept as a private virtual-pointer overlay. Unresolved supported-width symbol relocations, including RELA PLT entries, remain unknown; stored zeros never establish no-body. COPY, TLS descriptor, REL, packed APS2 and other unsupported relocation encodings fail closed. Input bytes are unchanged.
ELF relocation tables and file-backed destinations may span adjacent load ranges when every range agrees on the file translation. Any overlapping zero-fill declaration makes those bytes ambiguous; REA refuses the table or relocation destination instead of choosing the stored file bytes. Missing file bytes remain distinct from conflicting owners: supported unresolved relocations outside file-backed memory retain their unknown overlay, while ambiguous destinations refuse linking. Registration coverage uses the same file/zero-fill ownership checks as pointer reads.
Native address queries batch exact 64-bit hexadecimal `addresses` and `states` (`code`, `no-body`, `unknown`), intersecting indices and pagination; `generic_method_instances` accepts the same filters plus `method_indices` (generic definition indices). Numerically equal address spellings match; original row indices and every shared-body alternative survive:
Native address queries batch exact64-bit hexadecimal `addresses` and `states` (`code`, `no-body`, `unknown`), intersecting indices and pagination. Numerically equal address spellings match; original row indices and every shared-body alternative survive:
```json
{
@@ -498,11 +329,8 @@ loaders place the binary: `0x100000` for an ELF linked at 0, and the link base
for Mach-O and PE; `--seed-base 0x...` overrides it for a different import. When
identical code folding gives several methods one address, the first method names
the function and a `# folded <address> <kept> also <names>` comment line after
the seeds lists the others, so the kept name is visibly one of several. With
`--generic-instances`, linked generic instances are seeded after the method
definitions under their Il2CppDumper-style names; an instance sharing an
address with a definition or another instance is folded the same way. Ghidra still names a seeded function that is a thunk after its
target. The file is atomically published with exclusive creation; an existing destination is never replaced. Failed staging retains its cleanup owner for retry. Source digest and link/seed-base comments make the mapping inspectable. Unknown slots are skipped separately from observed no-body slots. `--seed-base` requires `--ghidra-seeds`; overflowing rebased addresses and names/outputs beyond the existing Ghidra protocol limits (2,000 name characters, 32 MiB file, 1,000,000 entries) are refused before publication. The CLI and reusable service use one formatter and owned output workflow; ordinary MCP inspection remains read-only.
the seeds lists the others, so the kept name is visibly one of several. Ghidra still names a seeded function that is a thunk after its
target. The file is atomically published with exclusive creation; an existing destination is never replaced. Failed staging retains its cleanup owner for retry. Source digest and link/seed-base comments make the mapping inspectable. Unknown slots are skipped separately from observed no-body slots. `--seed-base` requires `--ghidra-seeds`; overflowing rebased addresses and names/outputs beyond the existing Ghidra protocol limits (2,000 name characters,32MiB file,1,000,000 entries) are refused before publication. The CLI and reusable service use one formatter and owned output workflow; ordinary MCP inspection remains read-only.
## Failures
@@ -526,15 +354,16 @@ message names one reason:
| `nested-type-conflict` | A type is listed as nested in two types, in itself, or in a cycle of declaring types. |
| `declaring-type-mismatch` | A method inside a type's method range names a different type, or a method lies outside its type's range. |
| `image-range-overlap` | Two images claim the same type definitions. |
| `invalid-string` | A name index is outside the strings section, its string is unterminated, or it is not valid UTF-8. |
| `invalid-string` | A name index does not start a string, or the name is unterminated or not valid UTF-8. |
A version field that disagrees with the file's layout, for example a version
39 file relabeled as 29, usually surfaces as `unexpected-header-size` or
`record-size-mismatch`.
An empty range's start must lie between -1 and the collection size, which is
stricter than some runtime paths. String indices are byte offsets; a valid
offset may select a suffix of another NUL-terminated name.
Two checks are stricter than the IL2CPP runtime: a name index must start a
string, and an empty range's start must lie between -1 and the collection size.
Every Unity-written file checked so far satisfies both; metadata rewritten by
other tools may be rejected.
An incomplete inventory is never reported as complete. Failures after a verified
snapshot retain partial source Evidence with its SHA-256, byte length and any
@@ -554,11 +383,10 @@ report limit can still require the summary and paging path.
- Decrypting or deobfuscating protected metadata.
- Linking Mach-O binaries that use chained fixups, ELF images with Android
packed (APS2) relocations, and 32-bit images; adjustor thunks and invokers;
generic method instances for metadata versions 24 and 39 or builds without a static
CodeRegistration.
packed (APS2) relocations, and 32-bit images; generic method instances,
adjustor thunks and invokers.
- Full method signatures beyond the rendered field, parameter and return type
names, and type names for metadata version 27.
names, generic method instances, and type names for metadata version 27.
- Running Ghidra or another disassembler. `--ghidra-seeds` only writes a seed
file for [`REA_GHIDRA_SEED_FILE`](installation.md); use the
[native workflow](native-investigation.md) for native code.
+14 -16
View File
@@ -47,24 +47,22 @@ For runtime instrumentation, see the [Frida guide](./frida-instrumentation.md).
## Choose a guide
| Task | Guides |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Identify a desktop installation | [Runtime candidates, evidence and follow-up files](desktop-applications.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [PE TLS/load config](pe-load-config.md), [ELF notes](elf-notes.md), [Linux kernel modules](kernel-modules.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [PE TLS/load config](pe-load-config.md), [ELF notes](elf-notes.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Select a native provider or import format | [Ghidra semantics](ghidra-provider.md), [Windows](windows-ghidra-p0.md), [DOS](ghidra-dos.md), [raw bytes](raw-binary.md), [GameCube DOL](ghidra-dol.md), [MIPS](ghidra-mips.md), [PSP](ghidra-psp.md), [NativeAOT](ghidra-nativeaot.md), [IDA](ida-provider.md), [Kuna](kuna-provider.md), [JEB](jeb-analysis.md) |
| Analyze JavaScript or Electron artifacts | [Static analysis](javascript-artifact-reconstruction.md), [tracing and comparisons](javascript-application-workflows.md), [application graph](javascript-application-graph.md), [source recovery](javascript-recovery.md) |
| Compare static JavaScript with runtime observations | [Runtime attribution](javascript-runtime-observation.md), [reconciliation](javascript-runtime-reconciliation.md), [Electron observation](electron-observation.md), [Electron fuses](electron-fuses.md) |
| Inspect assemblies, packages, or firmware | [Godot PCK](godot-pck.md), [.NET](managed-code-analysis.md), [Python packages](python-packaged-applications.md), [Android](android-analysis.md), [APK signatures](apk-signature.md), [Apktool resources](apktool-resource-analysis.md), [ADB acquisition](adb-device-analysis.md), [Apple](apple-application-analysis.md), [Harmony](harmony-analysis.md), [firmware](firmware-analysis.md), [WASM](wasm-artifacts.md), [EVM](evm-bytecode.md) |
| Observe or compare a browser scenario | [CDP observation](browser-observation.md), [scenarios](browser-scenario-contract.md), [runtime evidence](web-runtime.md), [historical captures](web-network-captures.md) |
| Serve recorded behavior as an agent environment | [Agent environments](agent-environments.md) |
| Trace captured website source | [Source locations](web-source-location.md), [module traces](website-module-trace.md), [script export](website-script-export.md) |
| Capture and compare a command | [Process capture](process-capture.md) |
| Use CLI/MCP and retain evidence | [CLI](cli.md), [MCP contracts](mcp-contracts.md), [agent prompts](mcp-prompts.md), [obligation ledgers](reconstruction-obligation-ledgers.md) |
| Task | Guides |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Identify a desktop installation | [Runtime candidates, evidence and follow-up files](desktop-applications.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Select a native provider or import format | [Ghidra semantics](ghidra-provider.md), [Windows](windows-ghidra-p0.md), [DOS](ghidra-dos.md), [raw bytes](raw-binary.md), [MIPS](ghidra-mips.md), [PSP](ghidra-psp.md), [NativeAOT](ghidra-nativeaot.md), [IDA](ida-provider.md), [Kuna](kuna-provider.md), [JEB](jeb-analysis.md) |
| Analyze JavaScript or Electron artifacts | [Static analysis](javascript-artifact-reconstruction.md), [tracing and comparisons](javascript-application-workflows.md), [application graph](javascript-application-graph.md), [source recovery](javascript-recovery.md) |
| Compare static JavaScript with runtime observations | [Runtime attribution](javascript-runtime-observation.md), [reconciliation](javascript-runtime-reconciliation.md), [Electron observation](electron-observation.md), [Electron fuses](electron-fuses.md) |
| Inspect assemblies, packages, or firmware | [Godot PCK](godot-pck.md), [.NET](managed-code-analysis.md), [Android](android-analysis.md), [Apktool resources](apktool-resource-analysis.md), [ADB acquisition](adb-device-analysis.md), [Apple](apple-application-analysis.md), [Harmony](harmony-analysis.md), [firmware](firmware-analysis.md), [WASM](wasm-artifacts.md), [EVM](evm-bytecode.md) |
| Observe or compare a browser scenario | [CDP observation](browser-observation.md), [scenarios](browser-scenario-contract.md), [runtime evidence](web-runtime.md), [historical captures](web-network-captures.md) |
| Serve recorded behavior as an agent environment | [Agent environments](agent-environments.md) |
| Trace captured website source | [Source locations](web-source-location.md), [module traces](website-module-trace.md), [script export](website-script-export.md) |
| Capture and compare a command | [Process capture](process-capture.md) |
| Use CLI/MCP and retain evidence | [CLI](cli.md), [MCP contracts](mcp-contracts.md), [agent prompts](mcp-prompts.md), [obligation ledgers](reconstruction-obligation-ledgers.md) |
For development, use [testing](testing.md), [client verification](client-verification.md),
[tool design](tool-design.md), [admission limits and recovery](design/limits-and-recovery.md),
and [releasing](releasing.md).
[tool design](tool-design.md), and [releasing](releasing.md).
[Reconstruction readiness](reconstruction-readiness.md) describes a development
conformance verifier. [The 6.2 migration](migration-6.2.md) records that release's
compatibility changes; [the roadmap](roadmap.md) tracks future work.
+45 -327
View File
@@ -24,10 +24,6 @@ Restart the agents named in the completion message. Quit Claude Desktop fully
before approving, because a running app can overwrite the registration; on
Windows, see [where Claude Desktop reads its configuration](client-configuration.md#claude-desktop).
During package resolution, npm may print a deprecation warning for `prebuild-install`
from transitive native dependencies (such as Frida). This warning originates from
the upstream dependency tree and does not affect REA or indicate setup failure.
For the latest published release, use `npx rea-agents@latest setup`.
[Package-runner and global-installation options](#package-runners-and-global-installation)
cover explicit versions, Bun, runtime requirements, and the curl wrapper.
@@ -60,10 +56,7 @@ Before applying changes, REA checks your current configuration. The plan lists:
Malformed or unsafe existing configuration blocks the whole transaction before
Hopper installation or any file write. Declining or pressing Ctrl-C makes no
changes. Client configuration files and bundled skill files have separate
publication and recovery paths; the guarantees in this paragraph apply to
client configuration. See [skill destinations](#skill-destinations) for skill
rollback limits. Agent configuration writes preserve unrelated entries and comments,
changes. Agent configuration writes preserve unrelated entries and comments,
create backups, stage changes in a private directory, and verify their result.
Before publication, they refuse detected changes to the selected configuration.
Replacement uses an explicit destination under the selected canonical parent;
@@ -88,11 +81,6 @@ Setup also retains valid environment overrides in REA's existing server entry,
such as `REA_MCP_INPUT_SCHEMA_PROFILE`, `REA_MCP_OUTPUT_SCHEMA_PROFILE`, and
`REA_MCP_MAX_RESPONSE_BYTES`.
Detected provider settings replace only matching environment keys.
Setup manages REA's launcher, selected provider settings, and recommended timeout
defaults. Codex approval policies, tool selections, and other caller-owned server
settings are preserved. Known malformed policy values block setup before it
writes the configuration or a backup; see
[Codex settings preservation](client-configuration.md#codex-and-devin).
Setup and doctor report a malformed REA environment map or invalid runtime
setting before setup writes files. Repair the named setting in the client
configuration; setup does not silently discard the rest of the map.
@@ -112,11 +100,9 @@ your investigation. Failed steps and diagnostics remain in terminal history.
Select exact clients in scripts with repeatable `--client` flags. Each explicit
client skips interactive selection. Use `--all-detected` only when you intend
to configure every detected supported client. Combining `--client` with
`--all-detected` configures both the explicitly selected and detected clients,
even when an explicit client is not detected. Each client is configured once.
`--skill=false` omits the workflow, and `--dry-run` returns a read-only plan
with status `planned` and exit code 0:
to configure every detected supported client. `--skill=false` omits the
workflow, and `--dry-run` returns a read-only plan with status `planned` and
exit code 0:
```bash
rea setup --client codex --client cursor --skill=false --dry-run
@@ -143,15 +129,6 @@ exits 1. Installing missing Hopper non-interactively additionally requires
rea setup --yes --all-detected --install-hopper --json
```
Client configuration writes detect changes to the file read by setup, including
when a matching recovery backup already exists. A detected conflict reports
`reason: "conflict"` and preserves the newer file; review it and rerun setup.
Rollback likewise requires the file identity and content installed by that
transaction. Requests for the same resolved path are serialized within one REA
process. These checks do not provide an operating-system compare-and-swap or
coordinate arbitrary external writers, so avoid editing client configuration
while setup is applying it.
### Skill destinations
The bundled skill is installed in each selected client's personal skill directory.
@@ -170,26 +147,6 @@ paths as `unmanaged`, names the affected paths, and provides those alternatives;
an unreadable skill identity remains `unknown`. Uninstall retains and reports
files it cannot safely remove, including their ownership manifest.
The skill installer preserves byte-exact backups of changed existing files,
including invalid UTF-8, BOMs, and permission modes, and verifies each published
file through its owned descriptor. Before publication it checks the selected
file identity; first installation uses an exclusive hard link and refuses a
concurrently created destination. Filesystems without hard-link support cannot
publish a first-install skill file.
Rollback restores an existing file only while its published identity, state,
and bytes still match that installation attempt. A recovery backup preserves
the original bytes; it does not authorize overwriting a later user edit or
replacement. Conflicts report the affected paths and recovery backups. If a
newly created skill file cannot be verified, setup retains it and reports manual
recovery rather than deleting by pathname. Staging cleanup failures remain
owned and retryable, and setup does not report installation success while
cleanup is unresolved.
These checks do not lock out another writer modifying the same inode or exclude
pathname changes between the last check and publication. Avoid editing managed
skill files or running another installer while setup applies them.
`rea setup --skill-client claude_code --yes --json` refreshes and verifies the
selected skill destination independently of MCP registration selection. The
final `doctor.scope.skill_clients` records which skill destinations were
@@ -319,14 +276,14 @@ Register with the variable unset:
<!-- x-release-please-start-version -->
```bash
claude mcp add --scope local rea -- npx -y rea-agents@6.8.0 mcp
claude mcp add --scope local rea -- npx -y rea-agents@6.7.0 mcp
```
On native Windows, use the same project directory and wrap the package-runner
shim so the stdio client can launch it:
```powershell
claude mcp add --scope local rea -- cmd.exe /d /c npx -y rea-agents@6.8.0 mcp
claude mcp add --scope local rea -- cmd.exe /d /c npx -y rea-agents@6.7.0 mcp
```
<!-- x-release-please-end -->
@@ -423,13 +380,8 @@ rea uninstall --purge-data
```
Uninstall preserves Hopper, Node.js, Evidence files, captures, unrelated skills
and other MCP servers. Purging removes REA's legacy cache and state under
`~/.rea`, the default Kuna cache (`~/Library/Caches/rea/kuna` on macOS or
`${XDG_CACHE_HOME:-~/.cache}/rea/kuna` on Linux), and Linux Hopper release
archives under `${XDG_CACHE_HOME:-~/.cache}/rea/hopper-releases`. Other cache
namespaces remain intact. A caller-selected `REA_KUNA_CACHE_DIR` is retained
and reported because REA cannot establish ownership of its entire contents.
A client configuration that is malformed, unreadable, or at an unsafe
and other MCP servers. Purging removes only REA's cache and state under
`~/.rea`. A client configuration that is malformed, unreadable, or at an unsafe
path stops the operation before anything is removed, and a client that fails
while being updated stops the remaining removals. Uninstall reports each
candidate `configuration_path` with its outcome. A client with multiple files
@@ -446,65 +398,12 @@ symbolic link is retained and reported rather than followed. Uninstall checks
REA's managed shared and Claude Code personal skill locations. See the
[CLI guide](cli.md#output-and-exit-status) for exit statuses.
### Missing dependencies in an npx installation
If REA exits with `ERR_MODULE_NOT_FOUND` before `doctor` can produce a report,
inspect the missing path in the error. A path under npm's `_npx/<key>/node_modules`
identifies a package-runner installation. In [#5259](https://github.com/morluto/rea/issues/5259),
the missing file was `@modelcontextprotocol/client/dist/index.mjs`. That file is
present in a clean Windows installation of REA 6.7.0; its absence alone does not
establish an incompatible dependency release or explain how the file went missing.
`npm cache verify` checks npm's downloaded content cache; it does not restore
missing files in an already extracted npx installation. Repeating the same npx
command can reuse that incomplete installation. To reinstall only the affected
entry:
1. Stop the REA processes and agent clients using that entry. In the same shell
environment as the failing command, identify the cache and its entries:
```powershell
npm.cmd config get cache
npm.cmd cache npx ls
npm.cmd cache npx info REA_CACHE_KEY
```
Replace `REA_CACHE_KEY` with the key from the failing `_npx` path. Check that
`info` reports that location and the expected `rea-agents` version. Its
`valid` label describes the cache entry metadata, not the presence of every
installed file. These subcommands require npm with `cache npx` support.
2. Remove that specific entry and rerun the affected version with registry
access. For the version reported in #5259:
```powershell
npm.cmd cache npx rm REA_CACHE_KEY
npx.cmd --yes rea-agents@6.7.0 doctor --json
```
Keep the key argument: this recovery does not require clearing all npm caches
or deleting REA configuration, skills, or Evidence. On Linux/macOS, use `npm`
and `npx` without `.cmd`. A JSON doctor report confirms startup recovered;
it can still exit nonzero for missing analysis engines or registrations.
Restart the affected agent after repairing its cache entry.
If a fresh installation still loses the file, retain the error, REA/Node/npm
versions and installation details in the issue. Check installation logs and,
on Windows, security software's protection history before attributing the cause.
Do not rename `index.cjs` to `index.mjs` or disable protection to make startup pass.
### Update the owning installation
`rea update` installs the exact resolved
release into the npm prefix or Bun global directory that owns the running
package, then checks the new executable's version before reporting success.
That verification covers the CLI version; it does not establish readiness of
optional native providers such as Frida. Updates keep dependency lifecycle
scripts disabled, so a native binding that needs an install or build step may
remain unavailable. A later provider command reports its own load failure;
retain that diagnostic and the owning installation path before choosing a manual
repair. The updater does not download or rebuild the binding. It does not reopen
onboarding. For npm installations, release lookup and
package, then checks the new executable's version before reporting success. It
does not reopen onboarding. For npm installations, release lookup and
installation both use npm's configured registry. Bun global updates require
Bun 1.2.17 or later, which introduced `bun info`. The updater checks `bun --version`
first and stops with an instruction to upgrade Bun manually if the version is
@@ -616,12 +515,9 @@ hosted connector; see [its configuration](client-configuration.md#other-client-s
| WorkBuddy | `workbuddy` |
| ZCode | `zcode` |
| DeepSeek Harness | `dsh` |
| FreeBuff | `freebuff` |
`droid`, `dsh`, `kimi`, `qoder`, `trae`, `workbuddy`, and `zcode` require
`rea-agents@6.5.0` or later. The 6.4.0 registry check above does not cover them.
`freebuff` requires `rea-agents@6.8.0` or later. Older releases reject
`rea setup --client freebuff` as an unsupported client.
See [client configuration and troubleshooting](client-configuration.md) for
configuration paths, profiles, trust policies, and schema-limit workarounds.
@@ -640,18 +536,13 @@ Hopper is separate commercial software with its own license. Its free demo has
vendor-defined limits, and a paid license is optional. REA reuses any detected
installation and preserves Hopper during uninstall.
The automated Hopper installer supports macOS 12+ and x86_64 hosts running
Ubuntu 24.04+, Fedora 41+, Nobara 44+, Arch Linux, CachyOS, or Omarchy. The
official Linux packages are x86_64-only, so setup does not offer Hopper on other
Linux architectures; configure [Ghidra](#ghidra) there instead. REA itself runs on any
The automated Hopper installer supports macOS 12+, Ubuntu 24.04+, Fedora 41+,
Nobara 44+, 64-bit Arch Linux, CachyOS, or Omarchy. REA itself runs on any
Linux distribution with a recognized apt, dnf, or pacman package family, where
an existing Hopper launcher is selected through `HOPPER_LAUNCHER_PATH`. Ghidra
and IDA have their own provider-specific host requirements; Windows Ghidra uses
the [experimental P0 boundary](windows-ghidra-p0.md).
Hopper metadata and package downloads require HTTPS throughout the redirect
chain; a redirect to HTTP is refused before that destination is requested.
On macOS, approved setup downloads the official DMG, checks its published size
and digest, validates the application bundle, and atomically installs it to
`~/Applications/Hopper Disassembler.app`. REA then opens Hopper so the
@@ -684,12 +575,6 @@ search permissions (`0700`) on a directory you own before retrying.
On supported Linux distributions, approved setup verifies Hopper's official
`.deb`, `.rpm`, or Arch package before invoking the native package manager.
On apt systems, Hopper's runtime dependencies are installed with
`--no-upgrade` in their own transaction, so setup no longer names an
already-installed dependency as an upgrade target. apt can still upgrade a
package that the Hopper package or the Python library repair requires at a
newer version. Arch's `pacman -S --needed` still updates an outdated
dependency.
The official release feed selects the current public demo without a version
pin. REA verifies the package's detached PGP signature against Hopper's stable
primary fingerprint `7C2D73BAFAF5B9A710A69DA1A26DF815099B1BCD`, then checks
@@ -747,24 +632,10 @@ manual dependency repair.
When the package-manager step fails, setup reports the failing command and its
captured output, so a package conflict is distinguishable from an authorization
failure. Hopper is optional, so setup still applies the approved client
registrations and skill, then reports `needs_human` with the Hopper failure. On
Arch Linux and CachyOS, the unversioned compatibility package `zlib-ng-compat`
may conflict with Hopper's versioned `zlib` dependency. Resolve the package
conflict yourself, install Hopper's official package manually, or use another
provider such as Ghidra; rerun `rea setup` after installing Hopper.
If setup cannot finish releasing its resources, a normally returned structured
result reports `needs_human` and includes `cleanup.reason` with the identified
paths in `cleanup.resources`. A thrown typed failure reports the same retained
paths in `details.resources`, preserving the primary failure separately. Reasons
that do not identify a resource remain explicit without guessing paths from text.
For in-process callers retaining REA's setup host, a successful retry of its close
updates the original failure's cleanup state and removes released resource labels.
Custom `SetupHost` implementations must bind returned or thrown cleanup errors
to their resource owners to provide the same live recovery. An ownerless error
remains an unconfirmed snapshot: `runSetup` does not observe later direct calls
to the caller's port or replace that port's methods.
failure. On CachyOS, an unversioned compatibility package such as
`zlib-ng-compat` may conflict with Hopper's versioned `zlib` dependency. Resolve
the package conflict yourself or install Hopper's official package manually,
then rerun `rea setup`.
The supported Linux demo forwards additional launches into its existing
application, even across private displays. REA therefore reserves one Linux
@@ -798,10 +669,8 @@ Automatic discovery accepts executable regular files, including file symlinks,
and skips directories.
On Linux, it prefers executable `/opt/hopper/bin/Hopper`, then executable
`~/.local/share/rea/hopper/bin/Hopper`, followed by `/opt/hopper-v4/bin/hopper`
and `/usr/bin/hopper`; if none exists, `/opt/hopper/bin/Hopper` remains the
diagnostic fallback. `rea doctor` checks only the current platform's candidates, in the
same order; other platforms have no automatic candidates.
`HOPPER_LAUNCHER_PATH` overrides those choices:
and `/usr/bin/hopper`; if none exists, the former remains the diagnostic
fallback. `HOPPER_LAUNCHER_PATH` overrides those choices:
```bash
export HOPPER_LAUNCHER_PATH=/absolute/path/to/Hopper
@@ -812,10 +681,7 @@ Hopper startup allows 120,000 ms for launch, initial analysis, and authenticated
bridge readiness. For large targets, set `REA_HOPPER_STARTUP_TIMEOUT_MS` to a
decimal integer from 1 to 2,147,483,647 milliseconds in the environment that
starts REA. CLI and MCP use the same setting; invalid values fail configuration
validation. In MCP, `update_analysis_settings` can set
`hopper.startup_timeout_ms` for the next startup attempt without restarting REA;
accepted work keeps its budget and retained cleanup must still complete. An
MCP client's own request timeout must also allow enough time.
validation. An MCP client's own request timeout must also allow enough time.
`open_binary` validates and selects a target without waiting for native provider
readiness. During Hopper startup, `binary_session.provider_operation_health`
reports `busy` at stage `launch`; `analysis_activity` describes bridge requests
@@ -954,11 +820,6 @@ When Hopper cannot start in CI, run
`rea doctor --provider hopper --json` in the failing runner.
Structured failures distinguish private-display dependencies, an unsupported
demo dialog or build, process-ownership conflicts, and an early lifecycle exit.
If a private-display probe times out or is cancelled and its process cleanup
fails, REA retains that exact probe owner and reports `cleanup_incomplete`.
Session close retries the probe before releasing its cleanup marker; doctor
uses its own retained resource scope for the same retry. A probe timeout whose
process has already been released remains an ordinary startup diagnostic.
Apply the reported remediation rather than exposing the runner's desktop,
copying license secrets into logs, or killing unrelated Hopper processes.
@@ -992,30 +853,6 @@ dossiers, instructions, recovered data types, measured load mappings, loaded
memory bytes, and observed file offsets. Independent load-image attestation
supports DOS MZ, explicitly selected COM, and explicit raw-binary imports;
PE returns measurements with an unsupported attestation status.
ELF target opening checks the declared program and section header tables and
their file-backed ranges before selecting a provider. A declaration beyond EOF
is refused with its range and the actual file size, including a file cut one
byte short. This also refuses carved or deliberately damaged copies that retain
out-of-file declarations; they are not presented as complete smaller programs.
Files declaring no tables remain valid inputs, subject to the provider's lane.
Inactive headers, zero-length ranges and `SHT_NOBITS` memory-only sections do
not require stored payload bytes. Extended counts come from section zero.
The check streams header records in chunks of at most 64 KiB; it does not decode
section names, copy payloads or impose the metadata decoder's structural budget.
Passing it establishes physical range consistency, not complete function
discovery, loader validity or runtime behavior. Existing ISA/ABI checks remain
separate, including the bounded recordless MIPS lane.
Target admission preserves recognized PE/NE/LE/LX signatures separately from
reader layout support. A Windows header overlapping the first 64 DOS-header
bytes remains an unsupported layout in this release; it is not reported as an
invalid offset. Tiny PE files with short optional headers have additional
unverified loader requirements. A nonzero Windows-header pointer without a
recognized signature remains an unresolved candidate, with independently
validated DOS facts in the target error diagnostics; those facts do not prove a
Windows header absent. When a nonempty relocation table ends inside a complete
DOS header, the remaining header bytes are DOS padding, so a nonzero dword
there is not a Windows declaration. An unread signature beyond the current
probe is requested before that padding rule applies.
On Linux and macOS, `annotate_native_function` also edits a function name and/or
entry comments atomically and returns refreshed analysis, and `set_address_name`
and `set_addresses_names` name a function entry or any other mapped address
@@ -1047,10 +884,7 @@ selected formula's `libexec/openjdk.jdk/Contents/Home`, for example
`/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home` or
`/usr/local/opt/openjdk/libexec/openjdk.jdk/Contents/Home`. Use a JDK inside the
Java version bounds declared by your Ghidra installation; REA does not select
another JDK when `JAVA_HOME` is explicit or the PATH JVM runs. On macOS,
when the PATH launcher cannot start a JVM, it tries executable Homebrew
`opt/openjdk` candidates, preferring the host architecture's prefix. Failed
discovery probes remain available in installation diagnostics. Doctor distinguishes an absent Java
another installed JDK automatically. Doctor distinguishes an absent Java
command from a command that runs and fails, and retains bounded probe output
and any parsed properties when the selected runtime cannot be admitted.
@@ -1070,17 +904,6 @@ installation remains unavailable on Windows. Setup never installs Ghidra,
Java, or Python. It preserves valid detected Ghidra/JDK settings in agent
registrations. See [Windows Ghidra P0](windows-ghidra-p0.md) for provider diagnostics.
The separate `rea mcp add` builtin currently uses Incur's package-runner child
launcher, which can fail on Windows with `MCP_ADD_FAILED: spawn npx ENOENT`
before registering a client. REA's setup path is unaffected. For a client supported
by setup, review `rea setup --client opencode --skill=false --dry-run --json`
(substitute its client ID), then use the same scoped command without `--dry-run`
and approve the displayed plan. Non-interactive application additionally needs
`--yes`. This configures setup's user-level client location; it does not replace
`mcp add --no-global`, arbitrary `--command` registrations, or unsupported clients.
The builtin launcher defect remains pending a dependency fix; a successful setup
run does not verify that `mcp add` works on Windows.
When `GHIDRA_INSTALL_DIR` is unset, doctor and the Ghidra provider also search
`PATH` for `ghidra` or `ghidraRun`, resolve the selected launcher through its
real path, and accept it only when that location belongs to a Ghidra tree with
@@ -1096,23 +919,9 @@ It also checks that the reported JVM `os.arch` and `os.name` map to the same nat
directory validated for the host; unrecognized reported architectures or a
non-host operating system are refused. LaunchSupport overrides of either
property, including through helper-environment JVM option variables, are
rejected at launch. `JDK_JAVA_OPTIONS` argument files (`@file`) are expanded
and inspected with that same check using the Java launcher's argfile quoting,
comments and continuation rules. The reader admits regular UTF-8 files, retains
failed descriptor cleanup, and bounds all admitted file contents and the prepared
environment to 64 KiB per launch. Unreadable, oversized or nested `@` references
are refused before the analysis JVM starts. Validated bytes are snapshotted under
the owned runtime root, so later helpers and the analysis JVM do not reopen mutable
caller files. Repeated references share that launch's snapshot; no live state is
cached across launches. `JAVA_TOOL_OPTIONS`
and `_JAVA_OPTIONS` do not expand `@file` tokens, so they remain
literal-token checks.
rejected at launch.
When Java is found through `PATH`, setup records its observed JDK home so GUI
MCP clients do not depend on an incidental shell path. A caller-selected
`JAVA_HOME` spelling is preserved when it resolves to that observed JDK. On
macOS, a discovered Homebrew Cellar home uses the formula's stable `opt` alias
only when both resolve to the same JDK. The Java check retains the JVM-reported
home as evidence. Setup shows every exact
MCP clients do not depend on an incidental shell path. Setup shows every exact
environment entry in its plan, writes only after approval, and never downloads,
installs, upgrades, or modifies Ghidra or Java.
@@ -1185,25 +994,19 @@ can time out earlier even when Ghidra would complete within its startup deadline
See [Ghidra first-query deadlines and recovery](mcp-contracts.md#ghidra-first-query-deadlines-and-recovery)
for client options and the close/reopen recovery flow.
The 330-second default can be changed on a running MCP server by setting
`ghidra.startup_timeout_ms` through `update_analysis_settings`. The next accepted
operation that starts a provider uses that value; an accepted startup keeps its
original budget. Changing an environment variable in another shell, or closing
and reopening a binary, does not change the launch configuration already read
by that server.
The 330-second default is configurable. Configuration is read when REA starts:
changing an environment variable in another shell, or closing and reopening a
binary in the same MCP server, does not change that server's startup allowance.
For a CLI retry with an example 30-minute allowance:
```sh
REA_GHIDRA_STARTUP_TIMEOUT_MS=1800000 rea analyze /absolute/path/to/app.exe --provider ghidra --json
```
For MCP, update the runtime setting and resolve any retained cleanup before the
next startup attempt. Follow the error's close/reopen guidance where needed;
the settings update does not clear cleanup blockers. To change the launch
environment instead, export useful Evidence, close the binary and resolve any
cleanup failure, update the client's REA server launch configuration, then
restart REA and reconnect. Allow startup plus the query in the client's own
tool-call deadline; runtime updates do not change that deadline. Each new startup attempt uses
For MCP, export useful Evidence before restarting, close the binary and resolve
any cleanup failure, update the environment in the client's REA server launch
configuration, then restart REA and reconnect before reopening the target. Allow
startup plus the query in the client's own tool-call deadline. Every retry uses
a fresh ephemeral import; smaller result requests do not reduce the prerequisite
auto-analysis. A larger time allowance does not raise the JVM heap limit or
guarantee completion. Keep [heap and host CPU controls](#ghidra-heap-and-cpu-controls)
@@ -1214,33 +1017,7 @@ One session contains exactly one imported Program; use `provider_id: "ghidra"`,
One persistent decompiler is owned by the Program, and a serial queue keeps
Ghidra API calls on the owning Program thread without a fixed queue length.
Operations run until a result, caller cancellation, or provider shutdown; there
is no fixed per-operation or response-size ceiling. Cancelling an operation that
is already running stops the Ghidra session, so one function whose
decompilation runs past the MCP client's deadline costs the whole import.
`REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS` (an integer from 1 to 2,147,483; absent by
default, which leaves decompilation unbounded) bounds each native decompile
instead. This maximum keeps Ghidra’s signed 32-bit seconds-to-milliseconds
conversion from overflowing. The client must still support a deadline covering
startup, that limit and operation headroom. Ghidra stops that one decompile and starts a fresh decompiler process
for the next request. `analyze_function` still returns the function's assembly,
references and basic blocks, with pseudocode, `native_api` and
`native_value_flow` unavailable for the reason "exceeded the N-second limit",
and that dossier is not saved to analysis snapshots. `procedure_pseudo_code`
fails with `provider_timeout` and that message; other Ghidra tools stay
available. If the caller also cancels while the timeout settles, the session
still closes and the message says so. Session reuse concerns an active MCP or
library session; a standalone CLI command still closes its own session when it
finishes. In MCP, `ghidra.decompile_timeout_seconds` can override the launch
value for newly accepted operations on the existing Program; explicit `null`
removes the deadline. A transient timeout remains uncached so the same function
can be retried after an update. See the
[settings design](design/limits-and-recovery.md#runtime-settings-through-mcp).
Codex/Kimi registrations derived by `rea setup` add the selected launch value
to the tool deadline as a single-decompile estimate; a runtime update does not
change that registration. A bulk call can need that limit for
every attempted function plus listing and file work; select the client deadline
for the workload, split batches, use `decompile_all` limit/resume, or reduce
`trace_native_values` `max_functions` to select fewer serial decompiles. Ghidra's own interactive default is 30 seconds. Unresolved computed calls
is no fixed per-operation or response-size ceiling. Unresolved computed calls
remain unknown, reference-kind provenance is preserved, and provider-specific
pseudocode is never treated as original source or Hopper-equivalent text.
@@ -1334,22 +1111,17 @@ override in `analyzer_preset` (for example
open when its committed profile and the setting differ. A name starts with a
letter or digit and contains only letters, digits, spaces, periods, underscores,
and hyphens; at most 32 names are admitted, each at most 120 characters.
The setting applies to every import in the REA server, but Ghidra registers
format-specific analyzers, such as `Objective-C Message Analyzer` or
`Demangler Swift`, only for programs they can analyze. A name that the Ghidra
installation provides but did not register for the selected program is not
applicable: the import continues, the profile still commits the configured
names, and result limitations report the applied preset with that name marked,
for example `ghidra-default+disabled:Objective-C Message Analyzer(n/a)`. A name
that no installed analyzer provides, or an option that remains enabled, aborts
the headless import before auto-analysis and the bridge with an error naming
`REA_GHIDRA_DISABLED_ANALYZERS` and the analyzer, so a misspelled override
cannot produce a session claiming that the analyzer was disabled. Changing the
environment requires restarting the server; reopening a target alone keeps the
current setting. For Windows PE resource analysis, the exact name is
`WindowsResourceReference`. Disabling an analyzer skips the facts it would have
produced.
Names must identify analyzers registered for the selected program. Unknown names
or options that remain enabled abort the headless import before auto-analysis
and the bridge, so an unsuccessful override cannot produce a session claiming
that the analyzer was disabled. The setting applies to every import in the REA
server. A format-specific analyzer may not be registered for another target, so
the same override can prevent that target from opening. For mixed-target work,
omit the override or use a separate REA server configured for the affected
format. Changing the environment requires restarting the server; reopening a
target alone keeps the current setting. For Windows PE resource analysis, the
exact name is `WindowsResourceReference`. Disabling an analyzer skips the facts
it would have produced.
### Ghidra heap and CPU controls
@@ -1417,10 +1189,8 @@ truncation markers. Darwin ownership-helper compilation failures also retain
the compiler outcome, exit code or signal, and bounded stderr; a timed-out
compiler remains distinguishable from a compiler that exited with an error.
The helper compile deadline defaults to 120,000 ms; set
`REA_SWIFTC_COMPILE_TIMEOUT_MS` to an integer between 1 and 2,147,483,647 in
the environment that starts REA when a busy host needs longer. Restart REA to
apply a change; the next ownership-helper compile uses the new value. A
timeout names that effective deadline.
`REA_SWIFTC_COMPILE_TIMEOUT_MS` to an integer between 1 and 2,147,483,647 when
a busy host needs longer.
On macOS, the Java prerequisite probe waits for this independently bounded
preparation before starting its five-second command deadline. Caller cancellation
still interrupts preparation, and failed helper cleanup remains owned for retry.
@@ -1471,7 +1241,7 @@ For a client that requires manual configuration, use:
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents@6.8.0", "mcp"]
"args": ["-y", "rea-agents@6.7.0", "mcp"]
}
}
}
@@ -1485,13 +1255,6 @@ setup runs through `bunx`, and pins it to the exact version that performed
setup. Run current setup to refresh an older registration, then restart the
client.
Package runners key their package cache by the requested spec, so the entry
installed for the `@latest` invocation that ran setup cannot serve the pinned
registration. Setup therefore materializes the pinned entry once, best-effort
with a bounded timeout, before finishing. When that step cannot run, for
example offline, the first client start performs the one-time install inside
the client's MCP startup deadline and may need one more restart.
## Package runners and global installation
A package-runner invocation can use a REA version installed in the current project. To
@@ -1523,7 +1286,7 @@ equivalent entry point. Setup run through `npx` registers
`cmd.exe /d /c` so clients can start the runner's command shim. Neither
registration points into the runner's temporary package cache.
REA supports Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), and 26.12.1+. Node.js 23, 25, earlier 26.x releases, and prereleases are unsupported. It uses the npm already paired with that runtime and never upgrades Node.js, npm, or Homebrew.
REA supports Node.js 22.x (>=22.19), 24.x (>=24.11), and 26+. Node.js 23, 25, and prereleases are unsupported. It uses the npm already paired with that runtime and never upgrades Node.js, npm, or Homebrew.
When REA runs on Bun, the `rea doctor` `node` check reports the Bun version and
the Node.js compatibility version Bun declares, and passes when that
@@ -1558,26 +1321,6 @@ REA process and analysis state. Direct registrations follow the installed CLI;
use `rea update` to update it. Running setup through `npx` again selects the pinned
package-runner command. Do not register paths inside npm's temporary package cache.
### npm workspace errors before REA starts
An `npx` launch can inspect the caller's npm project before executing REA. For
example, a direct dependency whose override conflicts with its declared spec
can cause npm's `EOVERRIDE`, even when the requested REA package is already
cached. This is a package-runner failure: REA has not started and cannot return
an MCP error. [#3690](https://github.com/morluto/rea/issues/3690) reproduced it
outside OpenCode as well as through that client's workspace-default launch.
Use the direct global-installation setup flow above to replace the affected
package-runner registration, then restart the client. If REA is already installed
globally, run that installed `rea setup --client <client-id> --dry-run`, review
the direct launcher in the plan, and apply the approved scope. Installing a
global copy alone does not change an existing registration. Keep the client's
working directory: changing it to bypass npm inspection can change the meaning
of relative target paths. A direct REA entrypoint preserves that directory
without asking npm to resolve the workspace's dependency graph.
### Global installation wrapper
The optional curl wrapper installs only the global npm package:
```bash
@@ -1593,29 +1336,4 @@ curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh |
bash -s -- --dry-run
```
To review a version-pinned installer before running it, select the release
version, download its tagged script, and inspect the downloaded file:
```bash
rea_version=6.7.0
curl -fsSL "https://raw.githubusercontent.com/morluto/rea/rea-agents-${rea_version}/install.sh" -o rea-install.sh
less rea-install.sh
```
After reviewing it, install the same package version:
```bash
bash rea-install.sh --version "$rea_version"
```
The tag pins the selected script revision; `--version` selects the npm package
version. Neither command verifies a signature on the downloaded script.
Supported options are `--version <semver>`, `--dry-run`, `--no-setup`, `--no-prompt`, and `--verbose`. Neither `--no-prompt` nor a non-interactive shell grants permission to install external dependencies.
Hopper setup distinguishes a confirmed occupied destination from a failed
destination inspection. It also reports local archive staging and installed
launcher inspection failures separately from download failure or launcher
absence. Follow the reported operation, path, and available filesystem code; an
unresolved inspection does not establish that a destination exists or a launcher
is missing.
+2 -15
View File
@@ -76,17 +76,12 @@ identities even when their displayed values match. Partial identities also retai
the source site: matching expression text at different sites does not establish
the same runtime target. This records observed source syntax; it does not
establish that the call ran or contacted that endpoint.
Version comparison can align equal unresolved source sites and syntax across
artifact changes, while target identity remains unknown. Equal unresolved text
at different sites and partial versus exact values cannot establish a pair.
Unresolved targets qualify endpoint absence only within the same endpoint kind;
known metadata differences on an established pair remain visible.
Graph coverage describes the analyzed artifact scope and resource limits, not
runtime execution. A partial target alone does not imply omitted source.
The directed relation vocabulary is `contains`, `loads`, `imports`, `maps_to`,
`exposes`, `sends`, `invokes`, `handles`, `calls`, `accesses`, `persists_to`,
`observed_as`, and `changed_from`. Provider names are deliberately absent from both
`exposes`, `sends`, `invokes`, `handles`, `calls`, `persists_to`, `observed_as`,
and `changed_from`. Provider names are deliberately absent from both
vocabularies.
For a `maps_to` edge targeting a source-map node, the edge carries
@@ -163,14 +158,6 @@ module produced it. In particular:
zero omissions;
- complete coverage cannot be truncated or omit items.
Semantic graph validation enforces the same truncation coupling. Unknown
omission extents remain `null`; positive omitted unknown occurrences can explain
truncation even when omitted node and relation counts are both zero. Ordinary
source unavailability, including invalid UTF-8, can make coverage partial without
resource truncation. JavaScript summary `limitation_count` counts the full,
deduplicated union of parent, application and semantic caveats, including messages
whose delivery is omitted by the view's byte budget.
Unavailable values are represented explicitly rather than as missing fields,
so absence of evidence cannot silently become evidence of absence.
+11 -148
View File
@@ -23,11 +23,6 @@ artifact digest and structural graph ID. Semantic tracing requires the semantic
relation graph and reports when it is unavailable rather than treating missing
data as an empty graph.
Worker progress reports the source path and last observed parse, static,
semantic, projection or transfer stage. These notifications do not measure
stage CPU/time or establish the root cause of a slow or incomplete analysis.
Semantic projection limits bound retained output, not upstream analysis work.
Explicit TypeScript `import type` and `export type` declarations remain source
facts and do not create runtime import or export relationships. Type export names
are reported separately from value export names. Inline `type` specifiers can
@@ -45,12 +40,6 @@ combine its fields with the fact's location. Semantic trace results include the
canonical context subset referenced by their returned facts, so those facts
remain self-contained in the response.
Content-deduplicated module references match every retained observation path.
A path-filtered reference reports the caller-selected path while keeping its
shared node identity. Item selection also accepts any retained path when it
identifies one module; if several module kinds share that path, select the
reported `node_id`. Node-ID selection uses the stable representative path.
## Retained module facts
For canonical identities and retained source-range counts, query the existing
@@ -160,10 +149,7 @@ matching seeds and the complete graph reachable in the selected direction.
The result contains the matching basis, complete reachable subgraph, terminal
paths, authority summaries, and native handoffs. A handoff binds
the exact native artifact digest and requested exports from the application
graph. Trace direction selects the reachable addons; requested
exports for each selected addon come from the complete supplied graph, so an
outgoing trace does not discard an incoming export association. Existing Hopper
or Ghidra Evidence is linked only when its subject digest
graph. Existing Hopper or Ghidra Evidence is linked only when its subject digest
matches exactly. Otherwise the result recommends provider-neutral follow-up
tools and reports `requires-provider-analysis`; it never starts or switches a
provider implicitly.
@@ -197,19 +183,6 @@ registrations/removals and dispatch, Node timers and cancellation handles, async
`node:child_process` creation and ownership, configuration sources and direct
defaults, request construction and response consumers, parse/coercion/
validation boundaries, and built-in resource acquisition/release.
Reassignments connect retained right-hand reads to the binding or property
write. Compound assignments also retain the previous-value input; logical
assignments keep conditional inputs as candidates. These edges describe
influence without evaluating arithmetic or establishing execution order or the
resulting value. Deferred callback bodies do not become inputs to the enclosing
assignment. Computed method keys belong to the enclosing callable; parameter
defaults and method bodies belong to the method. Uncertain later property reads
remain opt-in through `include_ambiguous_dynamic_edges`.
Global timer, `fetch`, `WebSocket`, `JSON.parse`, and coercion calls receive
these built-in effects only when the file has no visible possible write to that
global, including a computed write through a global object. The check does not
evaluate order or reachability, so a later or guarded polyfill write also
removes the resolved effect.
Local callback candidates do not establish a complete target set when a
conditional write may leave a parameter, catch binding, or uninitialized
@@ -311,14 +284,6 @@ change hashed event IDs and longer event/listener labels; query the preserved
name or select IDs from the parent graph. Previously stored graphs keep their
original IDs and remain valid trace inputs.
Empty string event names remain exact identities and can be selected with an
event seed whose `name` is `""`. A property seed selects the named slots and
retained accesses whose producer established that no slot was resolved. Those
accesses keep their source locations and unknowns; a missing retained relation
does not prove that an access is slotless. New access nodes record
`property_slot_resolution` independently of whether capacity permitted retaining
the resolved slot node or its relations.
## Version comparison
REA pairs entities only when a tier produces one unique candidate on each side.
@@ -355,11 +320,6 @@ remain `changed` while retaining any separate relationship uncertainty.
An unknown item with no observed content, property, or relationship difference
does not emit a `changed_from` edge; its pairing and uncertainty remain in the item.
Within a valid `file_occurrence`, `container_sha256` records provenance and does
not by itself count as a semantic property change. The graph retains the original
occurrence; its path and source digest still participate in comparison.
Unrecognized occurrence metadata remains part of property comparison.
One-sided absence is `added` or `removed` only when the opposite input graph has
complete coverage. With partial, unavailable, or truncated input, the same
condition is `unknown`. The comparison retains every classified item and
@@ -382,16 +342,9 @@ files; multiple canonical-equivalent candidates remain unknown.
Each historical source file is classified as `unchanged`, `modified`,
`removed`, `split`, `merged`, `duplicated`, or `unknown`. Multiple equally
scored path mappings form a static split inference. Multiple current file
occurrences with the exact historical digest are duplicated; multiple historical
paths with exact identity to one current occurrence are merged. Fresh file
observations carry their container digest, inventory path, and content digest.
Their asset, source-module, and export-binding representations form one candidate,
which retains `current_file_occurrence` and `representation_node_ids`. Equal bytes
at two inventory paths remain two occurrences even when they share an asset node.
Logical bundle modules and older observations without occurrence facts retain
their graph identities; matching does not infer common ownership from a label.
`removed` requires
scored path mappings form a static split inference. Multiple current
nodes with the exact historical digest are duplicated; multiple historical
paths with exact identity to one current node are merged. `removed` requires
complete historical and application inventories. The comparison analyzes and
returns all source files, candidate nodes, and matching signals represented in
the supplied graphs. Partial inventories remain unknown where absence cannot
@@ -404,36 +357,12 @@ equivalence.
name on each authenticated graph. Missing or duplicate exact selectors return
candidate inventory and an unknown result; the tool never chooses a fuzzy
match. An export is linked to a callable only through exact lexical/module
relationships recovered by the AST analysis. A later named CommonJS deletion
invalidates the earlier publication per exported field at the receiver owner;
surviving fields from the same assignment stay available. Descriptor-defined
fields retain unknown deletion success unless configurability is established.
Conditional or
dynamic deletions retain uncertainty. Unresolved invocation spreads remain
unknown source sequences and cannot publish a phantom array index.
relationships recovered by the AST analysis.
Direct return expressions, including expression-bodied arrows, are evaluated
through an execution-free value lattice. Literal object fields and direct
return sites are represented in the result. Calls, dynamic spreads, computed
keys, and parser recovery remain partial or unknown. Known conditional and
logical tests select only the established branch; uncertain tests retain
alternatives. Object copies preserve known own primitive data fields at copy time while
unknown sources retain incomplete coverage. Array holes do not overwrite own
fields during a copy. Copied reference fields keep explicit uncertainty; shared
reference projection remains responsible for their later member reads.
Declaration initializers and later assignments share the binding-at-read owner;
a declaration-only `var` read before its initializer does not inherit the future
value. Earlier `break`/`continue` paths can leave a var initializer unexecuted.
Legacy non-strict block-function copies are indexed on their outer variable
owner, while strict/module and lexical-shadowing boundaries keep their values.
A catch-shadowed var declaration keeps declaration ownership separate from its
initializer's lexical assignment, including object and array destructuring slots.
Projected initializer facts retain their source and alias identity on the lexical
recipient, so a later mutation through that alias prevents reporting the original
object field as an unchanged constant. Ordinary functions own an implicit
arguments object; arrows retain lexical arguments lookup. Promise source identity uses
the same read-position selection and requires an unmodified intrinsic factory.
keys, and parser recovery remain partial or unknown.
Rebound values are evaluated at their read position: saving an incoming parameter
before a later assignment preserves caller-dependent uncertainty, while a proven
preceding assignment can supply a constant. Unresolved ordering remains unknown.
@@ -448,10 +377,8 @@ uncertainty rather than pretending to execute later writes. References
handed back by local functions, methods, constructors, async functions, and
generators also retain escape uncertainty, including when the call is read-only;
this does not establish that a mutation occurred. Aliases
and shared children in spread and rest copies retain that uncertainty. Exposing
the copy alone does not invalidate copied primitive slots or unrelated containing
properties. Dynamic child writes such as `copy[key].value = 2` remain conservative
about which original slot was selected. Object rest
and shared children in spread and rest copies retain that uncertainty; copied
primitive slots and unrelated containing properties remain known. Object rest
excludes consumed keys, array rest excludes the consumed prefix for known
arrays, and later explicit data properties and methods replace earlier object
spread references. A known array prefix does not expose values from a following
@@ -468,76 +395,14 @@ Receiver projections follow known data fields initialized from `this` or earlier
self-referencing fields; static fields retain their declaring class identity.
An alias cycle that repeatedly selects deeper children retains escape uncertainty
at its recovered roots rather than claiming a fixed number of loop iterations.
Equivalent member reads in a repeating loop share unknown ordering instead of
creating a distinct capture for every branch history. Straight-line captures and
once-only loop inputs retain their own selection sites. When unobserved suffixes
are widened, their exact observed prefixes stay separate so unrelated siblings
remain known.
Writes through an object getter's returned receiver retain that uncertainty
when the object is an array element, nested inside mixed object/array
containers, or assigned to a previously declared identifier. Saving the getter
result in an alias does not make its old field values immutable. Known array
indices include holes; an element after a spread has only a minimum possible
index. For these returned-reference writes, unrelated properties and known
array prefixes remain precise. Accessor applicability uses the shared object
reference owner for literal definition order, own-property shadowing, known
array elements and explicit literal prototypes. Getter invocation effects
project write and call operands through the same local alias and `this` owner
used by returned references. Reads of another selected getter continue that
effect walk. A receiver reached by these effects becomes uncertain on an
ordinary or optional read, compound assignment or update. A simple assignment
or deletion does not invoke the getter. Selected setters invalidate
the receiver and its shared children on assignment, including through known
array and prototype paths. Supported class descriptors use the same inherited
field and own-property shadowing rules as class reference selection. Inert
getters and ordinary data slots retain their supported precision. Exposing a
constructed receiver also exposes its retained shared field references, using
the same reference owner as selected getter effects.
Object spreads, rest copies and destructuring obtain their property reads from
the shared pattern-source owner. Copies read own enumerable properties;
ordinary destructuring can invoke an inherited getter. Operations requiring
primitive conversion conservatively expose their conversion operands, including
computed keys, arithmetic and untagged template interpolation. `using` and
`await using` expose the retained resource at its lexical scope exit; earlier
scalar reads retain their capture timing, including reads before a later scope's
disposal. Repeated loop iterations can expose
a previous disposal and therefore retain uncertainty. Private getter fields
retain their parsed initializer references; `super` reads use the selected
receiver conservatively, including superclass reads whose lookup and receiver
authority differ. Custom `instanceof` hooks expose their receiver and argument.
Known unmodified intrinsic Symbol keys cannot overwrite string properties or
invoke primitive conversion; their omitted symbol-slot inventory stays partial.
Shadowed or visibly replaced Symbol references retain unknown key effects.
These are source-derived effects,
not execution results or proofs of accessor purity. Reflectively installed
descriptors remain opaque; their setup calls retain ordinary argument/receiver
escape uncertainty. External prototype changes and unsupported runtime protocols
remain unknown rather than supplying exact post-invocation values.
Identifier references and closure captures share Babel syntax-role facts plus
explicit binding/write roles; noncomputed declaration keys and import/meta names
are not lexical reads. TypeScript type positions, including `typeof` type queries
and computed keys in erased signatures, do not evaluate JavaScript bindings or
create runtime calls or captures. Decorators on declared class fields can still
run when the class is defined, and a decorated declared or abstract field can
evaluate its computed key. Provenance-only expression-depth omissions enter
the same typed source coverage as value-evaluation bounds and remain visible in
retained application Evidence.
Immediate synchronous function results also share the loader reference owner.
An expression arrow returning the retained CommonJS `require` can preserve that
loader identity. Possible loader results with opaque alternatives or block
bodies retain candidate uncertainty; async and generator results are their
wrapper values.
This does not interpret `typeof` or Proxy behavior in bundler shims. Historical
source imports consume the same proven loader fact and keep their existing
requirement for a known loader association.
Direct generator and async calls produce iterators and
Promises; body returns do not become direct call-result flows. Their unresolved
consumption remains an explicit frontier. Replaced emitter bindings select their
initializer lifetime at each operation. A lexical `this` owner remains partial
because it does not prove one runtime instance.
index. Unrelated properties and known array prefixes remain precise, and a
getter read without a write does not invalidate the receiver's fields.
Nested callable returns are not assigned to their parent callable. Projected
graph observations carry source ranges but never source text.
@@ -551,9 +416,7 @@ Changes use JSON Pointer paths with `added`, `removed`, `changed`, or `unknown`
status and a separate `presence` object (`present`, `absent`, or
`unknown-coverage`) on each side. A name is `added` or `removed` when one paired
shape proves presence and the other proves absence. Complete parent-property
coverage can establish absence even when field values stay unresolved. Proven
absence of an ancestor also establishes absence of its descendants. An observed
ancestor with unresolved contents keeps its descendants unknown. Identical literals are
coverage can establish absence even when field values stay unresolved. Identical literals are
omitted. Unresolved values that remain on both sides stay `unknown` when the
projections differ, and are omitted when presence is unchanged and the unknown
projections match. Incomplete spreads and other partial parent coverage keep
+18 -257
View File
@@ -60,11 +60,6 @@ The result retains the canonical local path, root artifact digest, artifact
manifest and graph commitments, JavaScript Application Graph, static
Electron summary, reconstruction statistics, and explicit limitations. It does
not require a live Hopper, Ghidra, browser, or Electron process.
Selected directories require read and search access. A denied root remains a
typed host access failure; omission handling for denied descendants does not
make an inaccessible selected root analyzable. Other host filesystem failures
retain the selected input, reported errno and syscall/path when supplied, and
the original diagnostic separately from artifact-format failures.
The application graph selects the shallowest `package.json` outside
`node_modules` as its package root, breaking equal-depth ties by Unicode
@@ -101,63 +96,15 @@ caller's request and first-occurrence priority, analyzing the file once.
Omit the selection for the existing complete
application workflow.
For a long selection, pass the paths as a JSON array with
`--source-paths-json`, either inline or as a JSON file path. A file keeps the
command below host command-line limits, such as the 8,191-character limit of
the `cmd.exe` shim that `npx` uses on Windows. Its entries follow any
`--source-path` values and stay artifact-relative; they are not resolved
against the working directory:
```bash
rea analyze-javascript-application /absolute/path/to/app.asar \
--source-paths-json /absolute/path/to/paths.json --json
```
The result and summary retain `source_selection.requested_paths` and
`source_selection.unselected_files`. Selecting every relevant file leaves this
count at zero and adds no unselected-source caveat. Every relevant file keeps its
original inventory identity; unselected files report `text_status:
`source_selection.unselected_files`. Every relevant file keeps its original
inventory identity; unselected files report `text_status:
"outside-analysis-scope"`. Their relationships remain unknown, and application
coverage is partial while any file remains outside scope. Selection does not
claim whole-application completeness or change integrity policy. Summary detail
only reduces delivery; source selection reduces text acquisition and analysis.
Explicit native provider or snapshot routing rejects `--source-path` and
`--source-paths-json` rather than ignoring them.
For directory input, a permission-denied child file remains an unavailable
inventory occurrence, and an unreadable child directory records an unknown
descendant omission. Readable sources remain available; application coverage is
partial and retains those inventory limitations. An unreadable selected root
remains an error. Explicit readers that require selected child bytes keep their
access errors rather than treating denied paths as absent.
Unavailable source files also retain their applicable per-file processing gap
in the application graph. Their container inventory identity anchors that gap;
REA does not invent a content digest for bytes it could not read.
Incomplete traversal also qualifies negative path and package-metadata lookups:
an unobserved earlier loader candidate cannot be skipped in favor of a later
observed candidate. An explicit source selection whose discovery is unknown
returns an unavailable error with the retained inventory, rather than claiming
the selected source does not exist. Observed exact file candidates keep their
identity; unknown traversal cannot establish a unique application root.
ASAR header/index admission defaults to 16 MiB / 16777216 bytes. In MCP, use
`update_analysis_settings` to set `asar.max_header_bytes` to an integer byte
count from 1 to 268435456 (256 MiB), then retry the same full-archive request.
The update applies to new operations without restarting REA; cached headers
must also satisfy the new admission. For CLI or future MCP processes, set
`REA_ASAR_MAX_HEADER_BYTES` in the launch environment. Changing that environment
requires a new REA process. Invalid launch values are configuration errors,
never a silent fallback. See the
[settings design](design/limits-and-recovery.md#failure-and-retry) for recovery
metadata, precedence, and reset behavior.
The archive index is read before any member can be selected, so
`source_paths`, `--source-path`, `detail: "summary"`, `max_heap_mb`, and
available host memory do not bypass or raise it. When the header exceeds the
effective allowance, the failure reports the value, its known source, and the
setting's limits. Alternatively, analyze an extracted
application directory, which does not admit an ASAR header. Repeating the unchanged
request without changing admission fails the same way.
Explicit native provider or snapshot routing rejects `--source-path` rather
than ignoring it.
## Read the analysis
@@ -202,27 +149,6 @@ For supplied passive browser or Electron observations, use
[runtime reconciliation](javascript-runtime-reconciliation.md). It compares
existing Evidence without rerunning static analysis or contacting CDP.
Each newly analyzed JavaScript file retains `analysis_coverage` in its graph
observation. `syntax` distinguishes complete parsing, recovered syntax errors,
failed parsing and unexamined source. `semantics` records the number of observed
unknown findings and typed uncertainty, including returned-reference widening
and unavailable type-only compiler options. `examination` reports static semantic
resource limits and Electron wrapper expansion limits independently. A completed
pass does not establish complete runtime semantics. Route examination and result
capacity remain in the separate route inventory.
Electron applicability is `observed` when a static Electron module reference is
retained, and otherwise `unknown`: a source without such a reference does not
prove that the application is inapplicable. Sources excluded by `source_paths`
retain `not-examined` with a reason and null semantic counts. Historical Evidence
without this facet remains unmeasured. The existing `parse_status` remains its
conservative overall summary; finer facts do not relabel earlier partial results.
Summary delivery counts these facts under `summary.coverage.static_sources`.
Read exact per-file measurements with `inspect_analysis_view`, selecting
`kind: "javascript-facts"` and `property_keys: ["analysis_coverage"]`, or inspect
the module item. Both views use retained Evidence and preserve source provenance.
## ASAR integrity
ASAR inventory checks Electron integrity metadata for embedded archive entries
@@ -239,30 +165,14 @@ An unpacked entry whose companion bytes were not supplied remains
`unavailable`. REA continues analyzing embedded JavaScript and records the
missing native/resource bytes as unknown. The occurrence keeps
`hash_status: unavailable`; no content-addressed child artifact is created for
those missing bytes. New inventories preserve an ASAR companion declaration as
`unpacked: true`; an omitted flag in an older inventory does not establish that
unavailable bytes belonged to a companion. See [MCP integrity handling](mcp-contracts.md#integrity-record-and-continue)
those missing bytes. See [MCP integrity handling](mcp-contracts.md#integrity-record-and-continue)
for the tool-result contract.
JavaScript whose syntax cannot be parsed retains unavailable semantic coverage
with `incomplete-module` unknowns for each semantic family and unknown omitted
node and relation extents. The source module is retained when node capacity
permits. A parser failure alone does not imply resource truncation or a named
resource limit. Decoding failures and deliberately
unselected sources retain the same explicit semantic coverage gap.
HTML whose bytes are not valid UTF-8 retains its file observation and an unknown
`parse-html-script-references` scope with artifact-byte authority. Reference
discovery was unavailable; zero retained script links does not establish that
the unreadable HTML declared none. Readable HTML with no references has no such
gap, and readable external references retain their load edges.
## Large results
Use MCP `detail: "summary"` for a large application, then read selected views of
the retained analysis. Summary changes delivery only. To reduce source parsing,
select exact [source paths](#select-sources-before-analysis). Source selection
does not bypass ASAR header/index admission.
select exact [source paths](#select-sources-before-analysis).
If a complete response exceeds the transport budget, use its retained Evidence
ID for a summary, module page, or item, or export the bundle. See
@@ -296,16 +206,6 @@ waiting before receiving the reply, follow [MCP cancellation recovery](mcp-contr
to find the record. CLI failure output contains the complete partial Evidence.
Owned analyzer cleanup is verified before private files are released; uncertain
cleanup retains ownership for retry and is reported separately.
Typed cancellation or another primary interruption remains attached while
cleanup is incomplete. Confirmed release restores that primary classification,
including the caller's exact cancellation reason, while retaining partial
application Evidence and historical cleanup diagnostics.
Private worker storage failures retain the source identity, worker phase and
reported I/O message as execution failures; they are distinct from malformed
worker output. A per-source temporary-file cleanup failure preserves static and
module facts together with any parent application-projection limit. Cleanup
failure does not authorize expanding relationships that parent admission declined.
## What is reconstructed
@@ -336,15 +236,10 @@ utility-process runtime paths retain the runtime filename selected by the caller
Selected bounded text is then parsed as inert data to recover:
- `package.json` metadata and declared main or renderer entrypoints;
- Electron preload paths owned by proven BrowserWindow options and renderer
files loaded through proven BrowserWindow instances;
- Electron preload paths and renderer files visible in static syntax;
- local HTML script entrypoints;
- Webpack and Rspack chunk registrations and module factories represented as
AST literals, plus esbuild `__commonJS` module tables; recovered esbuild
CommonJS exports use its factory's `exports`-then-`module` parameter order.
Export receivers and loader helpers retain those calling-convention roles only
while the binding at the read still holds its original parameter value;
replacing a parameter does not give the replacement object that role;
AST literals;
- static imports, dynamic imports, CommonJS `require` calls, workers, and
service workers (only `new Worker(new URL(path, import.meta.url))` resolves
relative to the calling module; string worker and service-worker paths use
@@ -388,11 +283,9 @@ RPC client syntax also contributes network endpoint candidates, in both
initializer and no binding assignments; shadowed locals do not inherit client
identity. `subscribe` additionally requires a tRPC observer
argument (`onData`, `onError`, `onStarted`, `onStopped`, `onComplete`, or
`onConnectionStateChange`). `this` roots and unshadowed browser globals are
excluded. A lexical client can be named `history`, and its router segments can
use browser-global spellings. RTK Query `endpoints` chains are excluded only
when the root has no recognized tRPC client binding. Cache-key helpers such as
`queryKey` issue no request and are excluded.
`onConnectionStateChange`). `this` roots, browser globals, and any path
containing an RTK Query `endpoints` segment are never procedures; cache-key
helpers such as `queryKey` issue no request and are excluded.
- `trpc:link:<link>` records the literal `url` option of `httpLink`,
`httpBatchLink`, `httpBatchStreamLink`, and `httpSubscriptionLink` when the
link's callee name survives bundling, including `(0, module.httpBatchLink)`
@@ -423,27 +316,6 @@ recorded path keeps those leading carrier segments; the procedure path is its
suffix. Utility calls such as `useUtils().invoice.list.fetch()`, `useQueries`,
and links whose callee was renamed by a minifier are not recognized.
Bundler adapters own callback roles: Webpack supplies module, exports, then
loader; esbuild CommonJS supplies exports, then module. Export extraction uses
those binding identities for assignments and declarations, so renaming either
parameter does not change the recovered names. Global property-definition
intrinsics require lexical and visible-write proof. Factory export declarations
also require the loader's `d` member to have no prior or unordered visible
replacement, including writes through lexical aliases. Unrelated loader members
do not invalidate declarations. Ordering describes declarations within one factory
activation; helper integrity on entry, effects of earlier invocations, and
arbitrary call side effects remain unproven.
Computed writes through a standalone loader's exposed module/cache table use
the shared value owner; an unknown key invalidates the original-slot proof
rather than implying a fresh chunk installation. A function stored on a
standalone loader member does not write the table merely by being defined:
the bootstrap owner excludes its deferred writes only when that member is
unread and the loader namespace stays confined. Calls, member aliases, dynamic
member reads, namespace escapes, or
receiver-based access retain uncertainty about those writes. Export names
describe recognized declarations, not a proof
of final runtime property lifetime or arbitrary helper behavior.
Each recovered bundle module retains the exact factory-source digest. A complete
bounded AST also receives a `babel-ast-v3` structural fingerprint. It ignores
ordinary identifier spellings while retaining literal and directive values,
@@ -470,22 +342,6 @@ prefix is never presented as a complete stable fingerprint.
## Authority and unknowns
API identity is a derivation: a familiar method or property name does not
establish a browser or Electron API. The shared lexical, ambient-write, and
member-mutation owners must establish the receiver before static API facts become
roles or storage observations. Path `join`, `resolve`, and `fileURLToPath` use
proven Node imports; source-relative URL conversion additionally requires an
unmodified global or proven Node `url.URL` constructor and `import.meta.url`.
Native constructor aliases retain that identity; their local spelling does not
determine URL semantics, and replaced exports or local constructors remain unknown.
Relative results of native path operations retain filesystem-expression context;
their spelling does not turn them into module specifiers. Other calls preserve
their expressions. A property named `preload` outside a proven BrowserWindow
configuration does not establish a script entrypoint. Renderer recovery currently
covers direct BrowserWindow instances, stable local aliases, and their
`webContents`; other receiver-producing APIs remain unmodeled. Complete AST
traversal does not imply complete API-model coverage.
Artifact bytes and AST syntax are observations. Entrypoint resolution, imports,
loads, calls, and persistence relationships are static inferences and explicitly
say that syntax does not prove runtime execution. A malformed or unavailable
@@ -515,20 +371,7 @@ Recognized literal module-table bootstraps also support bounded lexical
substitution through module exports, import helper bodies, local wrapper calls,
returned closures, object spreads, and callback parameters. Recovered Electron
paths retain source ranges, module identities, literal channels and branch
conditions as static candidates. A channel the IPC call site itself
determines, such as a local constant, resolves that call's observation rather
than adding a second one. A channel or registration handler supplied through
a wrapper argument is a candidate beside the call's own unresolved
observation, because callers outside the traced paths may pass other values.
A wrapper call that is both its own entry and reached through another entry
is recorded once when both paths find the same facts; calls that supply
different handlers or channels stay distinct.
A contextBridge exposure reached through wrapper calls records one API per key
those invocations supply, each with its own bounded member paths. Invocations
that supply the same key to one exposure call describe one API covering each
alternative object. A traced invocation whose key, API object or world stays
unresolved or guarded keeps the call's own unresolved observation.
Unknown spreads, reassigned bindings, accessors,
conditions as static candidates. Unknown spreads, reassigned bindings, accessors,
dynamic channels and unsupported syntax do not become exact implementations.
This pass neither executes the bundle nor proves that runtime guards pass.
An ambiguous or unresolved handler retains its channel registration but does
@@ -619,14 +462,6 @@ candidate. Dynamic channels are never paired, and multiple compatible handlers
are reported as ambiguous without adding a caller-to-handler edge. A matching
channel still does not prove registration order, reachability, or runtime use.
Native-addon call bindings consume the same loader identity and normalized
arguments as dependency analysis, including optional calls. Local methods or
parameters named `require` establish no native binding; `require.resolve` records
lookup rather than a native namespace. CommonJS re-export bindings require a
selected export value and an applicable shared export receiver. Arithmetic and
logical compound stores keep their dependency observations without asserting that
the addon is the selected exported value.
For `.node` bindings, member names mean “requested by JavaScript syntax.” A
resolved add-on path does not convert them into verified native exports. Native
symbol verification remains a separate deep-analysis claim.
@@ -652,11 +487,6 @@ inventory digest before parsing, and malformed ASAR operations return typed
format diagnostics that retain the local container path. Native add-ons are
represented by metadata only.
POSIX directory names are read as raw bytes and admitted as logical paths only
when they round-trip through UTF-8. Invalid names are left untouched, retained
siblings remain inventoried, and traversal coverage is marked unknown with the
first rejected basename bytes identified in hexadecimal.
## Bounds and coverage
The reconstruction keeps internal resource safeguards for artifact entries,
@@ -744,10 +574,6 @@ deadline per source, including startup and fact transfer. Select
`max_heap_mb` and `analysis_timeout_ms` through MCP, or `--max-heap-mb` and
`--analysis-timeout-ms` on the dedicated CLI command. These choices control the
analyzer; the caller's `NODE_OPTIONS` still controls the CLI/MCP process heap.
For defaults across new MCP operations, use `update_analysis_settings` with
`javascript.max_heap_mb` or `javascript.analysis_timeout_ms`. Explicit per-call
arguments take precedence, and accepted work keeps its original settings. See
the [settings design](design/limits-and-recovery.md#precedence-and-lifetime).
V8 old-space limits do not impose a process RSS limit. When REA runs on Bun,
the analyzer ignores the V8 heap flags; resource failures then report
`worker_heap_limit_bytes` as null instead of a requested or V8-reported limit.
@@ -763,9 +589,8 @@ The default semantic capacities are 100000 nodes across the application and
`--max-semantic-nodes-per-file`), each from 1 to 1000000. The per-file offer is
also bounded by the remaining application capacity. Raising these controls does
not bypass worker heap, execution deadline or parent result admission. Truncated
coverage lists both effective ceilings, `max_semantic_nodes` and
`max_semantic_nodes_per_file`. The limitation names only the ceiling that
stopped retention. Omitted relationships remain unknown.
coverage reports both effective node ceilings; omitted relationships remain
unknown.
Unknown records have independent defaults of 100000 across the application and
20000 per source. Select `max_semantic_unknowns` and
@@ -773,10 +598,8 @@ Unknown records have independent defaults of 100000 across the application and
`--max-semantic-unknowns-per-file`), each from 1 to 1000000. The parent offers the
remaining capacity to the worker, and rejects excess records before adoption.
Unknown admission happens before canonical identity and provenance construction.
When capacity is exhausted, coverage is partial and truncated. Coverage lists
both effective unknown ceilings, `max_semantic_unknowns` and
`max_semantic_unknowns_per_file`. The limitation names only the ceiling that
stopped retention. `coverage.omitted_unknown_occurrences` counts examined
When capacity is exhausted, coverage is partial and truncated and names both
effective unknown ceilings. `coverage.omitted_unknown_occurrences` counts examined
emission occurrences by family and reason, including repeated emissions. It does
not claim distinct discarded identities or count frontiers in unexamined source.
Omitted unknown provenance and IDs are not retained. Existing source, node,
@@ -784,47 +607,17 @@ execution and memory limits continue to determine examined coverage. Raising onl
a node capacity does not raise the unknown capacity. Graphs without unknown
omissions omit this optional coverage field, preserving their existing identities.
These node and unknown capacities bound retained graph projection. Parsing,
static analysis and semantic fact construction precede that projection for an
admitted source. Lowering the capacities can reduce projection and retention
work, but does not bound upstream semantic-analysis time or memory. Select fewer
`source_paths` to reduce the sources analyzed; heap and deadline controls set
separate resource failure boundaries with the runtime-specific limits above.
While sources remain, the sequential worker reserves one next compact static
checkpoint at its 8 MiB bound with the same 6x retention expansion used at
admission. Earlier checkpoints are already charged to retained bytes; the
remaining inventory does not multiply the reserve. Streamed file transfers keep the default 512 MiB
allowance, and can grow with the parent's offered retention budget (8x byte
expansion), up to 2 GiB. Individual records remain bounded to 8 MiB. The 8 MiB
record ceiling and the 2 GiB file maximum have no runtime override, and raising
`max_heap_mb` does not raise them. The effective file ceiling follows the
parent's offered result budget. On Node, a larger CLI/MCP process heap
(`NODE_OPTIONS`, for example `--max-old-space-size=<MiB>`, after restarting that
process) can raise it up to 2 GiB. Selecting fewer `source_paths` can also
raise that effective allowance, because the reserve for remaining sources and
earlier retained results reduce the offered budget. Neither change raises the
record ceiling or the 2 GiB maximum. The parent
expansion), up to 2 GiB. Individual records remain bounded to 8 MiB. The parent
still independently checks actual bytes, record overhead, digests and source
identities before adopting results.
A source that exhausts the analyzer heap does not terminate the CLI/MCP
process. On Node, the failure names `max_heap_mb` (CLI `--max-heap-mb`), the
effective mebibytes, the inclusive range 128 to 16384, and the 1024 MiB
default. A changed value applies on the next analysis, which starts a new
worker. It does not change a worker that is already running and does not
require restarting the REA server. On Bun, the same heap-exhaustion diagnostic
is not attributed to that V8 old-space limit, because Bun ignores the flag.
Raising `max_heap_mb` does not raise the parent result budget,
the transfer record ceiling, the 2 GiB transfer file maximum, the CLI/MCP
process heap, or fixed parser limits. The parent result budget follows that
process heap. On Node, set `NODE_OPTIONS` (for example `--max-old-space-size=<MiB>`)
in the environment that starts the CLI or MCP server, then restart that
process, to raise the parent heap and the result budget. A completed static
checkpoint that does not fit the parent's remaining heap names that parent
process heap. Increasing `max_heap_mb` does not raise it. On Node, the same
`NODE_OPTIONS` restart can raise the parent heap and admit the checkpoint.
Completed static facts are retained, other files can still be analyzed,
process. Completed static facts are retained, other files can still be analyzed,
and unexamined semantic relationships remain explicit unknowns. The parent
also checks transfer expansion and accumulated results against its remaining
heap before accepting semantic facts. A resource-limited result has partial
@@ -911,24 +704,6 @@ URL targets before selecting a later package-local file; an unsupported bare
package or recursive alias keeps its unresolved outcome rather than selecting
a later fallback.
For a proven `createRequire(new URL(relativeFile, import.meta.url))` call,
relative `require()` edges use the selected filename's directory and retain the
source-relative `resolution_base_path` on the graph edge. This follows Node's
`createRequire(filename)` contract; the path is a loader-resolution fact and
does not change the source file or call-site Evidence identity. If the filename
cannot be proven, REA keeps the require target unresolved rather than silently
resolving it from the caller's directory.
Module-reference graph edges, including native-addon `loads` edges, retain
`properties.operation`: `load` identifies
an import or loader invocation that may execute the target; `resolve` identifies
a proven `require.resolve()` lookup that discovers a target without executing it.
Both retain the exact specifier, resolution mode, selected resolution base, and
call-site Evidence. Resolve-only edges do not propagate application ownership or
create executable-import gaps when their targets are missing. A modified
`.resolve` member remains an uncertain `load`, because its replacement may execute
code. These static facts do not establish that a call runs at runtime.
ESM relative module paths, legacy package subpaths without exports, and selected
package exports targets use URL suffix handling and one percent-decoding pass.
Bare specifiers retain their complete query/fragment text for exports key matching.
@@ -971,17 +746,3 @@ truncated under the `html_inline_script_locations_per_file` limit, and the
scope's evidence counts the omitted locations. The graph names the
file and up to 10 starting lines in its limitations, and coverage is partial. Import maps, speculation rules, data blocks, and blank
bodies are not JavaScript and are not reported.
For static source relationships, an explicitly requested declaration filename
(`.d.ts`, `.d.mts`, or `.d.cts`) retains its inventoried identity through both
`import` and `require`. A declaration found only as an emitted-extension fallback
remains unavailable as a runtime implementation. Package URL targets decode
filename escapes once, including dots within filenames; forbidden whole segments
and encoded separators remain rejected. ESM local-path classification uses the
authored request before URL whitespace preprocessing, so leading whitespace
cannot turn a bare request into a relative file dependency.
For example, a leading tab or newline before `./dep.mjs` remains a bare
package request; Node's later package-URL normalization can resolve it under
`node_modules`, independently of a same-named file beside the importer. Resolver
qualification checks the exact path and file existence: [`import.meta.resolve()`](https://github.com/nodejs/node/blob/v24.22.1/doc/api/esm.md#importmetaresolvespecifier)
alone can return a URL for a missing file.
+4 -14
View File
@@ -85,11 +85,6 @@ publication. REA writes verified bytes to an exclusively owned output tree and
rolls it back on failure. It also checks that the original source and engine
bytes remain unchanged before committing. The existing output is never overwritten.
A denied selected-input read reports `access_denied` with the observed OS code
and path. A source or engine identity change reports `artifact_changed`;
neither failure is a malformed recovery request. Cancellation retains the
caller's reason through admission, command launch and completion.
Multi-input relative filenames must be representable unchanged as artifact paths:
NFC Unicode spelling and no literal backslashes. Unsupported layouts fail before
invoking the engine rather than silently renaming inputs referenced by source maps.
@@ -134,15 +129,10 @@ equivalence remain unknown. No network fetching occurs.
## Resource policy and verification
Each operation uses one worker, a 1 GiB address-space ceiling and a 120000 ms
deadline. That deadline has no runtime override: raising `analysis_timeout_ms`
or `max_heap_mb` does not raise it. An unchanged retry may hit the deadline
again. The operation accepts up to 64 MiB of total
input across all selected files and 128 MiB/10000 entries of staged output,
with 8 MiB per diagnostic/report stream. A `provenance.json` over that report
allowance names the fixed limit: this release has no runtime override, and
raising `max_heap_mb` or `analysis_timeout_ms` does not raise it. Malformed
reports stay distinct from that size refusal. Exceeding a limit is an explicit
Each operation uses one worker, a 1 GiB address-space ceiling and a 120-second
deadline; it accepts up to 64 MiB of total input across all selected files and
128 MiB/10000 entries of staged
output, with 8 MiB per diagnostic/report stream. Exceeding a limit is an explicit
failure with rollback. Selected inputs beyond the total input allowance return a
non-retryable `resource_constraint` with `resource: "file-size"` and
`reported_limits.maximum_input_bytes`/`remaining_input_bytes`; input that is not
-143
View File
@@ -1,143 +0,0 @@
# Static JavaScript route inventory
`analyze_javascript_application` and `analyze_web_bundle` retain a shared
`route_inventory` alongside their existing graph and endpoint observations.
The inventory answers which route patterns the supplied code suggests, where
each pattern came from, and which parts remain unknown. It never requests a
route, executes scripts, expands a sitemap, or establishes reachability.
Select a compact view in either MCP analysis call:
```json
{
"detail": "summary",
"view": {
"kind": "javascript-routes",
"collection": "routes",
"where": { "path_prefix": "/reports" },
"fields": ["path", "classification", "confidence"],
"limit": 20
}
}
```
Keep the original analysis inputs in that call. For the CLI, pass the same
selection as `--view '<JSON>' --detail summary --evidence_output routes.json`.
The complete Evidence bundle is written without overwriting existing files.
Unselected delivery retains the existing complete result. A web-bundle summary
requires an explicit route view; application summaries also support the existing
general summary when `view` is omitted.
Use the returned `normalized_result.parent_evidence_id` with
`inspect_analysis_view` on the same connection. The CLI's exported bundle can be
reopened with `inspect-evidence-bundle`; its `records` contain the complete
Evidence. To use `inspect_analysis_view`'s `evidence-file` source, extract the
selected parent record into a separate local Evidence JSON file (that source
accepts one Evidence record, not a bundle). Views do not reopen source files or
reconnect to a browser. Historical Evidence without `route_inventory` is
unmeasured, rather than an empty route set.
Collections are `summary`, `routes`, `occurrences`, `sources`, and `unknowns`.
All row collections support exact batch `where.ids`, field selection, `offset`,
and `limit`. An explicit empty filter array matches nothing. Routes also support
exact `paths`, literal `path_prefix`, `classifications`, `mechanisms`, and
`min_confidence`; occurrences support `route_ids`, `source_ids`, and `mechanisms`;
sources support `keys` and `statuses`; unknowns support `source_ids` and `kinds`.
Follow `coverage.next_offset` until `coverage.exhausted`; a page may stop early
at the 1 MiB serialized view budget. Oversized indivisible facts return a
resource error carrying the retained parent Evidence ID.
Pattern IDs bind the exact path. Source IDs bind the source key, path, original
URL, content digest, container digest, and any retained Next source applicability
context (including the owning package digest). Occurrences identify a detector mechanism at a source location; one location
may support multiple mechanisms. `source_count` counts distinct sources. Lookup IDs use 128 bits of SHA-256;
full content and inventory digests remain SHA-256. Field selection always keeps
row identity and necessary provenance. The page's deduplicated `sources` table
contains the source descriptors referenced by returned rows. Retrieve all
occurrences by route ID when independent supporting evidence matters. Source
ranges use one-based lines and zero-based UTF-16 columns with exclusive ends;
filename-only discoveries have no invented AST range.
The shared parser handles literal router calls, nested route objects, typed
`_$` trees with explicitly inferred property-name fallbacks, Next App Router
page paths, and navigation targets. It follows stable lexical bindings and
keeps unresolved parents, mutations, spreads, and dynamic fields explicit.
Index-route flags use the same value-at-read boundary as path fields: erased
type wrappers and initialized stable boolean aliases preserve `true`/`false`;
uninitialized or unresolved flags retain an interpretation unknown instead of
silently disappearing.
A selected route-builder root owns its configuration even when mutation prevents
trusting its value. Ordinary wrapper keys do not become route ancestors. Missing
outer ancestors of unbound typed subtrees remain unknown rather than invented.
Generic Next runtime path constants require a top-level application segment
supported by structural or navigation candidates across the supplied sources.
This limits framework filesystem noise, while leaving unsupported candidates
unknown. The parser shares the existing AST traversal and lexical index.
Literal fragments of computed expressions, module references, and page-shaped
paths owned by Next interpretation do not also become generic route constants.
Selected getter results retain the shared semantic owner's allocation identity;
finite selections can claim their query data, while widened or cyclic selections
keep an unknown in the interpretation that requested them.
Next page conversion receives an explicit source context. For local source files,
the shared JSON owner first establishes a declared `next` dependency in the
nearest owning package. Inventoried directories then select that package's `app`
before `src/app`, following Next's [root selection](https://github.com/vercel/next.js/blob/v16.4.2/packages/next/src/lib/find-pages-dir.ts).
Only files beneath the selected directory supply source-page support. Default
`.next/static/chunks/app` output receives a separate compiled-directory context
from the same package owner; custom build directories remain unmodeled. Pages Router
files, unrelated components, and nested packages do not inherit that root merely
because their paths contain `app`. Missing package/framework evidence remains an
interpretation unknown for page-shaped source paths. Each page-shaped source retains its
`next_app_context` and package identity so this derivation remains inspectable.
Captured script URLs use the `/_next/static/chunks/app/` asset boundary, independently
of local package evidence. Source literals use recognized Next runtime context;
imports, exports, and direct call arguments do not themselves declare pages.
The parser completes its runtime-marker scan before interpreting retained module
literals, so marker placement does not change the result. A matching filename
suffix alone cannot establish App Router ownership.
[Next dynamic segments](https://nextjs.org/docs/app/api-reference/file-conventions/dynamic-routes)
map to `:id`, `:slug+`, or `:slug*`; groups and parallel slots do not contribute
URL segments. Intercepted routes remain unresolved. A prefix before `/_next/`
is an **inferred** routing base: [assetPrefix](https://nextjs.org/docs/app/api-reference/config/next-config-js/assetPrefix)
can differ from the routing base. Every affected occurrence keeps
`unprefixed_path`, `inferred_base_path`, and low confidence. `system` classification
is explicitly a path-name heuristic; `page` uses page-file or structured/typed configuration support. Name-only
`route`/`addRoute` calls remain `unknown` because they can also register server APIs.
Next page-source segments keep their filename spelling when converted to route
patterns. Next's [page-path conversion](https://github.com/vercel/next.js/blob/v16.4.2/packages/next/src/shared/lib/page-path/absolute-path-to-page.ts#L21-L39)
removes page extensions and the `index` tail without URI-decoding segments, so
ordinary percent sequences are not decoded: `%61` and `a` remain separate
candidates and a literal `%` is retained. Its [App pathname normalizer](https://github.com/vercel/next.js/blob/v16.4.2/packages/next/src/server/normalizers/built/app/app-pathname-normalizer.ts#L5-L18)
specifically applies the [underscore normalizer](https://github.com/vercel/next.js/blob/v16.4.2/packages/next/src/server/normalizers/underscore-normalizer.ts#L2-L9),
which maps `%5F` to `_`; private-folder checks apply to the source spelling
before that escape is mapped. When classifying a chunk URL, REA reads its
pathname, then applies these same Next-specific rules. This is static source-path
interpretation, not browser request-path decoding or reachability.
Known capture omissions remain inline in `summary.reported_capture_omissions`
and queryable as `unknowns` of kind `capture-omission`. Each keeps a stable gap ID,
its script-record/script-source unit, and the reported count (including `null`
for unknown). They do not invent missing script identities or source ranges.
Counts describe deduplicated patterns in the provided code sources. They are
not a denominator for complete application or capture coverage. In particular,
server-only, lazy, inaccessible, or unselected sources may be missing.
`coverage.application_routes` always remains `unknown`; parsing and candidate
enumeration have their own completeness. Static work and fact admission share
the existing 8 MiB per-source transfer ceiling, and the root inventory has an
8 MiB admission budget. Exhaustion preserves a prefix with explicit capacity;
the serialization owner also admits route facts within the actual space left by
legacy facts. `result-bytes` capacity preserves an earlier work-capacity reason;
if even that envelope cannot fit, the optional route facet remains unmeasured.
These are accounting bounds, not a claim that total process memory is 8 MiB.
Projection only reduces delivery, not analysis work. Retained immutable Evidence
can reuse validated tables through a weak cache with the snapshot's lifetime;
subject identity is checked on every query. No live state or source content is
cached as current. Use existing source selection and resource controls when
they match the investigation, keeping their partial coverage visible.
+5 -28
View File
@@ -44,19 +44,11 @@ launch does not, by itself, show that fuse.
The caller supplies the literal-loopback HTTP endpoint directly. Selecting an endpoint exposes all targets it serves and their local script locations or renderer origins. A target must match the exact requested target ID and expose a same-port WebSocket reported by that endpoint. Targets explicitly marked attached are rejected rather than displacing another debugger. Node's `/json/list` omits attachment state, so REA preserves it as `null` (unknown), reports that limitation, and permits observation unless the endpoint explicitly reports `attached: true`. Local paths are canonicalized after symlink resolution.
The `attached` field in JavaScript runtime and browser target results is nullable: `true` and `false` are reported only when discovery supplied a boolean; `null` means the endpoint omitted or could not report the state. HTTP `/json/list` discovery parses target rows independently. Malformed optional attachment metadata becomes `null` with a row and field diagnostic in `limitations`; usable siblings remain available. Rows with malformed target identity are excluded with diagnostics, and socket authority is still validated before connection. CDP `TargetInfo` worker records require an `attached` boolean; REA excludes a worker record with a missing or malformed value as an invalid protocol value.
The `attached` field in JavaScript runtime and browser target results is nullable: `true` and `false` are reported only when discovery supplied a boolean; `null` means the endpoint omitted or could not report the state. CDP `TargetInfo` worker records require an `attached` boolean; REA excludes a worker record with a missing or malformed value as an invalid protocol value.
`list_javascript_runtime_targets` returns every available target in one inline
array.
Attachment refusals identify `attached: true` and ask the caller to disconnect
the other debugger or select another target. A declared-role mismatch names
`runtime_kind` and the target protocol type; correct the role or omit it to
retain an unknown role. A role-only disagreement in supplied runtime Evidence
is attributed to `parameters.runtime_kind`, separately from a target-ID
disagreement. Partial-capture warnings count retained distinct script records;
`observed_total` retains the raw script-event count before deduplication.
For the MCP follow-up call, pass the same literal-loopback endpoint and the selected target ID to `observe_javascript_runtime`. REA rediscovers targets and validates the ID against the live endpoint before observation.
### Discovery file locations
@@ -99,12 +91,9 @@ The provider sends exactly two protocol commands:
- `Debugger.enable`
It never sends `Runtime.evaluate`, reads script source, installs breakpoints,
sends pause/resume commands, calls `Runtime.runIfWaitingForDebugger`, launches
the target, instruments JavaScript, or invokes Electron APIs. Enabling
`Debugger` can make target-authored `debugger` statements pause execution.
REA does not send `Debugger.setSkipAllPauses` or resume these pauses; passive
observation is not a guarantee of uninterrupted target execution. Closing a
call closes only REA's WebSocket.
pauses or resumes execution, calls `Runtime.runIfWaitingForDebugger`, launches
the target, instruments JavaScript, or invokes Electron APIs. Closing a call
closes only REA's WebSocket.
Each WebSocket handshake and command response has its own 20-second deadline,
separate from the caller-selected observation window. A stalled handshake or
@@ -116,9 +105,7 @@ The direct observations are:
- `Debugger.scriptParsed`, establishing that a bounded script location was
parsed in the observed execution context;
- `Debugger.scriptFailedToParse`, retained only as a bounded invalid-script
count in `scripts.parse_failed_total`. A well-formed parse-failure
event is distinct from malformed protocol data and does not make loaded
script coverage unavailable. Older Evidence may omit this count;
count;
- `Runtime.executionContextCreated`,
`Runtime.executionContextDestroyed`, and
`Runtime.executionContextsCleared`, establishing context lifecycle within
@@ -132,12 +119,6 @@ handlers, or script-unload events. Those facts remain explicit unknowns.
`scriptParsed` proves script presence, not the importing module, feature
execution, initialization order, or causality.
For an authorized file script, `location.file_path` is the verified filesystem
identity and optional script-item `reported_url` preserves the exact Inspector
URL. Script identity includes that reported URL, so query and fragment
variants of one file remain separate observations. Older Evidence may omit
`reported_url` and remains readable.
## Node and Electron roles
The caller declares one role: `node`, `electron-main`, `electron-preload`, or
@@ -147,10 +128,6 @@ Inspector does not authenticate the operating-system PID or distinguish an
Electron role, so Evidence records the role authority as
`caller-declared-unverified`.
If final script-location authorization fails, collected script and context facts
remain available in the typed failure with explicitly unverified locations.
Descriptor cleanup status and its retry owner remain independent of that capture.
Electron main, preload, and renderer behavior can therefore be observed only
as separate Inspector targets. The provider does not infer that two
targets belong to the same Electron application.
+2 -22
View File
@@ -57,11 +57,7 @@ are complete Evidence objects, not only evidence IDs:
`static_layers` requires exactly one `application` role and permits additional
`cache` and `assets` roles. A `file-root` mapping translates a canonical runtime
filesystem root to an artifact-relative prefix. A `url-prefix` mapping does the
same for one exact HTTP(S) origin and path prefix. Each URL path segment below
the prefix is percent-decoded separately: an encoded slash (`%2F`) stays inside
its segment and never names a nested artifact, so such a location, like one
with malformed or non-UTF-8 escapes, has no artifact path under that mapping.
An encoded backslash (`%5C`) remains a literal name byte. Mappings cannot add CDP
same for one exact HTTP(S) origin and path prefix. Mappings cannot add CDP
origins, broaden Electron roots, read files, or authorize source-map access;
they are caller-declared inference inputs applied only to already-authorized
locations.
@@ -110,12 +106,7 @@ nodes. REA evaluates compatible static candidates conservatively:
that disagreement. If a mapped candidate's digest is unavailable and no
mapped content match is known, the comparison remains `unknown`.
6. Missing authorization mappings and incomplete or truncated input coverage
remain unknown or truncated rather than becoming absence claims. A runtime
location with no automatic artifact-root or caller-declared mapping into
any layer, and no content match, is `unknown` with
`no-authorized-location-mapping`, with or without captured bytes; several
content matches remain `ambiguous` under rule 4. Only a mapped location can
be `unmatched` with `no-static-content-match`.
remain unknown or truncated rather than becoming absence claims.
Every successful cross-layer `observed_as` edge has
`authority: cross-layer-reconciliation` and `state: inferred`. The underlying
@@ -137,17 +128,6 @@ classification:
- `unknown` covers incomplete captures, layers outside runtime scope, and
unresolved correspondence. Nodes outside a mapped artifact prefix stay
unknown even when another prefix in the same static layer was observed.
Nodes beneath a mapped artifact prefix also stay unknown when a runtime
script or worker under that mapping has no artifact path.
Script inventory completeness remains a separate reported fact. Unavailable,
truncated, or attach-limited worker inventory prevents negative code-presence
claims even when the selected page's script inventory is complete. Accepted
positive script correspondences remain loaded.
An Electron script's explicit unknown `frame_scope` preserves its reported
frame ID and sourceURL provenance as node properties, but does not link that
script to the current frame inventory. Attach-limited script coverage cannot
establish negative code-presence claims.
None of these states proves feature execution, initialization order,
reachability, or causality. In particular, `not-observed-in-capture` never means
+11 -20
View File
@@ -28,21 +28,19 @@ JEB service is reachable or that a project is open; use the existing
MCP `binary_session` probes the live JEB service and its advertised tool catalog.
Availability is reported per operation: a reachable headless client can support
inspection and decompilation while refusing `open_jeb_project`. Catalog discovery
is one deadline across connection and pagination; a responsive server cannot keep
discovery running indefinitely by returning more pages.
uses one 60-second deadline across connection and pagination; a responsive server
cannot keep discovery running indefinitely by returning more pages.
Catalog discovery and each `tools/call` share `REA_JEB_MCP_TIMEOUT_MS`. Set it
in REA's launch environment to an integer from 1 to 2147483647 milliseconds,
for example `REA_JEB_MCP_TIMEOUT_MS=300000` for a five-minute budget. Omitted
means the pinned MCP client SDK default of 60000 ms (`@modelcontextprotocol/client`
2.3.1). Restart REA and reconnect to apply a change. Raising the setting raises
both phases. It does not make one end-to-end deadline across a whole CLI or MCP
operation: discovery and each `tools/call` each get that budget. A request that
reaches its budget returns a typed timeout naming the phase and the effective
milliseconds, while caller cancellation remains a cancellation. Allow execution,
cleanup, and delivery headroom in the outer MCP client deadline.
Each upstream tool call has an absolute 60-second request deadline by default.
Set `REA_JEB_MCP_TIMEOUT_MS` to a positive decimal number of milliseconds to
select a different per-request budget, for example `REA_JEB_MCP_TIMEOUT_MS=300000`
for a five-minute decompilation. This budget starts after discovery and applies
to each individual `tools/call`; it does not extend the separate catalog deadline
or establish one end-to-end deadline across a whole CLI or MCP operation. A
request that reaches its budget returns a typed timeout, while caller
cancellation remains a cancellation.
Earlier verification used JEB 5.48.0 (Linux x64, licensed build) serving `jeb-mcp-server` 1.3.0 over streamable HTTP. The official Linux x64 CE build `5.48.0.202610071539` was also verified through real CLI and production stdio MCP native-code workflows; see the [Linux CE audit](https://github.com/morluto/rea/blob/42e1a17d6e6c0487f17be0bae248553c4caba7f7/experiments/analysis-efficiency/jeb-linux-native.md) for measurements and limits. CE verification does not establish licensed/Demo Android decompilation or GUI behavior.
Earlier verification used JEB 5.48.0 (Linux x64, licensed build) serving `jeb-mcp-server` 1.3.0 over streamable HTTP. The official Linux x64 CE build `5.48.0.202610071539` was also verified through real CLI and production stdio MCP native-code workflows; see the [Linux CE audit](../experiments/analysis-efficiency/jeb-linux-native.md) for measurements and limits. CE verification does not establish licensed/Demo Android decompilation or GUI behavior.
## Operations
@@ -68,10 +66,3 @@ All four preserve target-file bytes. `open_jeb_project` selects or creates an en
- An unreachable endpoint is a provider-availability failure with recovery guidance; REA does not retry or spawn the engine.
Real-engine verification lives in the JEB lane described in [testing.md](testing.md); mock transport tests do not establish real-engine compatibility.
A project-open refusal retains the selected engine-host artifact path separately
from the service endpoint. Discovery of an unreachable service includes the same
start/configuration recovery as a direct request. If a primary reply completes
before a later client-information lookup fails or is cancelled, the typed failure
retains that reply as partial Evidence. Engine metadata stays unknown; this is not
a claim that the full operation completed.
-174
View File
@@ -1,174 +0,0 @@
# Linux kernel modules
`inspect_kernel_module` / `inspect-kernel-module` reads metadata from one
selected local ELF32/ELF64 relocatable object (`.ko`) without executing it.
That metadata is the `.modinfo` key/value strings, the `__versions` symbol
CRCs, the presence of `.gnu.linkonce.this_module` and `__ksymtab*` sections,
the observed file sizes of `.init.text` and `.exit.text`, and an optional
appended signature trailer. It does not require a Linux host or the `modinfo`
program.
```bash
rea inspect-kernel-module /absolute/path/to/module.ko --json
```
The CLI resolves relative paths against the caller's working directory. MCP
requires an absolute filesystem path and preserves that exact selection:
```json
{
"name": "inspect_kernel_module",
"arguments": { "path": "/artifacts/demo.ko" }
}
```
## Observations
The inline Evidence result contains the artifact path, SHA-256, and byte
length. `applicability` is `applicable` only for `ET_REL`. Other ELF types are
`not_applicable`: header facts are filled and `.modinfo`, `__versions`, and
the signature trailer are not examined.
`.modinfo` entries stay in file order. Duplicate keys such as `alias` and
`parm` are preserved. Each entry keeps its key, value, file range, and
SHA-256. Values are the bytes after the first `=`. NUL padding between
strings is skipped. Keys and values use a byte-preserving Latin1 projection:
each source byte becomes the same-numbered Unicode code point. They are not
decoded as UTF-8. `raw_sha256` hashes the original entry bytes, including its
terminating NUL when present; `raw_hex` shows those bytes when they fit the
inline limit.
`__versions` entries are `struct modversion_info`: a 64-byte record whose CRC
is an `unsigned long` and whose name occupies the remaining bytes. ELF32
stores a 32-bit CRC and a 60-byte name. ELF64 stores a 64-bit unsigned long
and a 56-byte name. CRCs are zero-padded lowercase hex, 8 or 16 characters,
not a JavaScript number. The layout is reported from the ELF class even when
the section is absent. Names use the same byte-preserving Latin1 projection
as `.modinfo` keys and values. Only this classic `__versions` layout is
decoded. `versions.status: "absent"` means no `__versions` section was found
when section names were complete; it does not establish the absence of version
metadata in `__version_ext_names` and `__version_ext_crcs`, which Linux uses
with `CONFIG_EXTENDED_MODVERSIONS` and this workflow does not decode.
`.gnu.linkonce.this_module` is reported as present, absent, or unknown.
`layout` is always `unknown` because `struct module` changes with the kernel
version and is not decoded. File-backed `__ksymtab*` sections are listed in
section-header order by name and file range; their records are not decoded.
Sections that are `SHT_NOBITS` or leave the file are counted in one limitation
without their names. `.init.text` and `.exit.text` report the
observed file-backed size. A compressed payload reports the stored size and a
limitation that the decoded size is unknown.
The signature trailer is the 27-byte magic `~Module signature appended~\n`
preceded by a 12-byte `module_signature` (`algo`, `hash`, `id_type`,
`signer_len`, `key_id_len`, three pad bytes, and big-endian `sig_len`). When
those parameter fields are zero and `id_type` is PKCS#7 (`2`), the preceding
`sig_len` bytes are the PKCS#7 range and its SHA-256. The range is not parsed
and no certificate, signer, or digest-coverage claim is made. An absent
trailer is observed absence, not proof the module is unsigned.
When section names are incomplete, a missing named section is `unknown`
rather than `absent`. An empty file-backed section is present and empty.
## Limits and failures
The selected file is limited to 64 MiB by default (512 MiB maximum). At most
8192 `.modinfo` and `__versions` entries plus listed `__ksymtab*` sections are
examined by default (65536 maximum); `coverage.examined_entries` counts all
three. Exceeding that budget fails with no partial success. Keys longer
than 256 bytes and values longer than 4096 bytes stay malformed per entry;
the field that fits is still copied. A `__ksymtab*` section name longer than
256 bytes keeps its range with `name: null` and `ksymtab_name_exceeds_limit`. Inline raw hex stops at 4096 bytes and
the SHA-256 remains.
A `.modinfo` span with no trailing NUL is `unterminated_modinfo_entry` and
earlier entries are kept. A string with no `=` is `modinfo_missing_separator`.
An empty key is `modinfo_empty_key`. A `__versions` name with no NUL is
`version_name_unterminated`. A `__versions` section whose size is not a
multiple of 64 bytes is closed with `versions_size_not_multiple` and no
entries. `SHT_NOBITS` or a declared range that leaves the file is
`modinfo_section_not_file_backed` or `versions_section_not_file_backed`.
`SHF_COMPRESSED` payloads are not inflated
(`modinfo_section_compressed`, `versions_section_compressed`).
An appended magic with fewer than 12 preceding bytes is
`signature_trailer_truncated`. A `sig_len` that does not leave at least one
module byte is `signature_length_inconsistent`. Nonzero `algo`, `hash`,
signer, key-id, or pad fields for PKCS#7 are `signature_parameters_nonzero`.
A parameter block whose `id_type` is not PKCS#7 is
`signature_id_type_not_pkcs7` and is unsupported rather than a validated
signature.
gzip (`1f 8b`), xz (`fd 37 7a 58 5a 00`), and zstd (`28 b5 2f fd`) whole-file
containers are `unsupported`. The result names `external_decompression` as the
next probe and does not claim the payload is a kernel module. This is an external
step, not a REA operation: neither this tool nor `extract_artifact` decodes these
containers. Use an external gzip, xz, or zstd utility to decompress into a separate
file, then call `inspect_kernel_module` on that file to inspect the `ET_REL` image.
Malformed ELF headers return no partial kernel-module metadata. This workflow
does not execute the module or validate an appended signature.
## Format references
The record layout follows Linux v6.12's
[`modversion_info`](https://github.com/torvalds/linux/blob/v6.12/include/linux/module.h)
and [`MAX_PARAM_PREFIX_LEN`](https://github.com/torvalds/linux/blob/v6.12/include/linux/moduleparam.h).
The [Linux kbuild module versioning guide](https://docs.kernel.org/kbuild/modules.html#module-versioning)
describes the separate extended version sections.
Trailer fields and acceptance rules follow
[`module_signature`](https://github.com/torvalds/linux/blob/v6.12/include/linux/module_signature.h)
and [`mod_check_sig`](https://github.com/torvalds/linux/blob/v6.12/kernel/module_signature.c).
These references establish the decoded layout, not runtime verification.
## Real-module verification lane
Run from a built checkout with Docker available. This downloads a Debian kernel
package into a disposable container, extracts a filesystem module, decompresses
it when necessary, and inspects it with REA. It never installs or loads that
kernel module. The temporary output is outside the repository; record the
printed package version and module digest with results.
```bash
module_lane=$(mktemp -d)
docker run --rm -i -v "$module_lane:/out" debian:stable bash -se <<'CONTAINER'
apt-get update -qq
apt-get install -y --no-install-recommends xz-utils zstd ca-certificates
cd /tmp
arch=$(dpkg --print-architecture)
package=$(apt-cache depends "linux-image-$arch" | awk '/Depends: linux-image-[0-9]/{print $2; exit}')
test -n "$package"
apt-get download "$package"
dpkg-deb -f ./*.deb Package Version > /out/package.txt
dpkg-deb -x ./*.deb /tmp/kernel
module=$(find /tmp/kernel -type f -path '*/kernel/fs/*' \( -name '*.ko' -o -name '*.ko.xz' -o -name '*.ko.zst' -o -name '*.ko.gz' \) | sort | head -1)
test -n "$module"
case "$module" in
*.xz) xz -dc "$module" > /out/module.ko ;;
*.zst) zstd -dc "$module" > /out/module.ko ;;
*.gz) gzip -dc "$module" > /out/module.ko ;;
*) cp "$module" /out/module.ko ;;
esac
cat /out/package.txt
sha256sum /out/module.ko
CONTAINER
node scripts/rea.mjs inspect-kernel-module "$module_lane/module.ko" --json > "$module_lane/rea.json"
python3 - "$module_lane" <<'PY'
import json, pathlib, sys
root = pathlib.Path(sys.argv[1])
report = json.loads((root / 'rea.json').read_text())['normalized_result']
entries = report['modinfo']['entries']
assert entries and all(entry['status'] == 'observed' for entry in entries)
keys = [entry['key'] for entry in entries]
for key in ('license', 'vermagic', 'name'):
assert key in keys, key
print('Observed .modinfo entries:', len(entries), 'distinct keys:', len(set(keys)))
print('Artifact:', report['artifact'])
PY
```
`parm` and `parmtype` are retained separately by REA. Synthetic tests cover
their generic key/value representation and all four ELF class/byte-order
combinations. A successful real lane verifies only the selected module and
printed fields; it does not validate certificates. This lane has not run in the
sandbox used for implementation because access to the Docker socket was denied.
+3 -7
View File
@@ -68,9 +68,9 @@ The background export belongs to the session. `close_binary`, or the end of a CL
The assembly label index is scanned once per export reader. Cancelling a request stops its wait without interrupting other readers; closing the session aborts and drains the shared scan before releasing its files.
Regex procedure and string search uses ECMAScript Unicode matching in a cancellable worker with a matching deadline and a worker old-generation heap. Literal matching remains the default. Cancellation or a matching deadline leaves the session available. The deadline is `REA_REGEX_MATCHING_DEADLINE_MS`, a positive decimal integer from 1 to 2147483647 ms. Unset uses 5000 ms. A larger value is refused because Node schedules a delay past that signed 32-bit bound as 1 ms. Invalid values are configuration errors and do not fall back to the default. The setting is read from the environment that starts REA, so a change needs a restart. A deadline failure names the effective milliseconds, whether they came from the default or the setting, the default, and the maximum. The worker old-generation heap does not raise it. The worker heap request is `REA_REGEX_WORKER_HEAP_REQUEST_MIB`, a decimal integer from 7 to 16384 MiB. Unset uses 128 MiB. A request below 7 MiB aborts the worker during startup, and a larger value is refused. Invalid values are configuration errors and do not fall back to 128. The setting is read from the environment that starts REA, so a change needs a restart. A heap failure names the effective request, whether it came from the default or the setting, the default, the floor, and the maximum. A positive Node.js `--max-old-space-size` on the process that starts REA, including `NODE_OPTIONS`, overrides that request and sets the effective heap. `--max-old-space-size-percentage` takes precedence over that flag in either order. REA does not turn the percentage into a MiB, so the effective heap stays unestablished instead of the worker heap request or the absolute flag. The reported `NODE_OPTIONS` value is the process initial environment when that block can be read. A later edit to the environment is not the value Node applied, and an unreadable initial value leaves the effective heap unestablished. The launch Node options are the initial command line on Linux when that buffer still has a program entry, not a later edit to `process.execArgv`. A preload can clear that array after V8 has applied the flags. Setting `process.title` or `--title` overwrites the command line, so a buffer without a program entry leaves the effective heap unestablished. `--env-file` or `--env-file-if-exists` can supply `NODE_OPTIONS` when the initial environment has none. That file is not part of the initial environment block, so the effective heap stays unestablished instead of the worker heap request. An initial `NODE_OPTIONS` is still used, because Node does not let the file replace it. A positive `--max-heap-size`, including the underscore form and launch `NODE_OPTIONS` on Node versions that allow it, sets the total heap rather than the old generation and can be lower than the worker heap request. The effective old generation stays unestablished while that value is in effect. A zero or empty value clears it. `--experimental-config-file` or `--experimental-default-config-file` can set `nodeOptions`, including a heap percentage, from a file that is not read back. The effective heap stays unestablished when either flag is present, including when an absolute `--max-old-space-size` is also on the command line or in the recovered `NODE_OPTIONS`. Node accepts the config path as `=` or the next argument.
Regex procedure and string search uses ECMAScript Unicode matching in a cancellable worker with a five-second matching deadline. Literal matching remains the default. Cancellation or a matching deadline leaves the session available.
Single-function subprocess output and each cached source facet share a 16 MiB buffered-text capacity. Cached spans are checked before allocation; larger text remains in the local export and the error identifies the file and range. `analyze_function` with `detail: "complete"` (the CLI default) returns the complete dossier inline, which can still be large; later retained views do not remove that first transfer. The MCP default retains the dossier and returns its summary, so page the facets you need through `inspect_analysis_view`, or prefer the individual text tools for a focused question.
Single-function subprocess output and each cached source facet share a 16 MiB buffered-text capacity. Cached spans are checked before allocation; larger text remains in the local export and the error identifies the file and range. `analyze_function` initially returns the complete dossier, which can still be large; later retained views do not remove that first transfer. Prefer the individual text tools for a focused question.
## Strings
@@ -125,7 +125,7 @@ REA writes Kuna's exports only inside `REA_KUNA_CACHE_DIR`:
- `trash/` holds retired builds and evicted exports awaiting removal.
- `home/` is Kuna's HOME.
Builds publish by one rename, so a reader sees a complete export or none. If later staging retirement fails, the typed cleanup error retains the completed export's key, directory and manifest in `published_export`. The session adopts that verified project; later queries reuse its immutable files while `project_export.cleanup_incomplete` reports the retained owner's live state. `close_binary` retries only the remaining cleanup, and a successful retry resolves prior cleanup errors. A failed export followed by failed staging cleanup retains both failures; it does not invent a publication receipt. A session keeps the export it uses from eviction only within its own REA process. If another REA process sharing the cache evicts it, the session rebuilds it; meanwhile pseudocode falls back to single-function runs. A damaged or edited export is removed and rebuilt. Staging moves to `trash/` for removal only after REA proves its Kuna process stopped. Staging of a process it cannot prove stopped is never swept; REA keeps it owned, retries the stop on `close_binary`, and reports it until the stop is proven. Failed file removal also stays bound to its export and is retried by `close_binary`; closing does not report success while that cleanup remains. `rea doctor --provider kuna` reports the cache directory without creating it.
Builds publish by one rename, so a reader sees a complete export or none. A session keeps the export it uses from eviction only within its own REA process. If another REA process sharing the cache evicts it, the session rebuilds it; meanwhile pseudocode falls back to single-function runs. A damaged or edited export is removed and rebuilt. Staging moves to `trash/` for removal only after REA proves its Kuna process stopped. Staging of a process it cannot prove stopped is never swept; REA keeps it owned, retries the stop on `close_binary`, and reports it until the stop is proven. Failed file removal also stays bound to its export and is retried by `close_binary`; closing does not report success while that cleanup remains. `rea doctor --provider kuna` reports the cache directory without creating it.
## Verify with a real Kuna
@@ -139,7 +139,3 @@ REA_KUNA_COMMAND=/absolute/path/to/kuna GHIDRA_INSTALL_DIR=/absolute/path/to/ghi
The lane compiles a small source-owned fixture with the host C compiler and verifies the MCP workflow end to end. `--targets DIR --functions a,b` adds caller-built binaries. `--compare-ghidra` (with `GHIDRA_INSTALL_DIR`) runs those same functions through Ghidra; it requires `--targets` and `--functions` and refuses empty matrices or requested function names missing from every target. See [testing](testing.md#kuna-lane) for coverage.
Failed runs also report process-lineage evidence and exit unsuccessfully. If provider or transport cleanup is unconfirmed, the report says `cleanup: "incomplete"` and records `retained_workspace`; that directory is preserved for investigation and cleanup retries.
Doctor reports an invalid Kuna cache, job count, or timeout setting with that
setting's correction. Correct or unset the named setting and restart REA; these
configuration rejections do not require replacing the selected Kuna executable.
+13 -263
View File
@@ -51,11 +51,6 @@ This keeps the complete source in the session and returns a distinct linked
evidence, metadata status, module/MVID, assembly, frameworks, returned-row counts,
coverage/issues and limitations. No assembly is loaded or executed.
ECMA-335 permits empty metadata streams to be omitted. An absent `#Blob`
stream therefore does not prevent module or assembly identity from being read
when all blob indexes are zero; a nonzero reference to that missing heap is
reported as a missing-stream issue.
Use `normalized_result.parent_evidence_id` to page original rows:
```json
@@ -90,7 +85,7 @@ rejects selected summary before analysis; complete delivery remains available.
Complete `inspect_managed_members` delivery always includes the assembly-wide
`member_refs` and `exported_types`, which `type_names` does not narrow, so even
one selected or unmatched type can return megabytes. In MCP, select
one selected or unmatched type can return megabytes. Select
`{"path":"/absolute/path/Assembly.dll","type_names":["Namespace.Type"],"detail":"summary"}`
to keep the complete source in the session and receive identity, metadata
status, the selection with its unmatched names, coverage/issues, the separate
@@ -120,49 +115,6 @@ literal pool's own coverage. Older Evidence that never examined exported types
or user strings reports a null count and refuses those pages rather than
returning an empty one.
#### CLI summary and saved member rows
The CLI accepts the same `complete` and `summary` detail choices. Omitted or
`--detail complete` returns complete Evidence. To receive a summary, choose a
new Evidence bundle destination so its original complete parent remains
available after the command exits:
```bash
rea inspect-managed-members /absolute/path/Assembly.dll --type Namespace.Type \
--detail summary --evidence-bundle /absolute/path/evidence/members.json --json
```
`--evidence-bundle` writes the complete original Evidence in REA's canonical
bundle format. Its parent directory must exist, and it never replaces an
existing path. Relative paths resolve against the command's working directory.
The option also works with complete delivery when a saved bundle is useful.
Summary without a destination is rejected before analysis. A publication failure
returns a typed error with the completed Evidence under
`details.partial_observation`; it does not report successful summary delivery.
Cancellation before publication prevents the new output. Once atomic
publication has started, it can complete despite cancellation; a cancelled
delivery then retains the committed path and byte count under
`details.diagnostics.evidence_bundle`.
Copy `normalized_result.parent_evidence_id` from the summary into the
`source.evidence_id` field of the `managed-members` request above, then save that
small request as `members-page.json` and run:
```bash
rea inspect-analysis-view /absolute/path/evidence/members-page.json \
--evidence-bundle /absolute/path/evidence/members.json --json
```
The view command authenticates the selected bundle and resolves that exact
parent ID. It does not reopen the assembly, so the original target may have
been moved or removed. Subsequent calls can select another collection or use
the returned `coverage.next_offset` to continue the same collection. An inline
source already includes its parent and cannot also select `--evidence-bundle`.
Summary changes delivery, while type selection still controls decoding and the
same inspection resource limits apply. Each separate CLI page reads and
authenticates the saved bundle; the full source is retained on disk.
## Shipped scope
The canonical parser admits PE/CLI bytes and their metadata/CIL without loading
@@ -182,9 +134,6 @@ declares API compatibility across multiple .NET implementations. REA preserves
that target in `target_frameworks` and leaves `runtime_family` as `unknown`
unless independent markers identify a runtime family, such as a ReadyToRun
signature.
When ReadyToRun is observed, `runtime_family` remains `modern-dotnet` even if
Unity or target-framework hints are also present; the separate hint evidence
is retained in the classification evidence list.
### Resource admission
@@ -206,40 +155,10 @@ reports `resource-limit` with unavailable or partial coverage; omitted facts do
not become empty observations. Ambiguous PE section mappings are rejected
rather than interpreted according to section order.
Minimal Edit-and-Continue metadata marked by `#JTD` is not a standalone
assembly inventory. The pinned [.NET 9 reader](https://github.com/dotnet/runtime/blob/v9.0.0/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/MetadataReader.cs)
uses different reference widths for this mode, and delta handles require the
baseline and prior generations. The released
[metadata serializer](https://github.com/dotnet/runtime/blob/v9.0.0/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.cs)
emits this marker with `#-`; the
[PE writer](https://github.com/dotnet/runtime/blob/v9.0.0/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/ManagedPEBuilder.cs)
can embed that metadata root. REA reports `unsupported-metadata` at the marker
header before interpreting table rows, preserving the selected path, artifact
digest, parsed metadata version and stream names, and admitted PE/CLI facts.
Metadata status is partial; member and native
declaration inventories are unavailable. Empty collections do not establish
absence, and a requested type name is not declared unmatched. Ordinary
standalone `#~` and `#-` metadata keep their existing interpretation. This does
not add raw `.dmeta` input, baseline attachment or delta reconstruction.
Regression coverage uses authored metadata in a generated PE shell; it does
not establish naturally emitted application PEs, CLR loadability or a real
Edit-and-Continue execution.
### Selected member inspection
Member inspection admits type names, tokens, list ranges/counts, nesting facts,
module identity and their output representation before decoding members. If
later decoding or projection reaches the 64 MiB representation budget, the
result retains this type inventory with `coverage.state: "partial"` and the
original `resource-limit` coordinate. It does not return partially admitted
member collections: empty fields, methods, references and edges are unexamined,
not proof of absence; exported types, user strings and signature-resolution
attestation remain null. Summary and retained pages keep this partial coverage.
The capacity limit is unchanged. If metadata or the type inventory itself cannot
be admitted, the result remains unavailable.
Use the returned `types[].full_name` values to select declaring types before
their signatures and CIL bodies are decoded:
When whole-assembly member decoding reaches the representation budget, select
known declaring types before their signatures and CIL bodies are decoded:
```bash
rea inspect-managed-members /absolute/path/Assembly.dll --type Example.Parser
@@ -248,12 +167,9 @@ rea inspect-managed-members /absolute/path/Assembly.dll --type Example.Parser
MCP uses `inspect_managed_members` with
`{"path":"/absolute/path/Assembly.dll","type_names":["Example.Parser"]}`.
Repeat `--type` or supply multiple unique `type_names` for several types.
Names match the reported `full_name` exactly. Top-level names require that
namespace-qualified spelling. Nested declarations also accept their bare
`name`, including when their own namespace is nonempty; this can select several
types with that name. Selection uses the parsed TypeDef facts and comparison
uses the returned declaring types, rather than reconstructing scope from the
caller's spelling. Each type's `enclosing_type_token` records its enclosing
Names match the reported `full_name` exactly. Top-level names are namespace
qualified; nested types currently report bare names, which can select several
types with that name. Each type's `enclosing_type_token` records its enclosing
TypeDef. `nesting_read` distinguishes complete source rows (true), incomplete
current rows (false), and historical Evidence (null). A null enclosing token
alone does not establish that a type is top-level.
@@ -272,36 +188,7 @@ remain an assembly-wide inventory, so output size is not guaranteed to be small.
Coverage describes the selected members. Comparisons leave members outside the
opposite observation's selection unknown, and application graphs report their
selected scope as partial. Neither makes an absence claim about the whole
assembly. Older member Evidence without `selection` remains readable. The
comparison resolves a member's declaring TypeDef token in its own source, then
checks that nominal `name_sha256` against the opposite selected type set.
Pairing, absence and aggregate selected-scope agreement share these retained
facts, including enclosing names, rather than requested spellings or display
labels. Equivalent requests can cover the same types, while the same display
name can select different nested owners. Such disjoint selected owners cannot
establish a member pair or added/removed status even when both inventories are
complete. Missing nominal identities cannot establish selected membership,
including historical selected Evidence that retains only display names;
the explicitly limited historical whole-inventory comparison remains available.
Incomplete selected inventories or missing identities leave aggregate agreement
unknown and coverage partial; known observed member pairs remain available.
Complete empty selected inventories agree even if their unmatched request names
differ. Whole-assembly and selected
inventories remain distinct scopes, and each source retains its exact request.
The `method_rows` interval records the examined zero-based MethodDef offset, row
count and full table total before type selection. A complete observation can
cover only that interval. Comparison keeps omissions from a method page unknown:
build-local row positions cannot prove member absence across builds. Historical
Evidence without this interval also cannot establish method absence; observed
pairs and field inventory comparisons remain available.
For MCP callers, request `detail: "summary"` and page the `types` collection
using the returned parent Evidence ID. Select one or more returned full names
in a subsequent `inspect_managed_members` request. Selection is a recovery path,
not a guarantee: a single type or the assembly-wide member references can still
exhaust the representation budget. A successful command exit means the Evidence
was returned; inspect `coverage.state` and `coverage.issues` before treating any
collection as complete.
assembly. Older member Evidence without `selection` remains readable.
### User strings
@@ -360,30 +247,8 @@ when matching declared native calls to supplied native Evidence. Static inspecti
does not load or execute the assembly. Reconstructed structure and unresolved
native/runtime questions retain their own authority and coverage.
When the selected source changes during acquisition or no longer matches its
admitted digest, inspection and comparison report retryable `artifact_changed`;
any completed hash retains the observed digest and byte count. Retry against a
stable source. This differs from corrupted retained Evidence, which remains an
Evidence-integrity failure. Malformed selected PE input has the same input-format
classification in inspection and comparison. Its issue identifies the selected
filename and argument: `path` for inspection, or `leftPath` / `rightPath` for CLI
comparison. A failed right-side comparison retains the completed left observation.
## Member comparison
Call edges and field accesses retain `il_offset` as their instruction site within
the caller's build-local method token and artifact identity. Their
`target_identity_sha256` combines the existing enclosing-type nominal digest,
member name and resolved decoded signature. Comparison joins edges to their
instruction sites rather than array positions and compares these identities;
display labels alone cannot establish equal targets. Same-named nested types
under different enclosing types and different overload signatures stay distinct.
This is nominal metadata identity, not assembly resolution or runtime binding.
Historical edges without a site or identity, unresolved/cyclic type scopes,
TypeSpec owners and unresolved MethodSpec instantiations retain unknown target
equivalence. Valid MethodSpecs combine the base member identity with the
resolved instantiation signature, preserving generic argument distinctions.
Names and semantic labels do not establish durable member identity. Comparison
preserves observed differences, structural inferences, and unknowns separately.
@@ -459,24 +324,6 @@ constants, and bounded shape context. It reports all candidates at the winning
score and remains ambiguous when the evidence does not distinguish them. The
digest does not itself remap metadata tokens.
Matched methods also compare aligned references. `call-shape` and
`field-shape` report opcode and operand-kind differences; `call-target` and
`field-target` report differences in the observed target declaring type or
name from `call_edges` and `field_accesses`. Target tokens remain build-local
and are not compared across artifacts. Missing or unaligned edges and missing
target names leave that facet unknown; equal names do not establish overload
identity or runtime dispatch. `exception-shape` compares clause flags and
ranges. `exception-target` compares the nested nominal catch-type name digest;
for resolved TypeDefs it also compares the enclosing assembly identity. A
different known name or assembly scope establishes a change. Equal TypeRef
names remain unknown because their resolution scope is not resolved here.
Exception tokens remain in the source facts but are not cross-build identity;
an unresolved catch type leaves that facet unknown. The method body retains the
display `class_type`, nested `class_type_name_sha256`, and whether its local
token identifies a TypeDef. A TypeDef digest alone does not prove identity
without matching assembly identity. Missing assembly identity and equal TypeRef
names leave the comparison unknown.
Tokens are always remapped through observed structure. A caller cannot carry
`0x06001234` into a new MVID and assume it names the same method.
@@ -527,47 +374,7 @@ point identities with supplied native export or function evidence. Name/module
matches establish declaration links; they do not establish that a call executed.
When either side lacks a usable module identity, a matching name remains
unresolved and retains the candidate evidence; REA reports a module mismatch
only when both identities are available and their format-specific comparison
establishes a difference. ELF module names retain case, PE module names compare
case-insensitively, and a case-only match for Mach-O or an unknown format stays
unresolved because the available Evidence does not establish case behavior.
For PE imports, `.dll`, `.exe`, a trailing period, and absolute paths suppress
appending `.dll`; other names may also be tried with `.dll`. Unix loaders use
the platform suffix (`.so` or `.dylib`) and `lib` prefix variations. A suffix
already present at the end of the name or before a period still gets an
appended-suffix variation, so Linux can match `nativedep.so.6` with
`libnativedep.so.6`. The `lib` prefix is omitted when the name contains `/`, and
absolute paths are used as supplied. POSIX paths keep backslashes as literal
characters. These candidates follow [Microsoft's native library loading rules](https://learn.microsoft.com/en-us/dotnet/standard/native-interop/native-library-loading).
These filename rules compare declared and observed identities; they do not
establish which library the runtime loaded.
Signatures retain their decoded fields, but a Field or MethodDef row whose
signature has the other member kind is malformed and cannot establish an
unchanged signature. Comparison retains exact byte-based pairs for malformed
signatures, including empty, truncated, or invalid headers, but reports them as
unknown with a `signature-malformed` limitation when no change is observed.
This applies to historical Evidence without signature-resolution attestation as
well. Runtime-provided methods are counted separately in
`native_implementations.runtime_provided`; they have no file-backed native body
for this workflow to map and do not increase `native_body_unresolved`. Historical
verification Evidence without this separate count leaves it unknown (`null`).
An empty declared import name remains unchanged in verification Evidence and
produces no native-name candidates. With a supplied PE native context and
`no_mangle: false`, an unspecified charset includes the ANSI `A` candidate;
`auto` includes the Windows Unicode `W` candidate. Other native formats do not
inherit these Windows defaults. Known ELF and Mach-O contexts use the declared
entry point without A/W suffixes or Windows stdcall decoration, including when
the metadata declares an explicit ANSI or Unicode marshalling charset. The
original charset and calling convention remain unchanged. This follows the
[.NET runtime's platform-specific entry-point lookup](https://github.com/dotnet/runtime/blob/v10.0.0/src/coreclr/vm/method.cpp#L3134-L3177).
Windows name decorations are static inferred candidates;
exact spelling still suppresses decoration and no runtime binding is proven.
The mapping follows [Microsoft's charset guidance](https://learn.microsoft.com/en-us/dotnet/standard/native-interop/charset).
Partial input coverage records a residual question even when no unresolved
individual boundary was produced. Selected literal inventories likewise remain
partial when unknown MethodDef ownership can hide a selected body.
only when both identities are available and differ.
Metadata tokens are not native addresses. Native body mapping for C++/CLI,
ReadyToRun, NativeAOT, and IL2CPP requires separately verified format and provider
@@ -578,32 +385,6 @@ Declared import inventory supports a bounded positive claim. Its absence does
not exclude dynamic resolution, generated code, protected code, native helpers,
or server-side behavior.
## Application graph relations
`project_managed_application_graph` turns member Evidence into `contains`
edges for declarations, `imports` edges for P/Invoke declarations, and two
relation kinds read from method bodies:
- `calls` links two `managed-method` nodes when a `call`, `callvirt`, `newobj`
or `jmp` site in the caller targets a MethodDef in the inspected module.
- `accesses` links a `managed-method` node to a `managed-field` node for each
field it loads, stores or takes the address of.
Each edge stands for every site between one source and one target. Its
properties carry `site_count` and per-opcode `opcodes` counts; `accesses`
edges also list `access` as `read`, `write` and/or `address`. Edge evidence
links the member Evidence.
Some sites are counted rather than projected: calls to MemberRefs (members of
other assemblies or of generic type instantiations), generic method
instantiations (`MethodSpec`), unknown targets, `ldftn`/`ldvirtftn` function
pointer loads, recursive self-calls (the graph has no self-loops), MemberRef
fields, and sites whose method or field node is absent, for example under a
type selection. Limitations name each counted class, and graph coverage is
`partial` with `omitted_count` equal to the omitted sites when nothing else is
incomplete. A missing `calls` edge is therefore not evidence that a method is
never called.
## Evidence record shape
Member comparison, application graph projection, and managed/native verification
@@ -637,10 +418,6 @@ preserving readable member and attribute facts.
The retained inverse indexes are admitted against the existing representation
budget before allocation.
Custom-attribute `type_name` uses `Outer+Nested` when its local TypeDef or
TypeRef enclosing chain is readable. It remains `null` when the constructor's
type or required enclosing facts cannot be resolved.
`exported_types` reports ExportedType rows as declared. Facade assemblies such
as .NET `System.Runtime.dll` provide their types this way: `forwarder: true`
marks a type forwarder, and `implementation_token` names the AssemblyRef, File,
@@ -654,19 +431,6 @@ CLI `#GUID` heap values retain their exact 16-byte GUID text. REA does not impos
RFC 4122 UUID version or variant bits on MVID, EncId, or EncBaseId because
ECMA-335 metadata does not require those bit patterns. Tokens and RVAs are
build-local coordinates, not durable cross-build identities or native addresses.
Field and method signatures validate embedded TypeDefOrRefOrSpec operands
against the artifact's metadata tables. An invalid operand leaves the member
name and raw signature digest available while marking that signature malformed
and coverage partial.
A fat method header's nonzero LocalVarSigTok must name an existing StandAloneSig
row whose blob is a structurally valid LocalVarSig. REA validates its local
types and exact blob coverage but does not project their identities. An invalid
token or malformed signature leaves decoded CIL anchors intact but marks the
body malformed and coverage partial. Unsupported local type encodings retain
the readable body with partial coverage. Zero continues to mean no locals
signature. Comparison detects whether a locals declaration is present, but raw
table tokens are not cross-build identities and local type contents are not
compared.
## Decoded-CIL fingerprint
@@ -692,10 +456,6 @@ Tuple operands use these exact projections:
- signed integer and variable operands: decimal strings; floating-point
operands: JavaScript's `String` representation of the decoded IEEE value.
For `ldtoken` MemberRef operands, `operand_kind` follows the parsed member
signature (`field` or `method`); if that signature does not establish the kind,
the anchor keeps the generic `token` classification.
Opcode names retain short/long forms and prefix opcodes. This makes the value a
reproducible decoded-tuple fingerprint, not a canonical semantic CIL identity:
it does not resolve tokens across MVIDs, does not commit full control flow or
@@ -703,8 +463,7 @@ exception semantics, and does not replace the raw CIL digest. Cross-build
comparison requires equal raw CIL digests as well for exact CIL/signature
matches and reports ambiguity when structural evidence is insufficient.
For example, a present body (including any valid declared locals signature)
whose IL is `nop; ret` serializes exactly as
For example, a decoded `nop; ret` body serializes exactly as
`[["nop","none",null],["ret","none",null]]` and has digest
`5e5fad7741cb44bca3a4f045546b7449990da343f612f5f34c0ca30e9eee0636`.
This vector specifies the tuple order, `null` handling, absence of whitespace,
@@ -737,9 +496,7 @@ runtime, supported host, output contract, and conformance results.
Target platform is not host support. The canonical parser must inspect
Windows-produced PE/CLI bytes on REA's supported Linux and macOS hosts with the
same result. Pinned Windows oracle jobs may provide additional conformance, but
do not establish that the REA managed analysis workflow has been verified on a
Windows host. The [experimental Windows Ghidra boundary](windows-ghidra-p0.md)
has its own host and provider verification scope. Native
they do not claim that the REA application itself supports Windows. Native
ReadyToRun, C++/CLI, NativeAOT, or IL2CPP coverage is additionally constrained
by the selected Hopper/Ghidra host and format matrix.
@@ -865,13 +622,6 @@ which managed methods executed or how the CLR resolved them.
Managed member comparisons report a `metadata` dimension when an observed
MethodDef flags or implementation flags value differs, including accessibility
or synchronization changes. Observed method-body `init_locals` and `max_stack`
differences also contribute to `metadata`, even when CIL decoding is incomplete.
For complete bodies, presence or absence of a locals signature also contributes;
the locals table token is build-local and is not compared. Local type contents
are not currently compared, so this facet does not establish equivalent locals
declarations.
An unavailable header value keeps body coverage unknown rather than establishing
equality or a difference. These comparisons are separate from the CIL/signature
matching tiers: identical instructions do not establish unchanged method metadata.
The reported difference is static metadata evidence, not proof of runtime behavior.
or synchronization changes. This is separate from the CIL/signature matching
tiers: identical instructions do not establish unchanged method metadata. The
reported difference is static metadata evidence, not proof of runtime behavior.
+68 -339
View File
@@ -21,37 +21,11 @@ as an upstream tool's original report, and is `null` when there is none. The sam
record is retained in the session bundle. `analysis_profile` is always present:
a concrete profile object or `null`; either value participates in semantic
identity. Records omitting this field are rejected. Pass the returned Evidence
directly to a compatible comparison tool: complete `analyze_function` Evidence
can be passed directly to `compare_functions`, and `inspect_artifact` Evidence
to `compare_artifacts`. Use `get_evidence_bundle` when the task needs broader
directly to a compatible comparison tool: `analyze_function` Evidence can be passed
directly to `compare_functions`, and `inspect_artifact` Evidence to
`compare_artifacts`. Use `get_evidence_bundle` when the task needs broader
retained session history or an explicit bundle for transfer.
`analyze_function` does not deliver its complete record by default. Without
`detail`, it retains the complete dossier in the session and returns
`inspect_analysis_view` summary Evidence whose
`normalized_result.parent_evidence_id` names that dossier; see
[retained analysis Evidence](#retained-application-evidence-inputs). Page the
dossier with `inspect_analysis_view`. Tools that consume function dossiers
reject the summary record. Give `compare_functions` the retained dossier as
`{"kind": "retained-evidence", "evidence_id": "<parent_evidence_id>"}`; give
`match_native_functions` the same reference inside `left_observations` or
`right_observations`, since its `left` and `right` take `list_procedures`
Evidence. Request `detail: "complete"` for `build_call_path`, which accepts
only inline complete dossiers.
Saved records from the pre-6.6 identity projection that omitted
`relationship_coverage: null` from its digest remain incompatible, even when
the serialized field is present. REA recognizes that exact previous commitment
for its diagnostic and keeps rejecting it; the original ID and file are not
rewritten. Recreate the observation with the current release. Re-exporting the
old bundle cannot update its identity. A nonmatching digest that does not
verify under that previous rule remains an ordinary integrity mismatch.
Application comparison, export-shape comparison and feature tracing preserve
the same Evidence authentication failures: an admitted source that fails
authentication is `evidence_integrity_mismatch`, while an authenticated source with an unsupported
workflow role or invalid selector is `invalid_request`.
Ghidra inventories and function replies use `raw_result: null` when their facts
are already preserved in `normalized_result`, limitations, locations and
relationship coverage. Distinct overview details, measured load-image
@@ -60,36 +34,22 @@ derived metadata keep their upstream representation. Read function results from
`normalized_result`; older retained Evidence keeps its original representation
and identity.
Artifact source-map filename relationships require a unique, identified regular
file at the sibling path. A known directory cannot be the mapped file; the map
retains its actual container relation. This admission does not establish source
byte correspondence or decode the map's original source paths.
Artifact inventories declare `coverage.scope` (`root` or `container-entries`)
and `coverage.status` (`complete` or `unknown`) independently of diagnostic
wording. Comparison requires matching scopes, complete graph members and complete
traversal coverage before concluding equality. Fully observed roots can be
`unchanged` within root scope; their scope notice stays in `limitations` without
creating a gap. Root-scoped equality establishes equality of the observed root
bytes. Child-container presence and unexamined format details remain unresolved,
including when the root format is `unknown`. Content and executable metadata
changes still produce root deltas. Missing graph members yield `truncated`;
unknown traversal coverage or unavailable occurrence hashes retain uncertainty.
Integrity mismatches remain
traversal coverage before concluding equality. A fully observed root without
container children can be `unchanged`; its scope notice stays in `limitations`
without creating a gap. Content and executable metadata changes still produce
root deltas. Missing graph members yield `truncated`; unknown traversal coverage
or unavailable occurrence hashes retain uncertainty. Integrity mismatches remain
contradictions. Static artifact equality does not establish unchanged runtime
behavior.
Source-map relation deltas include the content identity of the mapped source,
so an unchanged map file is reported when its target source bytes change.
Exhausted traversals with deliberately omitted filesystem entries retain
`unknown` coverage and identify each omitted path/type in `limitations`; FIFO,
socket and device entries are not opened. Case-distinct archive members remain
separate observations. Their case-insensitive materialization diagnostic does
not reduce traversal coverage when both spellings were read.
PyInstaller inventory also preserves distinct table rows that share a logical
path, including decoded children beneath a colliding file/directory path.
Inventory coverage describes observed archive facts; extraction still refuses
destinations where those paths cannot coexist.
Current inventory Evidence requires scoped `coverage` with a `complete` or
`unknown` status. Consumers use this producer-owned fact and reject records
@@ -120,12 +80,6 @@ can observe backend recovery; healthy dossiers keep their existing cache policy.
`procedure_pseudo_code` still fails for that function. Hopper's unavailable reference-kind metadata and reported unresolved
calls still prevent equality conclusions for those dimensions. Analysis profiles
version these declarations so older snapshots are not reused as current results.
For `strings_names`, a string marked with `decoding.available: false` keeps its
provider display text in the source Evidence, but that text is not compared as
decoded content. If no independently known string, name, or literal fact differs,
the dimension remains `unknown` with the decoding reason. Known content and typed
string-object differences can establish a change; missing typed metadata remains
unknown, and `encoding_status` describes provenance rather than content.
IDA declares the facets its selected adapter cannot provide; an empty
unsupported array does not prove observed absence.
Evidence and bundle imports reject unsupported fields in their envelopes,
@@ -141,15 +95,11 @@ optional bounded `recovery` next step. `recovery.kind=inspect_input_contract`
names the rejected operation: re-read that operation's advertised input schema
(call `tools/list` if the catalog may be stale), correct the listed fields, and
do not repeat the unchanged call. `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
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;
do not invent one. Raising only the MCP
client's deadline cannot extend this budget. These hints never include the
complete catalog or caller arguments.
the operation and the REA server/provider deadline; inspect partial Evidence
and reported cleanup state, then follow producer-specific remediation or reduce
supported work. Generic errors do not assume adjustable timeout controls. Raising
only the MCP client's deadline cannot extend this budget. These hints never
include the complete catalog or caller arguments.
Failures omit `structuredContent`, since each advertised `outputSchema`
describes successful results. Successful replies retain their schema-validated
structured data and matching text projection. Oversized errors are retained
@@ -177,11 +127,6 @@ visible. When a requested goal is not reached but an expansion is unresolved,
`goal_status` is `unknown`; `not_reached` means traversal exhausted without
unresolved relationship reads. Cancellation remains a tool error.
`trace_feature` validates each reference's containing-procedure resolution using
the same contract as navigation context. Explicit absence remains a successful
negative observation; malformed provider replies return `unreadable_output`
for `resolve_containing_procedure` before successful-result projection.
REA prepares complete MCP results within the pinned stdio client's 10 MiB
receive-buffer budget, including both text and structured representations and
room for the JSON-RPC envelope. Tool errors omit `structuredContent` from the
@@ -278,16 +223,6 @@ Evidence ID. Distinct results retain independent questions and source links;
repeating the same result does not create another unknown. Workflow Evidence
keeps the original residual question text. Explicit `record_unknown` requests
keep caller-selected identity and reject duplicate creation.
Duplicate creation is a nonretryable `invalid_request`; use the existing head
or update it with its current `expected_revision`. A stale update remains a
retryable `revision_conflict`.
When `record_unknown` or `update_unknown` commits but its complete reply exceeds
the response budget, `details.reported_limits.committed_unknown` identifies the
exact unknown ID, committed revision and mutation Evidence IDs. Its retained
Evidence reference remains available on the same connection. Export the bundle
or page `list_unknowns` to recover the result; do not repeat the committed
mutation. A delivery failure does not undo that revision.
For a portable exported bundle, the CLI shares this projection:
`rea inspect-evidence-bundle '{"path":"/tmp/evidence.json","detail":"summary","filters":{"operation":"analyze_function"}}' --format json`.
@@ -299,14 +234,11 @@ The destination changes only when a complete export is atomically published.
Clients that explicitly configure a larger receive buffer can set the REA
server's `REA_MCP_MAX_RESPONSE_BYTES` environment variable to the same byte
count. This setting must be a safe decimal integer at least 10485760 (10 MiB);
REA reserves 1024 bytes for the envelope. The value is read when the REA
process starts, so a change requires a restart and a matching client receive
buffer. Raising it restores complete inline delivery for responses that fit
that buffer. It does not raise REA's reserved single-string limit, which is
1024 UTF-16 code units below Node's single-string ceiling, and it does not
raise that Node ceiling. It does not change analysis coverage or retained
content. Ordinary responses keep their existing complete result contract.
count. This setting must be a safe decimal integer at least 10485760; REA
reserves 1024 bytes for the envelope. Raising it restores complete inline
delivery for responses that fit that buffer and Node's single-string limit.
It does not change the client's buffer, analysis coverage, or retained content.
Ordinary responses keep their existing complete result contract.
### Error recovery and deadline ownership
@@ -367,19 +299,15 @@ after target content was read.
## Retained application Evidence inputs
`analyze_javascript_application`, `inspect_binary_layout`, `inspect_artifact`,
`inspect_wasm_artifact`, `inspect_macho`, `decode_interface_builder`, and
`analyze_function` accept `detail`:
`complete` returns the complete analysis Evidence; `summary` records
`inspect_wasm_artifact`, `inspect_macho`, and `decode_interface_builder`
accept `detail`:
`complete` (default) returns the complete analysis Evidence; `summary` records
that complete Evidence in the session and returns the derived
`inspect_analysis_view` summary Evidence instead. `complete` is the default
except for `analyze_function`, whose dossier can reach several megabytes of
value flow and references, so it defaults to `summary`. Its
`inspect_analysis_view` summary Evidence instead. Its
`normalized_result.parent_evidence_id` and `evidence_links` name the retained
analysis, so later views and application workflows read it without repeating
analysis or moving the complete record through the response. A server without
session retention refuses summary detail with `capability_unavailable`;
`inspect_artifact`, `inspect_wasm_artifact`, `inspect_macho` and
`decode_interface_builder` refuse before any analysis starts.
session retention refuses summary detail with `capability_unavailable`.
For `inspect_artifact`, omission also keeps the original request parameters and
complete output shape. Delivery selection never reaches the inventory engine;
the complete inspection and its atomic inventory Evidence remain retained.
@@ -401,7 +329,7 @@ See [environment manifests](web-network-captures.md#environment-manifest).
`inspect_analysis_view` also accepts `environment-state` for the same parents,
extracting the initial state the recorded reads observed: entity types with an
identity field, entities, documents for non-entity reads, and a render
identity field, entities, documents for written non-entity paths, and a render
plan per read tool that reproduces its recordings from the state or names why it
cannot. Types and identity are inferences, so the Evidence is always inferred.
See [environment state](web-network-captures.md#environment-state).
@@ -424,41 +352,10 @@ application and semantic coverage. The result carries a digest of the selected
facts. Actual serialized size determines MCP transport admission: reduce the
page size or export retained Evidence if the view is too large.
`compare_source_to_bundle` accepts the committed historical graph inline or as
`reference: {"kind":"file","path":"/absolute/reference.json"}`. The shared
reader checks resource bounds and the graph's root commitment on every read. File-backed
comparison Evidence records `parameters.reference_source`: the exact
`requested_path` and the reader's absolute selected `path`, preserving POSIX
`..`/symlink semantics. Normalized comparison facts and mapping identities agree
with inline input; Evidence IDs distinguish caller-selected file origins.
Omitting `view` preserves the complete comparison. For a compact initial reply,
select `view: {"kind":"summary"}` or a `source-to-bundle` page; the session retains
the complete comparison before delivery. Later calls use `inspect_analysis_view`
with the returned `parent_evidence_id`.
A `source-to-bundle` view selects `collection: "mappings"` or `"candidates"` with
exact `source_paths`, `mapping_ids`, `statuses`, `fields`, `offset` and `limit`.
Omitted filters select all; empty arrays select none. The default mapping fields
include status, confidence, source digest/language and candidate/mapped counts.
Candidate defaults include kind, score and confidence; request `signals` for exact
supporting values. Mapping identity and `source_index` always remain; candidates
also retain `current_node_id`, `candidate_index` and whether they were actually
mapped. Indices refer to the complete comparison's item array and that item's
candidate array. All classifications are computed before selection. The original
`source_coverage` remains separate from page `coverage`; exhausting a page never
turns unknown correspondence into absence or proves behavioral equivalence.
The CLI accepts the same reference-file alternative (relative paths resolve from
the working directory). Save its complete `--json` Evidence, then use
`inspect-analysis-view` with an `evidence-file` source and the same view. Neither
adapter executes the target or repeats analysis for retained queries.
Completed `inspect_artifact` and atomic `inventory_artifact` Evidence also
support summary and inventory views. A summary reports the manifest's expected
counts separately from available record counts, observed entry kinds, inventory
Evidence IDs, the inventory producer's traversal `source_coverage` and
inspection coverage. Inspection coverage is `partial` when that traversal
coverage is unknown. An inventory view selects `nodes`,
Evidence IDs and inspection coverage. An inventory view selects `nodes`,
`occurrences`, `edges`, or `integrity_contradictions`, with `offset` and `limit`
defaulting to 0 and 64. For occurrences, optional `logical_paths` matches exact
recorded strings: omission selects all available records; `[]` selects none.
@@ -469,13 +366,6 @@ unreturned paths are absent. Views carry their own Evidence IDs and preserve
links to the original parent; complete producer output and historical inline
Evidence input parsing retain their existing shapes.
An `inspect_artifact` bundle published by the CLI includes its atomic substep
Evidence as addressable records alongside the inspection parent. MCP retains
the same source closure. After importing that bundle, select either the parent
or an atomic inventory by its retained Evidence ID without reopening the target.
Unsupported view families report an input issue at `view`; an invalid source
reports its issue at `source`.
Completed `inspect_wasm_artifact` Evidence supports a summary and
`view: {kind: "wasm", facet: "exports", offset: 0, limit: 64}`. Other facets
are `sections`, `imports`, `candidates`, `glue`, `wat`, `headers`, `details`, and
@@ -616,14 +506,7 @@ should expand `tool_availability.groups` and use `tools/list` for schemas. The
same catalog identity summary is returned by CLI `capabilities` and `providers`;
MCP tool definitions remain available through `tools/list`.
Provider capabilities are grouped by provider identity and shared facts.
Tool families group discovery; they do not imply an active-binary prerequisite.
Contracts whose handlers dispatch through that session declare it explicitly.
Cutter, GDB and Rizin debugger tools use their independent session/input owners,
so discovery does not ask them to open an unrelated binary. Session tool effects
also retain the applicable provider composition's UI, process, network and
filesystem declarations; for example, attached IDA navigation may show UI while
a headless profile launches a worker. In each
Provider capabilities are grouped by provider identity and shared facts. In each
set, `shared_limitations` is the ordered common prefix; append a group's ordered
`limitations` suffix to recover each operation's exact limitations. Group
`effects` arrays use the same true-only closed-vocabulary rule as tool
@@ -656,12 +539,6 @@ Binary-only operations and binary analysis snapshots do not apply in this
session. `open_binary` can switch back to executable analysis, and
`close_binary` retires either session kind.
`inspect_plist` requires an open binary or bundle-resource session. An explicit
`path` selects a plist in that context and does not create a session;
`binary_session` reports `target_required` while neither session is open.
Use `open_bundle_resources` for a bundle whose executable cannot be resolved.
The CLI `rea inspect-plist <path>` opens its selected artifact for one invocation.
Each provider client receives `binary_session.analysis_run.run_id` before any
provider startup, including clients whose startup fails. `process_lineage` is
`not_observed` until a dynamic provider starts, then becomes `snapshots` with
@@ -689,15 +566,6 @@ error's reported resources.
## Transports
The eager stdio runtime owns SIGINT, SIGTERM and stdin EOF before opening an
initial target or loading optional providers. Shutdown cancels the initial open,
waits for late startup handles, and closes them through the same retryable owner
as a running server, retaining provider documents. Incoming stdio bytes during
startup are staged without parsing, within the installed SDK's stdio buffer
capacity, then handed unchanged to its single framing owner. Exceeding that
startup capacity ends startup with a transport resource constraint; it has no
runtime override. HTTP startup never reads stdin.
stdio is the default transport and the only one that client registrations,
`rea mcp doctor` and the MCP registry entry describe. `rea mcp --http [--host H]
[--port N]` is an opt-in alternative that serves the same catalog over the
@@ -775,7 +643,7 @@ the bound address is local; names are added only after classifying the actual IP
On such a bind, requests whose `Host` header is outside the SDK loopback allowlist plus
the configured name and the bound address are refused with `403`, and a browser
`Origin` outside that allowlist is refused the same way (a missing `Origin`
passes); a non-loopback bind relaxes both checks and logs the relaxation. A reply sent before a request body was read (a refused `Host` or `Origin`, an unknown route, the startup `503`) closes the connection after the reply, so an unread upload cannot keep a socket open. Request targets are taken verbatim as origin-form paths (`//host/path` is a path, not another origin); an absolute-form target, a target containing a raw fragment delimiter (`#`), or one whose path URL parsing would rewrite (dot segments such as `/public/../mcp`), is refused with `400`, and a method Fetch does not support with `405`. Percent-encoded `%23` remains valid query data. Only an `initialize` object can replace an open session. A sessionless array containing initialize, including a one-element array, is refused with HTTP `400` before session creation or handover. Scoped
passes); a non-loopback bind relaxes both checks and logs the relaxation. A reply sent before a request body was read (a refused `Host` or `Origin`, an unknown route, the startup `503`) closes the connection after the reply, so an unread upload cannot keep a socket open. Request targets are taken verbatim as origin-form paths (`//host/path` is a path, not another origin); an absolute-form target, a target containing a raw fragment delimiter (`#`), or one whose path URL parsing would rewrite (dot segments such as `/public/../mcp`), is refused with `400`, and a method Fetch does not support with `405`. Percent-encoded `%23` remains valid query data. Only a lone `initialize` can replace an open session; a batch that carries one is left to the transport's refusal. Scoped
IPv6 literals (`fe80::1%eth0`) are refused before binding, because no URL can
name them. Listener errors reach the runtime's connection-loss handler only
after a successful bind; a bind failure prints the listen-failure message alone. Request bodies are bounded by the SDK's
@@ -908,11 +776,6 @@ temporary source files are declared as `launchesProcess` and `writesFilesystem`,
including for a single JavaScript source. This internal analysis does not execute
the supplied application code.
`analyze_web_bundle` also declares `launchesProcess` and `writesFilesystem`:
analysis of captured sources starts an owned worker and transfers source through
private temporary files. Its network and session effects remain declared, and
the worker does not execute the captured application code.
## Static Ghidra bytes and source mappings
`read_bytes` returns initialized analysis memory, including loader fixups, rather
@@ -932,25 +795,6 @@ of a later null value. Inspect the slot's `xrefs` and the writer's assembly or
pseudocode, retaining uncertainty about execution order and runtime values.
These operations inspect static evidence and do not execute the target.
## Editable analysis settings
Agents can call `update_analysis_settings` to raise a curated admission,
timeout or worker heap default on a connected server, then retry the same
request. Errors carry `details.limit` with the setting and bounds when the
failing owner supports runtime recovery; `inspect_analysis_settings` is optional
when those facts are supplied. Updates are atomic, apply to new work, survive
target close/open, and vanish when REA exits. Explicit per-call arguments take
precedence. Neither tool changes host deadlines or resolves retained cleanup.
See [runtime settings and recovery](design/limits-and-recovery.md#runtime-settings-through-mcp)
for the full setting list, reset/revision semantics and ASAR retry example.
For implementation ownership, accepted-operation snapshots, provider wiring,
and extension guidance, see the
[maintainer design](design/limits-and-recovery.md#ownership-and-data-flow).
Ghidra and Hopper startup updates apply to the next startup attempt without
restarting REA. Ghidra decompile timeout updates apply on the existing Program.
An operation already accepted retains its original budgets.
## Ghidra first-query deadlines and recovery
A successful MCP initialize handshake establishes the REA connection.
@@ -961,13 +805,13 @@ auto-analysis, bridge connection, and health readiness before returning analysis
These deadlines have different owners:
| Deadline | Owner and effect |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| MCP initialize | The client bounds transport/REA connection startup, before any target query. |
| Hopper startup | REA allows 120,000 ms by default; `REA_HOPPER_STARTUP_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid values fail configuration validation. Startup failure is reported by the first provider query. |
| Ghidra startup | REA allows 330,000 ms by default; `REA_GHIDRA_STARTUP_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid supplied values fail configuration validation. Startup failure is reported by the first provider query. |
| Darwin swiftc helper | REA allows 120,000 ms by default for ownership-helper compilation; `REA_SWIFTC_COMPILE_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid values fail configuration validation. Set it in the environment that starts REA and restart REA; the next ownership-helper compile uses the new value. |
| Individual tool call | The client bounds its wait, including cold engine startup. The pinned client SDK 2.3.1 defaults to 60,000 ms and can cancel earlier than REA's startup deadline. |
| Deadline | Owner and effect |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| MCP initialize | The client bounds transport/REA connection startup, before any target query. |
| Hopper startup | REA allows 120,000 ms by default; `REA_HOPPER_STARTUP_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid values fail configuration validation. Startup failure is reported by the first provider query. |
| Ghidra startup | REA allows 330,000 ms by default; `REA_GHIDRA_STARTUP_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid supplied values fail configuration validation. Startup failure is reported by the first provider query. |
| Darwin swiftc helper | REA allows 120,000 ms by default for ownership-helper compilation; `REA_SWIFTC_COMPILE_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid values fail configuration validation. |
| Individual tool call | The client bounds its wait, including cold engine startup. The pinned client SDK 2.3.1 defaults to 60,000 ms and can cancel earlier than REA's startup deadline. |
For [Codex and Kimi registrations](client-configuration.md), setup supplies
missing tool deadlines from the known provider allowance plus 120 seconds of
@@ -975,15 +819,7 @@ operation headroom: 450 seconds with the default Ghidra allowance, or 2820
seconds with `REA_GHIDRA_STARTUP_TIMEOUT_MS=2700000`. Positive caller-selected
deadlines remain unchanged. Doctor reports an informational recommendation
when a configured deadline is shorter than the known provider allowance. An
explicit JEB upstream deadline (`REA_JEB_MCP_TIMEOUT_MS`, shared by catalog
discovery and each tools/call) also contributes to that allowance.
An explicit `REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS` is added to the Ghidra startup
allowance as a single-decompile estimate. A multi-function call needs a client
deadline covering every attempted decompile plus other work; the static
registration value does not bound an arbitrary batch or `trace_native_values`
traversal. Split batches, use `decompile_all` limit/resume, or reduce the trace
`max_functions` to select work for the whole-call deadline. Existing caller
deadlines remain unchanged.
explicit JEB upstream request budget also contributes to that allowance.
For an already connected client using the pinned SDK, request options are the
**second** argument of `callTool`:
@@ -1052,13 +888,11 @@ the current client, so it is not a fresh-start recovery. On the same connection:
`provider_id: "ghidra"`, then retry the first query with an appropriate client
deadline. The connection and selected provider do not need to change.
If REA's provider startup allowance also needs raising, set
`ghidra.startup_timeout_ms` through `update_analysis_settings` before the next
startup attempt. This needs no REA restart and does not change accepted work,
clear cleanup blockers, or extend the client's deadline. Alternatively, change
`REA_GHIDRA_STARTUP_TIMEOUT_MS` in REA's launch environment and restart the
server, preserving Evidence and closing as above. Close/reopen on the same
connection cannot apply a changed launch environment. See the
If REA's provider startup allowance also needs raising, preserve Evidence and
close as above, then update `REA_GHIDRA_STARTUP_TIMEOUT_MS` in REA's launch
environment and restart the server. Reconnect the MCP client before reopening
the target. Configuration is read at process startup; close/reopen on the same
connection cannot apply a changed environment. See the
[CLI retry example](installation.md#ghidra).
This close/reopen flow was exercised on one real Linux stdio connection after a
@@ -1093,33 +927,12 @@ current/containing procedure. Its capability inventory exposes a
`current_selection` mode and an `explicit_document` mode; the latter works when
the caller supplies `document` and the provider lacks `current_document`. A
cursor outside a procedure is represented as
`procedure: null` only after a valid explicit absence result. Malformed
procedure resolution returns typed unreadable output; provider errors never
become absence from their message text. `inspect_address_context` requires an explicit address and
`procedure: null`. `inspect_address_context` requires an explicit address and
returns name, procedure, comment, inline-comment, and bookmark facets;
unsupported facets are local `unavailable` outcomes. Use `current_document`,
`current_address`, and `current_procedure` for direct single-field lookups; use
the aggregate tools when you need related context together.
## Integrity attribution and reconstruction claims
Artifact comparison results include `integrity_mismatch_evidence_ids`, naming
the actual inventory Evidence carriers with declared/observed integrity
mismatches. Argument order does not assign blame. The field is absent on older
comparison records, where culprit attribution remains unknown. Automatically
retained artifact unknowns use these source identities for contradiction links.
Static/runtime correlation preserves a selected contradiction or unresolved
conclusion without treating it as an observed content change. Such a selection
produces an unresolved pattern and cannot support a cochange hypothesis.
Ordinary observed content changes remain eligible for explicit hypotheses;
correlation does not establish causality.
An `overall` structural-function reconstruction claim selects every constituent
function dimension and its active scoped unknowns. A question scoped only to
pseudocode therefore keeps the aggregate unknown while an independent assembly
claim may pass. Unknown citations and recommended probes are retained.
## Integrity record-and-continue
Artifact integrity fails closed by default. A request can explicitly select
@@ -1143,32 +956,6 @@ bytes and returns the `integrity_contradictions` records for the extracted
files. An active ASAR unpacked entry whose companion file is absent cannot be
materialized; extraction fails as `unavailable` with that logical path.
If native DMG child traversal is unavailable after carrier identification,
the typed failure retains a revalidated root-only inventory under
`details.partial_observation.inventory`, with unknown child coverage and the
producer's diagnostics/provenance. It does not borrow or detach an existing
attachment. Native checksum verification failures still refuse traversal;
`record-and-continue` recovery applies to structured entry contradictions.
Occurrences use normalized logical paths for identity and lookup. Optional
`source_path` retains the original path within the owning reader when Unicode
NFC or reader admission changes its spelling; it is source evidence, not an
output destination. POSIX letter-colon names such as `a:b` remain valid logical
names; Windows extraction refuses names its filesystem cannot materialize.
Extraction materializes regular files exposed by the active archive reader.
Its temporary parent follows the filesystem semantics of `TMPDIR`, including
POSIX symlinks followed by `..`. A destination inside a directory source is
refused before copying; select a temporary parent outside that source.
When that reader exposes a nested archive as a file, extraction copies the
archive file; inspect and extract that file separately to unpack its contents.
Extraction retains inventory and materialization traversal limitations in its
result and outer Evidence. If either traversal has unknown coverage, the
observed regular files can still be copied, but the result does not establish
a complete source copy. For example, a POSIX filename that is not valid UTF-8
remains omitted with its raw-name explanation; it is not replacement-decoded
into an output filename.
Packaging tools commonly sign or strip `.asar.unpacked` native binaries after
writing the archive header, so their declared hashes no longer match. Electron
does not check unpacked companions against those hashes at runtime. Integrity
@@ -1178,12 +965,13 @@ failures for such entries name this cause and the operation to rerun with
## Schema advertisement
Advertised input and output schemas contain no reachable recursive references.
Input schemas inline repeated definitions before applying the selected
presentation profile. Both `full` and `compact` start from this self-contained
projection; output schemas can share schema-local definitions. Full inputs
retain nested fields, descriptions and valid literal examples. The compatibility
projections below can relax advertised constraints; canonical runtime validation
still enforces the complete input contract.
Schemas share repeated definitions through schema-local references while
preserving complete nested fields and validation rules. Input schemas whose
shared definitions would exhaust a client's declaration-rendering reference
budget (Pi 1.1.0 code-mode shares 32 reference expansions across an input and
renders every later reference as `unknown`) are re-projected without shared
definitions, so every advertised field renders with complete structure.
Input properties retain their descriptions and literal examples.
When a host forwards input schemas to the model, their examples consume context.
REA's advertised examples therefore never copy complete producer Evidence.
@@ -1195,9 +983,7 @@ advertised. Every advertised example is a distinct valid canonical input.
Root object unions are presented as
one object without root `anyOf`, `oneOf`, or `allOf` for model API compatibility.
The projection merges properties, retains shared required fields, and
describes the accepted input groups. A property default is advertised only when
every input group accepts that property with the same default, so default-filling
clients cannot inject a scenario-only property into a passive comparison. Which fields belong together is guidance
describes the accepted input groups. Which fields belong together is guidance
in that description rather than a `dependentRequired` or `dependencies`
keyword, because Gemini function declarations reject both and one unknown
keyword fails the client's whole tool catalog. Canonical runtime validation
@@ -1214,11 +1000,9 @@ masks without a repeating regular expression or temporary byte arrays. The
server rejects malformed or unconstrained patterns before provider dispatch;
the advertised strings alone do not express those rules.
Boolean and empty-string literal constraints in input schemas are relaxed only
in this advertisement. Boolean `const` becomes `type: boolean`; empty-string
`const` or an enum containing an empty string is widened without dropping the
accepted empty value. Defaults and descriptions remain intact.
An affected `not` is omitted, conditional predicates and their branches are
Boolean literal constraints in input schemas are relaxed only in this
advertisement. For example, a boolean `const` is presented as `type: boolean`;
an affected `not` is omitted, conditional predicates and their branches are
omitted together, pure boolean-literal `oneOf` branches collapse to a boolean,
and other affected `oneOf` branches are presented as `anyOf` to retain branch
guidance without making overlapping widened branches reject inputs.
@@ -1234,15 +1018,14 @@ verify the compact advertisement through the same client boundary.
Some function-calling providers reject a request while any single tool's
`parameters` schema exceeds a literal serialized size cap; Moonshot clients
such as kimi-cli have reported failures with the full catalog attached
such as kimi-cli fail every request while the default catalog is attached
([#1484](https://github.com/morluto/rea/issues/1484)). Setting the server's
`REA_MCP_INPUT_SCHEMA_PROFILE` environment variable to `compact` (the default)
renders every advertised input schema within a 13,312-byte budget, measured to sit below
`REA_MCP_INPUT_SCHEMA_PROFILE` environment variable to `compact` renders every
advertised input schema within a 13,312-byte budget, measured to sit below
Moonshot's observed cap (community measurements: a 13.7 KB schema is accepted
while a ~15 KB one is rejected). The setting is read at startup; restart the
server to apply changes, as with `REA_MCP_MAX_RESPONSE_BYTES`. Explicitly select
`full` to retain complete advertised input guidance and nested validation detail.
Existing explicit profile choices are preserved. Compact presentation
server to apply changes, as with `REA_MCP_MAX_RESPONSE_BYTES`. The default
value `full` keeps the complete advertisement unchanged. Compact presentation
also reduces schemas whose expanded JSON tree exceeds ten levels, the limit
reported by strict-depth MCP clients in [#2813](https://github.com/morluto/rea/issues/2813).
WorkBuddy/CodeBuddy gateways have also rejected full REA catalogs with an
@@ -1253,22 +1036,6 @@ Kimi-K3 backend, not the WorkBuddy MCP client. Set the profile on REA's
registration in the actual MCP host; the host's name alone does not identify
the downstream model or its schema constraints.
The compact profile also protects strict Anthropic-compatible gateways from
known schema-translation failures reported in [#3800](https://github.com/morluto/rea/issues/3800):
primitive `enum` properties without a sibling `type`, and `oneOf`/`allOf`
inside a property listed in `required`. Affected schemas use the same reduced
first-level projection described below, so the gateway receives a plain object
shape while REA continues to validate the complete canonical contract locally.
This is a compatibility projection, not a claim that the gateway reproduces
the full union semantics.
For array items expressed as an untyped `oneOf`/`anyOf`, the compact profile
adds a type when every branch has the same type. Mixed-type item unions use the
reduced projection; selecting one branch's type would exclude valid calls.
This addresses the emitted shape reported through a Gemini-backed gateway in
[#5201](https://github.com/morluto/rea/issues/5201). Gateway conversion and
live provider acceptance have not been verified for this projection.
The compact profile renders the same tool set, output schemas, annotations,
and canonical server-side validation. It changes only the advertised input
JSON Schema:
@@ -1300,11 +1067,6 @@ Boundary tests pin every advertised compact schema to the budget, keep them
valid JSON Schema that still accepts every canonical example. Client verification
also checks provider-facing sizes and canonical examples after conversion by
the actual Kimi Code CLI; local captures do not establish Moonshot acceptance.
The SDK catalog test checks that full-profile schemas retain nested ADB extra,
managed selection, and WASM timeout and detail restrictions. Path-contract tests
check full-profile local-path rules. Compact MCP workflow tests verify canonical
rejection of invalid inputs before provider work, independent of how much detail
the selected advertisement retains.
### Output schemas
@@ -1344,44 +1106,20 @@ includes all dimension limitations. Equal CFGs establish only equal observed
block sizes and successor relationships. Text from different provider
identities remains unknown.
The `calls` and `references` dimensions share endpoint identity and name
ambiguity rules. Calls retain procedure entry coordinates and direction;
references retain each edge's raw source and target coordinates, direction and
available kind facts. Resolving a reference endpoint to a containing procedure
does not replace its raw coordinate. Hexadecimal case and zero padding do not
change coordinates; address spaces remain distinct.
Coordinate equality requires equal artifact digests and formats, a known equal
architecture, and matching provider-owned native coordinate commitments. Those
commitments retain the exact selected Mach-O image where available and a digest
of the loader's mapping inputs. They exclude unrelated analysis settings, so
changing an analyzer option alone does not invalidate coordinate correspondence.
Equal coordinates in different slices or mappings cannot establish equality.
Hopper's default executable profile and Ghidra's supported deterministic imports
publish these commitments. Hopper loader overrides and attached databases, IDA
profiles, Ghidra universal-image imports without an exact slice commitment, and
older or imported Evidence without a commitment leave coordinate identity
unknown. Unparseable coordinates also remain unknown.
Across different artifacts, endpoints use nonempty procedure names whose observed
symbol facts establish a named origin. Dynamic/default symbols and unknown origin
remain unknown. Ghidra emits the same source/dynamic symbol facts for inventories
and procedure identities; Hopper, IDA and older imported dossiers without verified
origin facts cannot establish correspondence from display labels. No name-spelling
rule classifies `EntryPoint` or a `FUN_` prefix as generated. A name observed at
multiple procedure entries retains its label constraint but cannot establish equal
endpoints. Same-container records without a common coordinate context likewise
cannot establish equality from matching labels.
The `references` dimension preserves each edge's direction, endpoint identity
and available kind facts. Within one artifact it compares exact raw endpoint
coordinates, including the address space; hexadecimal case and zero padding
do not change identity. Resolving an endpoint to a containing procedure does
not replace the raw coordinate. Unparseable coordinates stay unknown.
Across artifacts, reference endpoints use stable procedure names. Missing or
address-generated names remain unknown. A name observed at multiple procedure
entries retains its label constraint but cannot establish equal endpoints.
Pairing preserves duplicate counts and considers alternative compatible
pairings; array order never identifies an edge. Compatible records with unknown
endpoint identity or reference kind facts remain `unknown`. Independent known
pairings; array order never identifies an edge. Compatible records with
unknown endpoint identity or kind facts remain `unknown`. Independent known
facts that contradict every possible pairing still establish `changed`.
Source Evidence retains the original addresses, names and unavailable facts.
Reconstruction verification uses the selected dimension's status: a same-name
call retargeting in a common context fails a calls claim; an unresolved slice or
mapping cannot pass it. Empty function labels leave `function_match` ambiguous
and the identity dimension unknown.
### Comparison input shapes
@@ -1392,8 +1130,7 @@ and the identity dimension unknown.
the complete `normalized_result` from `discover_webmcp_tools`, or null.
- Scenario: `before_scenario` and `after_scenario` contain complete
`normalized_result` objects from `capture_browser_scenario`. Optional
`normalization` defaults to `{ "rules": [] }` in the canonical scenario parser;
this scenario-only default is omitted from the merged advertised input.
`normalization` defaults to `{ "rules": [] }`.
The advertised schema includes complete nested capture fields and rejects
incomplete pairs and structurally malformed captures. Mixed comparison families,
@@ -1403,14 +1140,6 @@ invoking the handler. Domain validation additionally checks
relationships such as event sequence references and retained counts; JSON
Schema does not express those cross-field invariants.
Comparison also preserves the capture's coverage. A partial HTTP inventory can
establish an observed difference against a complete peer, but an omitted request
does not establish removal. Partial WebSocket streams do not establish frame
alignment. WebMCP frame IDs and tool keys are local to a capture; semantic owner
facts and known counts determine correspondence. Retained script digests can
establish source changes. See [browser comparison semantics](browser-observation.md)
for value uncertainty and storage-fingerprint recapture requirements.
## Generated catalog
Custom clients upgrading from 6.1 should follow the
-5
View File
@@ -61,11 +61,6 @@ The server advertises `listChanged: true`. Updating a registered guided prompt
emits `notifications/prompts/list_changed`, allowing clients to refresh their
cached prompt catalog.
Schema-invalid parameters to `prompts/get`, `prompts/list`,
`completion/complete`, and `tools/list` return JSON-RPC `-32602` with field
diagnostics. Correct the named fields and retry on the same connection.
Unexpected handler failures retain JSON-RPC `-32603`.
## Session-aware completion
MCP `completion/complete` completes guided-prompt arguments; it does not define
+5 -128
View File
@@ -5,11 +5,6 @@ GGUF v2/v3 or safetensors file. Detection uses content, including GGUF byte
order, rather than its extension. REA does not load a model, interpret weights,
run inference, import Python, open external-data references or use the network.
Unsupported GGUF versions remain refused. Their diagnostic preserves the four
version bytes and both UINT32 interpretations with byte order unknown, rather
than inferring big-endian from an unrecognized little-endian value. Little-endian
v2/v3 and big-endian v3 remain supported; big-endian v2 remains refused.
```bash
rea inspect-model-artifact /absolute/path/model.gguf --json
rea inspect-model-artifact /absolute/path/model.safetensors --hash full --json
@@ -30,11 +25,6 @@ result records the exact selected path, file size, header length, header SHA-256
bytes read and read scope. GGUF alignment padding is outside this prefix.
`artifact.sha256` and the Evidence subject are null: a header digest is not a
commitment to the weights. Different files can have identical headers and sizes.
The reported GGUF tensor-data offset follows the aligned tensor directory when
padding is present. For zero-tensor metadata-only files that end at the
directory, it is the directory end; no tensor payload is implied. A partial
alignment gap is rejected. `header_length` still ends at the directory, before
any padding.
`hash: "full"` additionally streams the remaining file bytes through SHA-256.
It returns a full-file digest and Evidence subject, while retaining the same
@@ -44,34 +34,13 @@ check descriptor and selected-path identity and metadata before completion;
this detects observed replacement or modification, not adversarial modification
that defeats filesystem metadata checks.
If full-file hashing fails or is cancelled after either format's header has
been validated, partial Evidence retains the parsed metadata and tensor directory.
Header coverage is complete; digests and final file identity remain unknown.
Safetensors header parsing is atomic, so interruption before validation does not
expose unvalidated JSON rows.
## Facts and selected queries
GGUF reports version, byte order, alignment, typed metadata, and each tensor's
name, original type ID, dimensions, relative data offset, file offset and derived
byte length. Alignment accepts every positive UINT32 multiple of eight, including
non-powers of two. Partial Evidence retains explicit alignment once validated;
the default alignment becomes known after metadata completes. Cancellation
retains completed declarations with unknown digests and unverified final file
identity. Metadata keys preserve their spelling and use lowercase
ASCII alphanumeric components separated by dots, underscores or hyphens;
non-powers of two. Metadata keys use ASCII lower-snake-case hierarchy components;
standard numeric components such as `general.base_model.0.name` are preserved.
Hyphenated components are retained for compatibility with the published
llama.cpp v0.7.0 writer, which emits keys such as
`gpt-oss.context_length` and `wavtokenizer-dec.context_length`; the current
[GGUF specification](https://github.com/ggml-org/ggml/blob/master/docs/gguf.md#standardized-key-value-pairs)
restricts `general.architecture` values to letters and digits.
This documents producer output that conflicts with that written restriction,
not a claim that the specification permits hyphens. See the pinned
[WAVTOKENIZER_DEC](https://github.com/ggml-org/llama.cpp/blob/620d2225cc436ab0b001e3200e590e3b286ef9ab/gguf-py/gguf/constants.py#L1447)
and [GPT_OSS](https://github.com/ggml-org/llama.cpp/blob/620d2225cc436ab0b001e3200e590e3b286ef9ab/gguf-py/gguf/constants.py#L1466)
architecture names, [context-length key template](https://github.com/ggml-org/llama.cpp/blob/620d2225cc436ab0b001e3200e590e3b286ef9ab/gguf-py/gguf/constants.py#L110),
and [writer method](https://github.com/ggml-org/llama.cpp/blob/620d2225cc436ab0b001e3200e590e3b286ef9ab/gguf-py/gguf/gguf_writer.py#L698-L699).
Scalar metadata is complete. Arrays have declared element types,
counts and at most eight preview elements per array, with an explicit
`preview_complete` flag. This includes tokenizer vocabularies: omitted values
@@ -81,101 +50,11 @@ Nonfinite floating metadata uses `NaN`, `+Infinity` or `-Infinity` with its
declared float type; negative zero is `"-0"`. Unknown tensor types retain their numeric IDs and null byte
lengths; affected totals and storage validation remain unknown.
Q8_1 (type ID 9) has two released layouts: 40 bytes per 32 elements in
[llama.cpp b1500](https://github.com/ggml-org/llama.cpp/blob/57ad015dc3011b046ed5a23186c86ea55f987c54/ggml-quants.h#L49-L55)
and 36 bytes in the pinned
[modern ggml layout](https://github.com/ggml-org/ggml/blob/da8e2a54b919557b816b1c2beb432ccb766f7ce0/src/ggml-common.h#L242-L253). Both use GGUF v3 and
quantization version 2, so those declarations do not select a storage layout.
REA retains the known type, shape, parameter count and starting offsets, with
`byte_length: null`, `storage_validation: "unknown"`, partial storage/range
coverage and an explicit limitation for nonempty Q8_1 tensors. Per-type and
overall byte totals remain unknown where affected. Padding, the next tensor's
offset and file size do not establish a layout; retained queries preserve the
same uncertainty. Empty Q8_1 tensors still have a derived zero-byte length,
and the known 32-element row-block constraint remains enforced.
K-quantization IDs 10–15 also have incompatible released layouts. The
[b1500 build option](https://github.com/ggml-org/llama.cpp/blob/57ad015dc3011b046ed5a23186c86ea55f987c54/CMakeLists.txt#L88)
`LLAMA_QKK_64` selects `GGML_QKK_64`; the same release's default and
[b3001](https://github.com/ggml-org/llama.cpp/blob/c429b33beb35f13934a4dfbe0c138d30b45e5d54/ggml-common.h)
use 256-element blocks. The
[b1500 structures](https://github.com/ggml-org/llama.cpp/blob/57ad015dc3011b046ed5a23186c86ea55f987c54/ggml-quants.h#L61-L169)
give these source-derived sizes:
| ID | Stable name | Bytes per 64 elements, K64 | Bytes per 256 elements, K256 | Padded extent for 256 elements, either layout |
| --- | ----------- | -------------------------- | ---------------------------- | --------------------------------------------- |
| 10 | Q2_K | 24 | 84 | 96 |
| 11 | Q3_K | 28 | 110 | 128 |
| 12 | Q4_K | 38 | 144 | 160 |
| 13 | Q5_K | 46 | 176 | 192 |
| 14 | Q6_K | 54 | 210 | 224 |
| 15 | Q8_K | 76 | 292 | 320 |
Both builds write GGUF v3 and quantization version 2. The
[quantizer](https://github.com/ggml-org/llama.cpp/blob/57ad015dc3011b046ed5a23186c86ea55f987c54/llama.cpp#L7430-L7492)
writes `general.file_type` directly from the selected `llama_ftype`, without
encoding the K64 build option. `GGML_QNT_VERSION_FACTOR` is defined in the
header but is not used by this GGUF serialization path. Global file type also
does not identify individual tensor types: the quantizer
[retains unselected tensors](https://github.com/ggml-org/llama.cpp/blob/57ad015dc3011b046ed5a23186c86ea55f987c54/llama.cpp#L7561-L7586).
The [writer pads each payload](https://github.com/ggml-org/llama.cpp/blob/57ad015dc3011b046ed5a23186c86ea55f987c54/ggml.c#L19549-L19565)
to the default 32-byte alignment; the final column shows why even the same file
extents need not distinguish these layouts.
REA preserves each K type's name and enforces the common 64-element row
multiple, which does not establish a storage block size. Nonempty K tensor
lengths and affected totals are now unknown, including common modern files.
Empty K tensors still have derived zero-byte storage.
Integer IDs also shifted each time IQ types were added. These released
`ggml.h` snapshots all declare GGUF v3 and quantization version 2:
| Released snapshot | I8 ID | I16 ID | I32 ID |
| --------------------------------------------------------------------------------------------------- | ----- | ------ | ------ |
| [b1500](https://github.com/ggml-org/llama.cpp/blob/57ad015dc3011b046ed5a23186c86ea55f987c54/ggml.h) | 16 | 17 | 18 |
| [b1803](https://github.com/ggml-org/llama.cpp/blob/36e5a08b203542dca53cca4eaf172c5dc4bbc991/ggml.h) | 17 | 18 | 19 |
| [b1871](https://github.com/ggml-org/llama.cpp/blob/bb0c1392479398f9aba86d9ec98db0b95ede6e6d/ggml.h) | 18 | 19 | 20 |
| [b2100](https://github.com/ggml-org/llama.cpp/blob/4aa43fab569215a13495a7f1a0f8afc541b16d03/ggml.h) | 19 | 20 | 21 |
| [b2172](https://github.com/ggml-org/llama.cpp/blob/bd2d4e393b2b7d2a1b2e201058e26017c9728ead/ggml.h) | 20 | 21 | 22 |
| [b2221](https://github.com/ggml-org/llama.cpp/blob/a14679cc30c785e75d38028bae6ec39c6209ddef/ggml.h) | 21 | 22 | 23 |
| [b2253](https://github.com/ggml-org/llama.cpp/blob/4c4cb30736582cacb1a164a9d4bc8e17b1014be7/ggml.h) | 22 | 23 | 24 |
| [b2275](https://github.com/ggml-org/llama.cpp/blob/a33e6a0d2a66104ea9a906bdbf8a94d050189d91/ggml.h) | 23 | 24 | 25 |
| [b2300](https://github.com/ggml-org/llama.cpp/blob/6ea0f010ff6967034528d9e0b8330b9b0f0b7c13/ggml.h) | 24 | 25 | 26 |
[Upstream PR 6050](https://github.com/ggml-org/llama.cpp/pull/6050) later pinned
integer values explicitly. Modern IDs 16–23 mean IQ2_XXS, IQ2_XS, IQ3_XXS,
IQ1_S, IQ4_NL, IQ3_S, IQ2_S and IQ4_XS, respectively. Thus every ID 16–25 has
multiple released meanings. REA keeps its numeric `type_id`, uses
`AMBIGUOUS_<id>` for `type`, and records the released candidate names in a
limitation. Nonempty byte lengths are unknown. Empty declarations still derive
zero-byte storage, while their textual identity remains ambiguous. Type filters
match the reported `AMBIGUOUS_<id>` label. ID 26 remains I32 across these
snapshots; later stable entries retain their existing interpretation. This audit
does not establish compatibility with every producer fork or future revision.
The inventory does not select a historical layout from model names, global
file types, padding or guessed producer provenance. A source revision together
with its build configuration can establish an external layout fact; these
headers do not carry that identification, and REA currently has no layout
selection input. Authored regression fixtures use the source-derived sizes
above; historical executables were not run.
Safetensors reports the header extent, exact string `__metadata__` values, dtype,
shape and declared data ranges. A null `__metadata__` is treated as omitted;
unknown fields on tensor entries are ignored within the reader's 32-level JSON
nesting limit, while the required dtype, shape and data offsets are still
validated. JSON duplicate keys, invalid UTF-8, out-of-file
shape and declared data ranges. JSON duplicate keys, invalid UTF-8, out-of-file
ranges, overlaps, gaps, trailing unindexed data and known dtype/shape byte-length
contradictions are rejected with a typed `file_format_rejected` constraint (public code `target_unavailable`, category `unsupported_target`).
A missing, non-regular or otherwise unusable selected path is also `target_unavailable`;
host I/O or resource failures on a valid selection, such as `EMFILE` or `EIO`, are
`execution_failure` with the path and reason in `details.diagnostics`. Empty
tensors and scalar shapes are supported. The reader retains shape dimensions as
exact unsigned 64-bit values, including values above JavaScript's safe-integer
range when a zero extent makes storage empty. Shape products are checked in
declaration order, as in the
[64-bit upstream reader](https://github.com/huggingface/safetensors/blob/e246a2560645b7525f5775669ed816eb57c5bcc8/safetensors/src/tensor.rs#L604-L609);
a later zero does not erase an earlier product overflow. Actual file and data
offsets must still fit exact host offset precision. Packed sub-byte types require whole
contradictions are rejected with a typed `file_format_rejected` constraint (public code `target_unavailable`, category `unsupported_target`). Empty
tensors and scalar shapes are supported; packed sub-byte types require whole
byte storage. Unknown dtypes retain declared ranges and an explicit limitation
that shape-to-storage validation is unavailable. Each tensor records
`storage_validation: "consistent" | "unknown"`; `coverage.storage_sizes` remains
@@ -192,9 +71,7 @@ GGUF supports zero extents and scalar shapes, with at most four dimensions;
known quantized types still require complete blocks in the first dimension.
MCP defaults to a compact summary with counts, selected short headline metadata,
chat-template presence (default or named template content) and derived totals.
A names-only `tokenizer.chat_templates` inventory does not establish template
content. The full type histogram and all rows
chat-template presence and derived totals. The full type histogram and all rows
remain in complete Evidence. Headline metadata uses short keys/types and scalar
values; long or compound values remain available through explicit metadata
queries. Use the returned `parent_evidence_id`:
+11 -39
View File
@@ -19,25 +19,15 @@ session references as `left` and `right`:
}
```
Unique exact names with observed symbol origin are paired first. Inventory rows
or optional `list_names` Evidence identify dynamic/default names to exclude.
Unknown or conflicting origin does not establish name correspondence; complete
independent body bytes remain eligible. Exact names establish name correspondence,
not unchanged code or original-source identity. Ghidra publishes its symbol facts;
Hopper, IDA and older imported dossiers retain unknown origin when no such facts
were observed. Display spelling alone, including `EntryPoint` or `FUN_` prefixes,
does not establish whether a name was generated.
Authentic Evidence selected for the wrong side, role, target, provider, profile
or document returns an input error naming the selected field or observation
index. Re-select compatible observations; authentication or contradictory producer
facts retain their separate integrity failures.
Unique exact reported names are paired first. Optional `list_names` Evidence
identifies dynamic/default names to exclude. If origin is unavailable, the
existing automatic-name exclusion rule applies and each function retains that
limitation. Exact names establish name correspondence, not unchanged code or
original-source identity.
For unmatched candidates, optionally supply `left_observations` and
`right_observations`. `procedure_info` or existing `analyze_function` dossiers
give exact inclusive body ranges; `read_bytes` supplies memory bytes. An MCP
`analyze_function` summary reply is not a dossier: pass its
`parent_evidence_id` as a `retained-evidence` reference instead. Observations
`right_observations`. `procedure_info` or existing `analyze_function` Evidence
gives exact inclusive body ranges; `read_bytes` supplies memory bytes. Observations
must belong to the side's exact target/provider/profile and document context.
Adjacent, consistent overlapping and partially returned reads compose by their
observed extent; conflicting intervals remain gaps. A byte match requires every
@@ -88,9 +78,9 @@ inventory Evidence and the owning inventory’s `source_index`, preserving same-
One validation, lazy member index and bounded recent filter are retained per
immutable parent; no live target state is cached.
When both functions already have unambiguous `analyze_function` dossiers in the
observations, a matched relation includes `compare_functions_input` with
directly usable retained references. Otherwise analyze just the selected canonical addresses in their
When both functions already have unambiguous `analyze_function` dossiers, a matched
relation includes `compare_functions_input` with directly usable retained
references. Otherwise analyze just the selected canonical addresses in their
respective targets, then compare those dossiers. Matching never claims behavioral
equivalence. Optional informative CFG/call/string fingerprints from supplied
dossiers connect unresolved candidate groups only. They require observed CFG,
@@ -117,25 +107,9 @@ in the JSON for all groups inline. Import the bundle into MCP for views, or
extract its matching Evidence record to a JSON file for the existing CLI
`inspect-analysis-view` `evidence-file` source. Input bundles are unchanged.
Representation admission is bounded before cloning selected observations
Representation admission is bounded at 96 MiB before cloning selected observations
and expanding matching indexes and repeated per-function source links. Work yields to cancellation. No worker/provider
concurrency is introduced; this budget is not a bound on total process RSS.
The allowance defaults to 96 MiB and is the runtime setting
`native.matching_representation_bytes`, from 1 byte to 1 GiB. On MCP, call
`update_analysis_settings` with that setting and retry; it applies to the next
operation without restarting REA. For CLI or future REA processes, set
`REA_NATIVE_MATCHING_REPRESENTATION_BYTES` in the launch environment; `rea
setup` copies it into client registrations. An invalid launch value is refused
by matching only, and a runtime value repairs it. A refusal carries
`details.limit` with the effective value, its source and bounds. Raising the
allowance lets matching retain more in the REA process. The 1 GiB maximum is
an REA policy, not a host-capacity claim, and peak heap during a request can
exceed the allowance.
Summary delivery happens after complete matching Evidence is constructed and
retained. It reduces the initial reply, but does not avoid that construction or
raise the allowance. Repeated provenance remains
part of the complete member contract; splitting inventories into separate matches
would change global name/body uniqueness and is not an equivalent workaround.
The self-owned source fixture is [nativeFunctionMatching.c](https://github.com/morluto/rea/blob/main/tests/fixtures/nativeFunctionMatching.c).
On Linux x86-64, compile with `cc -O2 -g0 -fno-pie -no-pie -DVERSION=1` and
@@ -144,5 +118,3 @@ It includes unchanged/changed code, a byte-identical rename, a removed/added
observation, an inlined function, identical-code aliases and a renamed changed
function with an observed call/string signature. Analyze without executing the
fixture. Full-provider verification still needs caller-supplied Ghidra and Java.
Malformed inline Evidence is rejected after admission with its selected input field and observation index. A structurally valid record whose authenticated contents or digest disagree remains an integrity failure; changing input diagnostics does not make that record trustworthy.
+25 -484
View File
@@ -6,15 +6,6 @@ Use a native provider to discover functions, inspect instructions and pseudocode
follow references, and examine database types. Static resources and runtime
observation complement that analysis when they answer a specific question.
`inspect_macho` reports build platforms from the selected slice. Legacy
`LC_VERSION_MIN_IPHONEOS`, `LC_VERSION_MIN_TVOS` and
`LC_VERSION_MIN_WATCHOS` commands are interpreted with the Mach-O CPU type, so
Intel slices report simulator platforms while device CPU slices keep the
device platform. A chained-import row ending in `+0x...` is ambiguous in
dyld's text output: it may be an addend or part of a literal symbol name. REA
keeps the full expression in `raw_name`, sets `name` and `addend` to null, and
marks `name_status: "ambiguous"`; it does not synthesize a symbol identity.
## Start with a function
Choose an installed provider from [setup](installation.md#hopper). For Ghidra:
@@ -96,14 +87,6 @@ After metadata edits, snapshot saves and imports remain unavailable in that
session. Use `export_evidence_bundle` to retain the observations, close without
`snapshot_path`, and reopen the target before importing or saving a snapshot.
Hopper executable opens use an owned private image verified against the digest
selected by `open_binary`. The ordinary ELF/PE path and configured loader overrides
copy the whole source; default Mach-O opens retain their selected-image checks.
A changed source fails before the launcher reads it. The bridge binds its Hopper document to that private
loaded path, while Evidence retains the selected original artifact identity. Hopper
database opens retain their separate database semantics. Real Hopper conformance for ordinary
copies and loader overrides remains unverified by the source regression tests.
Ghidra checks its private import copy against the artifact digest selected by
`open_binary`. If the source changes before that copy is acquired, the query
reports `artifact_changed` with the selected path and both digests. Reopen the
@@ -183,54 +166,6 @@ remains valid. This tool
has no dedicated CLI command; the existing `decompile` command remains the
single-procedure CLI operation.
Each batch schedules one provider read at a time, but has no shared input-count
or aggregate retained-output allowance. It retains every requested row and full
pseudocode body through Evidence construction. The MCP response budget checks
complete delivery afterward; it does not bound provider work or retained batch
state. Select small explicit batches when using this workflow. A general batch
admission/output policy, including completed per-item facts when capacity is
exhausted, remains unresolved.
`decompile_all` (`rea decompile-all <target> --output <directory>`) writes one
pseudocode file per analyzed function and returns only a manifest: counts, the
output directory, capped failure reasons, provider identity, and the
analysis-profile digest. The manifest and `decompile-index.json` do not contain
pseudocode. Each file records the provider name, canonical address, inclusive
body ranges when `procedure_info` supplies them, and the `procedure_info` and
pseudocode limitations for that function. Header values are abbreviated to
2,000 characters per value and the joined limitation line to 4,000 characters;
the index retains the complete selected names, addresses, provider facts and
caveats. Failure details retain at most 32 rows within 2,048 JSON-encoded
UTF-8 bytes; names may be omitted to keep complete addresses and reasons. The
index retains every failure and its complete observed name.
Each function's `decompile-index.json` record, whatever its status, keeps the
complete limitations of the observations it used. The returned Evidence keeps
at most eight first-observed distinct per-function provider limitations within an 8,000-byte JSON-encoded UTF-8 text budget and counts omitted observations. Repeated omitted text is counted per observation, not as a distinct caveat. This summary does not retain every omitted string in memory. A fresh output directory must not already exist. `--resume`
continues an export for the same target SHA-256, provider, analysis profile,
and layout; it requires the directory to exist, and an export without a known
target or profile digest cannot be resumed. Resume skips files whose bytes still match the last write the journal
recorded, even after a later read failure, tries
functions it has not attempted before retrying recorded failures, ignores a
journal line torn by an interrupted write, and never overwrites a conflicting
file. File reservations compare ASCII paths without case, so distinct address
spaces remain distinct on case-insensitive filesystems. A resumed journal with
conflicting reservations is refused. One function's decompiler error or deadline is counted and does not stop
the others. A function that `procedure_info` classifies as external, such as an
import, is counted as `external` and recorded with that status and the body
fact as observed. It gets no file, no pseudocode is requested, and a resume
skips it. A function whose classification is unknown is decompiled as usual;
a provider that returns null pseudocode for it is reported as `no_pseudocode`. Inventory rows without an address are counted in the limitations. `--layout dwarf`
reports that compilation-unit paths are unavailable and still names files by
address. Filters are a case-sensitive name substring, a case-sensitive name
prefix, an inclusive address range (including uppercase hexadecimal input), and a positive safe-integer attempt limit. The limit does not allocate space in proportion to its value. Cancellation leaves
files already written in place. One run at a time holds an output directory,
identified by its device and inode, through export finalization and any retained cleanup retry; another
run on the same directory is refused with the holder's process ID, and a
crashed run can be recovered by the next run. Failed descriptor or staging cleanup remains owned by the session; close the target to retry it. Initialization cleanup preserves unexpected entries rather than recursively removing the directory. Resume hashes existing files in bounded chunks, and index generation reuses committed journal facts already held by the export owner. After five consecutive functions whose files
could not be written or read, for example on a full disk, the export stops with
an error instead of decompiling the rest. Files and records already committed
stay, `decompile-index.json` is still written, and a later resume continues.
Type inspection selects one exact database pathname or typed data address.
Struct/union fields, enums, pointers, arrays, typedefs, size, alignment, packing
and bitfields retain database authority. Child types use exact IDs for further
@@ -263,72 +198,12 @@ observed source encoding. Hopper can split long literals into adjacent
unterminated objects; search matches each object's decoded bytes independently.
Undecodable objects retain native display text with `decoding.available: false`
and a reason, so one uncertain object does not block unrelated inspections.
Function dossiers and `trace_feature` string matches retain this same string
evidence, including decoding uncertainty; procedure matches keep their own
named observation fields without invented string qualifications. Tracing keeps
same-address search alternatives, including different string encodings, in their
original search order and shares one reference read per target address within
the call. Reference rows are emitted once per target address, preserving any
duplicates in the provider's observation. Native call edges retain
Function dossiers retain this same string evidence. Native call edges retain
Hopper's partial `CallReference` classification and exact endpoints; detailed
reference flags remain unavailable rather than being invented. Caller and callee
procedure inventories come from independent Hopper APIs; REA retains observed
relationships but reports their completeness as unknown. In particular, a resolved
tail call observed in one direction does not prove that the reverse inventory is
complete. An empty Hopper inventory cannot establish that no such call exists.
No whole-binary reverse scan is performed.
reference flags remain unavailable rather than being invented.
Regex searches use ECMAScript Unicode syntax in a cancellable worker with a
matching deadline and a worker old-generation heap. Deadline or cancellation
stops matching while leaving the Hopper API available. The deadline is
`REA_REGEX_MATCHING_DEADLINE_MS`, a positive decimal integer from 1 to
2147483647 ms. Unset uses 5000 ms. A larger value is refused because Node
schedules a delay past that signed 32-bit bound as 1 ms. Invalid values are
configuration errors and do not fall back to the default. The setting is read
from the environment that starts REA; changing that environment needs a restart.
In MCP, `regex.matching_deadline_ms` overrides the deadline for newly accepted
searches without restarting REA. A deadline failure names the effective
milliseconds, known source, default, and maximum. The worker old-generation heap
does not raise it. IDA and Kuna use these same regex controls; Ghidra's Java
matcher does not. The worker
heap request is `REA_REGEX_WORKER_HEAP_REQUEST_MIB`, a decimal integer from
7 to 16384 MiB. Unset uses 128 MiB. A request below 7 MiB aborts the worker
during startup, and a larger value is refused. Invalid values are
configuration errors and do not fall back to 128. The setting is read from
the environment that starts REA, while MCP `regex.worker_heap_request_mib`
can override it for workers belonging to newly accepted searches. Use
`update_analysis_settings` for either runtime control; accepted work keeps its
budget. See the [settings design](design/limits-and-recovery.md).
A heap failure names the effective request, known source, default, floor, and
maximum. A positive Node.js
`--max-old-space-size` on the process that starts
REA, including `NODE_OPTIONS`, overrides that request and sets the effective
heap. `--max-old-space-size-percentage` takes precedence over that flag in
either order. REA does not turn the percentage into a MiB, so the effective
heap stays unestablished instead of the worker heap request or the absolute flag.
The reported `NODE_OPTIONS` value is the process initial environment when that
block can be read. A later edit to the environment is not the value Node
applied, and an unreadable initial value leaves the effective heap
unestablished. The launch Node options are the initial command line on Linux
when that buffer still has a program entry, not a later edit to
`process.execArgv`. A preload can clear that array after V8 has applied the
flags. Setting `process.title` or `--title` overwrites the command line, so a
buffer without a program entry leaves the effective heap unestablished.
`--env-file` or `--env-file-if-exists` can supply `NODE_OPTIONS`
when the initial environment has none. That file is not part of the initial
environment block, so the effective heap stays unestablished instead of the
worker heap request. An initial `NODE_OPTIONS` is still used, because Node does
not let the file replace it. A positive `--max-heap-size`, including the
underscore form and launch `NODE_OPTIONS` on Node versions that allow it,
sets the total heap rather than the old generation and can be lower than the
worker heap request. The effective old generation stays unestablished while that
value is in effect. A zero or empty value clears it.
`--experimental-config-file` or `--experimental-default-config-file` can set
`nodeOptions`, including a heap percentage, from a file that is not read
back. The effective heap stays unestablished when either flag is present,
including when an absolute `--max-old-space-size` is also on the command
line or in the recovered `NODE_OPTIONS`. Node accepts the config path as
`=` or the next argument.
Literal mode retains Unicode casefold matching.
five-second matching deadline. Deadline or cancellation stops matching while
leaving the Hopper API available. Literal mode retains Unicode casefold matching.
## Bounded static traces
@@ -383,12 +258,8 @@ nodes and edges. Node payloads are bounded to 8 MiB and serialized results to
stable IDs for endpoints outside that page.
Within one value trace, each exact procedure selector is attempted at most once,
including failed analyses. A failed seed returns the provider's original typed
failure and corrective guidance; it cannot establish a trace or target identity.
Failures while expanding a validated seed remain unknown observations and do not
establish canonical function identity. A successful seed without a native def-use
model still returns its procedure and an explicit coverage unknown. A new trace
can retry failed selectors.
including failed analyses. Failures remain unknown observations and do not
establish canonical function identity. A new trace can retry those selectors.
The raw Java `is_dead` flag can remain set after Ghidra decodes a live block;
`block_membership` reports actual syntax-tree membership separately.
@@ -420,41 +291,6 @@ Mach-O fixtures check each case against source-owned return values.
## Static inspection
Thin Mach-O admission and each admitted FAT slice walk declared load-command headers, checking
width-specific alignment, command sizes and exact `ncmds`/`sizeofcmds` agreement
before opening a provider. Headers beyond the initial probe use another
4096-byte window; admission skips command payloads. Native image readers use
the same framing owner and perform their own payload checks. This validation
does not add a global command-table byte cap.
Native target admission checks Mach-O slice headers before assigning FAT
identity. A shared `CAFEBABE` prefix whose declarations do not identify Mach-O
images receives the generic unsupported-format refusal, including JVM class
files larger than the bounded admission probe. This does not identify a partial
prefix as a validated JVM class. For files sharing `CAFEBABE`, target admission
checks complete JVM framing within the same 16 MiB class identity bound as the
Mach-O image reader. A complete class remains unsupported even if its constant
pool resembles a known FAT CPU declaration. Above that bound, an ambiguous
malformed FAT interpretation remains unresolved. Malformed recognized FAT
declarations retain their format-specific reasons.
`inspect_artifact` and `extract_artifact` retain universal Mach-O slices under
their exact `lipo` names, including 32-bit ARM implementations such as `armv7`,
`armv7s`, and `armv7k`. These names are reconciled with the FAT CPU subtype;
the structural reader's `arm` family label alone does not identify a slice.
Available CPU type, subtype/capability bits, byte range, and alignment remain
checked against the source bytes. Inventory records source ranges and hashes, and
extraction copies those ranges without running the target. This does not
expand any analysis provider's supported architectures.
Malformed successful `lipo` output reports a format failure with its parse
diagnostic.
Apple lipo's hidden ARM64 compatibility row is retained separately from the
raw `nfat_arch` count and admitted only from its extra FAT table slot when its
in-range Mach-O header matches the row. `list_architectures` preserves the
per-row `hidden` marker from lipo output. Mach-O image and Electron fuse
inspection include the row in architecture selection while retaining that
marker in their reported slices.
`inspect_plist.format` describes the selected source, not plutil's conversion
format. Sources explicitly identified as XML, binary or JSON retain those
labels. For generic text, a bounded source-prefix probe recognizes leading XML
@@ -462,79 +298,25 @@ markup, including UTF-8 and BOM-marked UTF-16 sources. Unidentified text and
OpenStep plists, including a data-only value such as `<deadbeef>`, report
`unknown` even when plutil decodes their values through an XML conversion.
Plist inspection reads bounded original bytes through an owned file descriptor.
Binary and UTF-8/BOM-marked UTF-16 XML sources decode directly, retaining scalar
facts without a mandatory `plutil` conversion. UTF-8 JSON containers also decode
directly when their number facts need no further classification; explicitly
identified JSON sources can have scalar roots. Unidentified primitive text keeps
its existing conversion route, so OpenStep `123` remains a string. OpenStep,
unsupported XML encodings such as UTF-32 and declared legacy encodings, and JSON
numbers requiring XML type facts retain the `plutil` route. Invalid UTF-8 and
encoding-declaration mismatches remain explicit failures. Direct-source
provenance records `file` without claiming conversion; conversion limitations
and invocations appear only when conversion actually ran.
XML sources must also pass native format validation, with native refusals retaining
their diagnostics and successful validation recorded in provenance. Original
unknown scalar text, reasons and limitations survive successful validation;
output-budget omissions remain omissions.
Source input is limited to 64 MiB. Binary and direct XML decoding reserve an
estimated 64 MiB decode budget before parsing; this is not a hard process-memory
bound. JSON decode memory has not been measured and has no equivalent decode
estimate. A refused input reports non-retryable `resource_constraint` with
`reported_limits.maximum_input_bytes` or `maximum_decoded_bytes` as applicable.
Ordinary direct-source decoding has a ceiling of 998 source nesting levels,
derived from the existing 1000-level JSON result capacity by reserving one level
for typed scalar fields and one for the result's `value` field. Deeper input reports
`resource_constraint` with `reported_limits.maximum_nesting_depth: 998` and names
the fixed nesting allowance. XML admission also requires native validation, which
can refuse input below this decoding ceiling. Compatibility conversions can
likewise encounter an earlier native utility refusal; both retain native
diagnostics. Archive and Interface Builder decoding retain their separate
128-level allowance. Worker limits and host memory do not raise these fixed
allowances, and the unchanged request fails the same way.
A plist explicitly selected through the path input or as the active plist target
reports an unreadable or malformed selection as `invalid_request`. A missing or
unreadable default plist of another target reports `capability_unavailable`.
Permission failures remain `access_denied`; output and cleanup failures retain
their own error categories.
Selected plist paths preserve filesystem resolution, including `..` after a
directory symlink and trailing directory separators. Resource-only bundle
sessions resolve relative selections from the bundle root with the same path
semantics as executable sessions; the reported source path retains those
selected segments.
A conversion refusal includes plutil's diagnostic from either output stream.
A plist decode refusal includes plutil's diagnostic from either output stream.
`details.captured_output` retains bounded stdout/stderr excerpts, observed byte
totals and exit status. `truncated: true` means those excerpts are incomplete.
A dSYM companion that `dyld_info` refuses for an unbacked text section reports
`unsupported_target`, retaining the exact diagnostic and directing inspection
to the linked Mach-O. This classification requires both the recognized native
refusal and an independently parsed dSYM Mach header; other tool failures keep
their failure outcome.
Observed `dyld_info` diagnostics include `unknown mh.filetype 4` and
`unknown filetype 4` for `MH_CORE`, and the corresponding `3` spellings for
`MH_FVMLIB`. These refusals report `unsupported_target` when the independently
parsed Mach header confirms the reported file type. The error retains the exact
native diagnostic and recommends a linked Mach-O executable or relocatable
object. This classifies the selected target's refusal; it does not establish
support for core-dump analysis, historical FVMLIB layouts, or unobserved native
diagnostic spellings. Unknown or contradictory header values and signalled
commands remain failures.
`inspect_macho.selected_architecture` identifies the exact selected lipo slice
and its `container_file_offset` in the input file. A thin Mach-O has a known
offset of zero; an unknown universal-slice offset stays null. The Evidence
subject format describes the input container: `mach-o-universal` for FAT files,
including those with one slice. The normalized result also reports the container
in `format`; `selected_architecture` identifies the selected Mach-O image.
Architecture `file_offset` values are container-relative. Entrypoint, segment
and section `file_offset` values retain their source meaning and are relative
to the selected Mach-O slice. Raw load-command fields retain their original values.
including those with one slice. The normalized result describes the selected
Mach-O image. Architecture
`file_offset` values are container-relative. Entrypoint, segment and section
`file_offset` values retain their source meaning and are relative to the
selected Mach-O slice. Raw load-command fields retain their original values.
A field otool prints more than once, such as each `LC_BUILD_VERSION` tool, is
keyed `name #n` in print order. Each `LC_LINKER_OPTION` string is keyed
`string #n` and checked against the declared `count`. Evidence locations map the LC_MAIN entrypoint and nonempty file-backed segment
@@ -571,36 +353,10 @@ complete observation.
its outcome. Paths outside the analyzed root, including shared-cache system
libraries, stay undetermined. See
[Apple application analysis](apple-application-analysis.md#dylib-load-resolution).
ARM64 slice labels distinguish `arm64v8`, `arm64e`, and `arm64e.x1` while
retaining raw CPU subtype and capability bits. The `arm64` and `arm64e`
request filters select their respective families; a shared family label
does not establish dependency compatibility across different subtypes.
Executable-root discovery preserves traversal uncertainty, including
undecodable filenames and skipped symlinks, as partial coverage. Known
executable roots remain available; unexamined names and symlink targets
cannot establish an empty root set or the absence of other roots.
- `inspect_asset_catalog` / `rea inspect-asset-catalog <app>` reads compiled
`Assets.car` metadata through macOS `assetutil --info`. Catalog digests, raw
rendition fields, pagination and exact UI resource-name matches are returned.
`inventory_coverage` describes traversal of source entries and remains
`unknown` when the reader cannot represent a filename. Represented symlinks
are inventoried but their targets remain uninspected, with a limitation.
`catalog_coverage` and `resource_key_coverage` separately describe examined
catalog metadata and the UI join; an unmatched key cannot establish absence
when either source is incomplete. Resource wrappers retain their serialized
class and authored runtime class, including AppKit `NSCustomResource` names.
Invalid producer JSON and subprocess failures retain captured output with
their typed failure reason and selected catalog path. A complete normal
assetutil exit declaring that exact catalog path `is not a valid AssetCatalog`,
or its observed `Undefined error: 0` / `invalid file` rejection, reports an
invalid-artifact failure. Other tool failures retain their own
classification; unrecognized diagnostics do not establish malformed bytes.
Catalog-reader cleanup retains the primary
integrity, utility or cancellation failure and its cleanup resources; the
provider retains catalog, Interface Builder and keyed-archive readers until
cleanup is confirmed. A catalog failure also retains cleanup failures from
its parallel Interface Builder join. Image extraction and undocumented rendition
interpretation are unsupported.
Image extraction and undocumented rendition interpretation are unsupported.
- `inspect_keyed_archive` / `rea inspect-keyed-archive <plist>` reads XML or
binary Foundation keyed archives without instantiating classes. For an active
app, provide its relative archive path as the second CLI argument or MCP
@@ -608,12 +364,7 @@ complete observation.
shared/cyclic references, class descriptors, raw serialized fields and
malformed/unresolved/nil references. Missing fields stay absent; unknown
classes are preserved. Select `root`, `offset` and `limit` explicitly.
Binary references retain their serialized type-8 UID identity; ordinary
dictionaries with `UID` or `CF$UID` keys remain authored data. XML uses
Foundation's single numeric `CF$UID` dictionary convention for references;
a numeric `UID` dictionary remains authored data. Reference identity follows
the parsed format, rather than the displayed key alone. Object `kind`
describes the decoded archive value: data, dates and numbers
Object `kind` describes the decoded archive value: data, dates and numbers
remain `scalar` when their JSON value uses a `$plist_type` wrapper. An actual
dictionary with the same fields remains `dictionary`.
XML must be well-formed. Data literals use
@@ -622,19 +373,6 @@ complete observation.
emits no bytes. Character references retain their source spelling for data
decoding. Non-ASCII content, child markup and self-closing `<data/>` are format
failures; explicit `<data></data>` represents empty data.
Each XML dictionary key must have a following value element; a final key
without one is malformed. An explicit empty string remains a value.
Binary UID payloads may use nonminimal byte widths. REA normalizes them only
in a private decoder copy; archive digests still identify the original bytes.
Positive binary-plist offset and object-reference table widths are read from
the source; nonstandard widths are normalized only in that private copy for
the pinned decoder. [Foundation's table checks](https://github.com/swiftlang/swift-corelibs-foundation/blob/swift-6.2.3-RELEASE/Sources/CoreFoundation/CFBinaryPList.c#L759-L789)
accept positive widths subject to extent/reference bounds, and its
[sized-integer reader](https://github.com/swiftlang/swift-corelibs-foundation/blob/swift-6.2.3-RELEASE/Sources/CoreFoundation/CFBinaryPList.c#L707-L730)
handles non-power sizes. Widths over eight bytes follow its unsigned 64-bit
accumulator semantics.
UID values above JavaScript's exact integer range return an unsupported-target
outcome instead of rounded reference identities.
UID 0 cannot distinguish conditional nil from ordinary nil. Limits are
64 MiB of input, 200,000 objects/references and 128 nesting levels.
XML dictionary entries decoded as `__proto__` are omitted with an explicit
@@ -642,12 +380,6 @@ complete observation.
comments do not change that handling. XML key names combine their text and
CDATA segments without trimming or expanding literal CDATA entities. Other
values and the original archive digest are preserved.
Symbol fallback accepts complete Objective-C class/metaclass symbol leaves,
including qualified names. Pointer/string labels that merely contain a class
marker do not establish classes. Provider namespace method labels are not
interpreted as Objective-C declarations; class definition versus import origin
remains unsupported coverage rather than an inferred classification.
- `decode_interface_builder` / `rea decode-interface-builder <app>` reads
compiled storyboard and nib resources into objects, outlets, actions,
connections and controller/class names. A full app graph is often several
@@ -671,11 +403,7 @@ complete observation.
still identify the original serialized bytes. Bundle resolution reports
malformed bytes and unsupported encodings against the original plist path
before looking up the executable. Interface Builder decoding
accepts recognized NSKeyedArchiver or ibtool document structures and native
NIBArchive records; an ordinary plist renamed `.nib` remains undecoded with
partial `archive_decode` coverage. An explicitly empty ibtool object document
remains a recognized empty document. The decoder limits aggregate input
archives to 32 MiB.
limits aggregate input archives to 32 MiB.
Before decoding, it also bounds the serialized structure: expanded binary
plist references, NIB table records, XML elements and text, plus their
projected representations must fit the 256 MiB aggregate decode budget.
@@ -688,76 +416,15 @@ complete observation.
Archives beyond either aggregate budget are omitted with their paths and an
explicitly partial `archive_decode` facet; previously decoded documents and
their identities remain available.
The same facet is partial when source traversal cannot represent a filename
or an archive-shaped symlink was skipped. Unknown filename coverage has no
invented document count; known skipped nib and storyboard links are counted
as omitted. A readable empty bundle can report complete zero-document
coverage. Source I/O and identity failures retain their acquisition error
instead of being labeled invalid archive content. Rejected plist decodes
release their representation reservation for later documents.
Repeated NIBArchive fields retain all serialized values in order. Array
outlet connections resolve each serialized member and retain the collection
object ID and member position on the outlet observation. AppKit object-parent
registries use paired keys and parents; objects paired with `NSRoot` are
top-level, while nested objects keep their recorded parent. Conflicting or
unreadable registry pairs remain incomplete hierarchy evidence.
`NSString` objects with the [recognized `NS.bytes` representation](https://github.com/swiftlang/swift-corelibs-foundation/blob/main/Sources/Foundation/NSString.swift#L201-L215) are decoded
as UTF-8 without substituting malformed bytes. A failed conversion retains
the source string object ID and `nibValues.NS.bytes` base64 data; dependent
authored classes, titles and selectors remain unknown, and object, class,
connection and archive-decode coverage is partial. Other decoded objects and
connections remain available. Genuine U+FFFD and leading U+FEFF characters in
valid UTF-8 retain their identity; opaque data fields are not interpreted as
strings. This does not establish support for other NSString representations.
Embedded `UINib` archive data remains retained and undecoded; affected
object, connection, class and hierarchy coverage is partial. Recognized
`UIStoryboardShowSegueTemplate` source records similarly qualify connection
coverage until their navigation semantics are decoded.
- `inspect_native_dispatch_metadata` /
`rea inspect-native-dispatch-metadata <app-or-binary>` prefers a validated macOS Mach-O byte reader. It decodes
64-bit little-endian Objective-C class/metaclass records, superclass pointers,
absolute/relative method entries, ivar offsets/sizes/alignment and protocol
declarations. It also decodes Swift nominal type declarations, simple Swift
conformances, static synchronous witness slots, signed-relative pointers and
non-generic, non-resilient class vtable descriptors. Records include exact virtual addresses/file offsets and
declarations. It also decodes simple Swift conformances, static synchronous
witness slots, signed-relative pointers and non-generic, non-resilient class
vtable descriptors. Records include exact virtual addresses/file offsets and
artifact evidence. Vtable indexes are metadata word offsets; witness indexes
start after the conformance header. Names unavailable in metadata remain null.
The selected Mach-O header must match the requested CPU family for both thin
files and universal slices before metadata decoding. CPU subtypes and
capability bits remain source facts; an `arm64` request may select an
`arm64e` image under the existing slice preference policy.
The byte reader holds the whole selected file in memory, so it admits files up
to 64 MiB / 67108864 bytes by default. Select a larger budget with
`max_input_bytes` (CLI `--max-input-bytes`), at most 1 GiB / 1073741824
bytes. The whole file counts, including every slice of a universal Mach-O.
Results record `input_admission` with the effective budget, its source
(`default` or `caller`) and the bytes retained. A refusal names the observed
file size, the effective budget and its source, and either the budget to
select or, above 1 GiB, a deep analysis provider.
`swift_decls` lists each `__swift5_types` nominal type descriptor, which
survives stripping: `kind` (`class`, `struct`, `enum`, or `actor` from the
class actor flag), the descriptor's own `name`, `module`, `qualified_name`
(`Outer.Inner`), `generic`, the metadata `accessor_address`, the descriptor
`location`, and evidence citing its `__swift5_types` record.
Private and local types set `anonymous_context` because their anonymous
context is omitted from `qualified_name`. When a parent context cannot be
resolved, such as an extension of a type named only by a mangled name, a
missing module, or a cyclic chain, `qualified_name` is null and the entry is
partial with a reason. Access level, superclass, conformances, and fields are
not reported by this facet. The `swift_declarations` coverage counters count
examined type records and fully decoded declarations. Class vtable recovery
reads the same descriptors, so an unreadable type record also keeps
`swift_class_vtable_descriptors` partial.
Nominal declarations, conformance type and protocol references, and class
vtables share one snapshot-scoped name/context reader. Conformance `type_name`,
`protocol_name`, and dispatch `owner` retain enclosing named types;
conformances carry the known module.
Unresolved parents preserve the leaf name and a partial identity reason,
while independently readable witness implementations remain available.
Protocol instance properties use the same property-list reader as classes.
Unreadable instance properties and unimplemented class-property lists keep
the protocol's methods and record with partial protocol coverage.
The `swift_class_vtable_descriptors` coverage counters count examined vtable
slots and successfully decoded slots, rather than type-section entries.
The `objc_class_method_ivar_metadata` counters count admitted class and
@@ -767,21 +434,6 @@ complete observation.
arm64e formats;
- legacy `LC_DYLD_INFO` rebase and bind opcodes.
[dyld symbol names are byte strings](https://github.com/apple-oss-distributions/dyld/blob/dyld-1330/mach_o/BindOpcodes.cpp#L203-L208),
compared with [`strcmp`](https://github.com/apple-oss-distributions/dyld/blob/dyld-1330/mach_o/BindOpcodes.cpp#L42-L56).
Dependent library install names likewise originate as
[load-command byte strings](https://github.com/apple-oss-distributions/dyld/blob/dyld-1330/mach_o/Header.cpp#L1278-L1298)
and are [compared byte for byte](https://github.com/apple-oss-distributions/dyld/blob/dyld-1330/dyld/DyldAPIs.cpp#L607-L618).
When a symbol or applicable library name has no lossless UTF-8 text
projection, the affected binding remains unresolved and pointer-fixup
coverage is partial; later readable bindings and other metadata survive.
The internal fixup reader retains the exact name bytes and parsed binding
fields, while public Objective-C and Swift results retain the unresolved
reason instead of publishing a guessed symbol, library or class name. A literal
U+FFFD or leading BOM in valid UTF-8 remains part of the name. Legacy
weak-bind lookup has no library-name scope, so an opaque dependency name
does not prevent decoding an otherwise readable weak binding.
A Swift conformance whose type or protocol reference is an external bind
keeps its name as `null` and carries the raw dyld identity in
`type_binding` or `protocol_binding`; that record and its facet coverage are
@@ -792,19 +444,11 @@ complete observation.
an unfixed zero in a nullable ABI field is treated as null. Incomplete or
conflicting fixup streams keep affected pointers unresolved; coverage reports
parsing and resource limits while retaining validated prefix facts.
Conformance records preserve the raw three-bit `type_reference_kind`. Kinds
0 and 1 identify direct and indirect nominal descriptors; kind 2 preserves
the inline Objective-C class name and its source location. Kind 3 preserves
an imported class binding or the local class-object address when available,
but class metadata/name recovery requiring the Objective-C runtime remains
partial. Unknown kinds retain the raw kind and reference location without
being treated as absent or assigned a guessed type name.
So an external superclass resolves from its `_OBJC_CLASS_$_` bind (for
example `NSObject`). The `pointer_fixups` coverage facet names the mechanism.
Class properties preserve each encoded attribute as a raw `{name, value}`
pair, including commas inside C++ template arguments in the `T` type, for
example `{ name: "N", value: "" }`, and report atomicity on the
pair, for example `{ name: "N", value: "" }`, and report atomicity on the
property: `N` means `nonatomic`; a complete typed attribute string without
`N` means the Objective-C default `atomic`;
missing or incomplete attribute metadata leaves `atomicity` and any
@@ -862,19 +506,6 @@ capture or automatic permission prompts. Executable bytes and process launch
time guard against a different target or PID reuse. AX selection requires one
unique geometry match; ambiguity fails closed.
Accessibility node values preserve strings, finite numbers and booleans as their
original JSON scalar types. Values that JSON cannot represent faithfully,
including unsafe integers, negative zero and non-finite numbers, remain null
with a path-specific gap and incomplete capture coverage. Null alone does not
establish that an accessibility value was successfully read or absent.
An initial PID or window mismatch is `invalid_request` on `pid` or
`window_id` and leaves provider availability intact. An unavailable executable
URL or unresolved executable path leaves identity unknown and reports
`capability_unavailable`. Identity or window loss
after the initial capture remains an observation failure with collected facts.
Permission and other helper failures retain their specific remediation text.
`capture_native_ui_scenario` takes AX child-index paths for press, increment/
decrement scrolling and text-value entry, or bounded waits. No global event
injection is used. Unsupported AX actions fail explicitly. The result preserves
@@ -887,10 +518,8 @@ cleanup. A later abort does not replace an earlier helper or output failure.
Cleanup failures retain their runtime owner until a confirmed cleanup retry.
Scenarios accept caller-selected action lists and accessibility node counts.
One wait step stays capped at 180000 ms. That cap has no runtime override.
The operation deadline is `native_ui_deadline_ms` (omitted means 180000 ms)
and does not raise the wait-step cap. The complete scenario result has a
64 MiB output budget. Screenshots are scaled to
Individual waits cannot exceed the operation's 180-second deadline, and the
complete scenario result has a 64 MiB output budget. Screenshots are scaled to
at most 2,048 pixels and captured only for the selected window. REA compiles one
owned helper per scenario, removes its temporary compiler cache and stops its
helper on cancellation. It does not launch or own the selected application. UI
@@ -906,26 +535,10 @@ caller-selected entries, records them and continues:
- Objective-C methods by selector, optionally limited to one class and to
instance or class methods.
LLDB discovery uses a separate 60000 ms `xcrun` deadline with no runtime
override. Discovery timeouts report `provider_timeout` and retain timeout
recovery, captured diagnostics and cleanup state. Output-budget and I/O failures
retain their own error categories; only actual discovery unavailability reports
missing LLDB. `duration_ms` controls the later target observation window.
```sh
rea observe-native-calls /Applications/Example.app '{"breakpoints":[{"kind":"objc-method","class_name":"NSURLSession","selector":"dataTaskWithRequest:completionHandler:"},{"kind":"function","name":"open","module":"libsystem_kernel.dylib"}],"duration_ms":5000,"backtrace_frames":4}' --json
```
Function names select LLDB runtime symbols. On Darwin, a C function reported as
`_main` by `inspect_macho` or `nm` can resolve as `main` in LLDB. REA preserves the
exact selected name; it does not remove underscores or translate static names.
Use `{"kind":"function","name":"main","module":"Example"}` to limit that
runtime symbol to the executable image named `Example`, then inspect the reported
locations and image paths. Omitting `module` deliberately matches every loaded
image, including system libraries. A match outside the executable is valid for
that request and does not by itself make coverage partial. This C naming example
does not establish a translation rule for Swift, C++ or other mangled symbols.
Each event is an observation: thread, image, symbol, load and file address,
the raw integer argument registers, optional caller frames and, for an
Objective-C method, the selector read from `_cmd` and the receiver's dynamic
@@ -942,9 +555,6 @@ verified, the result or error preserves that uncertainty. The result reports:
Launch errors keep the fact that LLDB created a process separate from whether
it remains alive. An exited `SBProcess` is reported as exited only after LLDB
confirms that state; unresolved ownership remains retained for cleanup retry.
An invalid process returned by `Launch`, including a rejected working directory,
reports explicit no-process creation. With no target PID or other retained
resource, REA releases that runtime and admits the next observation.
Cancellation observed while output and Evidence are finalized returns partial
cancellation Evidence, even if the target has already exited.
@@ -952,7 +562,7 @@ cancellation Evidence, even if the target has already exited.
while excess bytes are drained and counted without growing capture files).
Each stream reports `complete`; when draining could not finish, its byte
count is an observed lower bound and truncation is explicit;
- caller-selected arguments, working directory, and environment overrides, including literal JSON environment names such as `__proto__`;
- caller-selected arguments, working directory, and environment overrides;
- `selected_file_sha256` and LLDB module identity observations. These checks do
not pin the executable against concurrent mutation; `loaded_image_sha256`
remains unknown;
@@ -1043,30 +653,8 @@ macOS ARM64 is the real host verified during this implementation. Admission of
macOS Intel does not claim an Intel verification run. Unsupported metadata and
unresolved runtime/value semantics remain visible in results.
Signature inspection requests the selected architecture explicitly for identity,
hashes, designated requirements, entitlements and runtime flags. For universal
inputs, other slices receive signing-state probes; their detailed metadata is
not included in the selected result, and coverage states that boundary. The
digest-verified temporary snapshot writes to the filesystem; discovery declares
that effect even though the selected artifact is unchanged.
## Interface Builder hierarchy coverage
Bundle archive selection preserves directory traversal uncertainty, including
filenames that cannot be decoded as UTF-8. These names are not guessed or
counted as known archive omissions. Recognized symlinked `.nib` entries are
not followed; they retain their paths and reader limitations as counted archive
omissions. Either gap makes `archive_decode` partial and the graph truncated
while preserving decoded documents. Its `examined` count remains actual decode
attempts, and asset-catalog UI joins retain their existing incomplete-graph
qualification.
UI dispatch joins use the authored receiver's class independently of its
presentation kind (controller, view, control or another object). An unresolved
action receiver remains an examined, unresolved dispatch relationship and makes
`ui_to_objc_dispatch` partial. Graph object limits retain their own cause in
connection coverage, rather than being attributed to a connection budget.
A document's `hierarchy_complete` flag follows its own hierarchy coverage.
If graph limits omit a hierarchy source or destination, or prevent a hierarchy
edge or traversal from being retained, that document reports partial hierarchy
@@ -1115,31 +703,8 @@ attributes; exact and ambiguous projection counts cover emitted attribute values
Integral reals stay numbers, and ambiguous or incompletely observed values retain
explicit precision limitations. Before plist decoding, the existing aggregate
decode budget reserves estimated room for number metadata and attribute
projection; these estimates are not a hard process-memory bound. Keyed-archive
object identities always come from their original `$objects` table indices.
Authored `objectID`, `object-id`, `archiveUID` and `id` fields remain ordinary
object data and do not change inventory, reference, connector or hierarchy
identity. This changes keyed-archive graph IDs that previously used authored
`objectID` or `object-id` values: `interface_builder_object_id` and linked node
IDs now use the decimal table UID, while the authored values remain in attributes.
For example, an authored `objectID: "2"` on table entry 1 now identifies object
`"1"`; its graph node ends in `object:["1"]`. Nonkeyed ibtool dictionary IDs
and native NIBArchive IDs keep their existing source-specific meaning.
Connector endpoints require encoded UID references to archived objects; strings
and dictionaries that merely resemble resolved reference stubs do not establish
identity. Inline hierarchy dictionaries have no table identity and count as
omitted references, while independently referenced children remain available.
The UID identity rule follows the
[Foundation unarchiver's table lookup](https://github.com/swiftlang/swift-corelibs-foundation/blob/28249ced079d8e7ec03b821c039bf9868738c3fc/Sources/Foundation/NSKeyedUnarchiver.swift#L213-L223).
UIKit keyed nibs select their explicitly named
`UINibTopLevelObjectsKey` collection; generic `root` is used when that named key
is absent. Unsupported or dangling named roots remain incomplete rather than
falling back to another collection. The key spelling follows the independent
[WinObjC UINib reader](https://github.com/microsoft/WinObjC/blob/94f6b5bfdc09d945e30607b56124b6f94a5b9d7b/Frameworks/UIKit/UINib.mm#L104-L109).
NIBArchive float32 and float64 values retain non-finite values and negative zero
through projection as the same typed real wrappers used for plist values; per-document limitations
count those values. NIBArchive nil remains JSON null.
projection; these estimates are not a hard process-memory bound. Native
NIBArchive decoding is unchanged by this plist-specific representation.
Dates are reported as `{ "$plist_type": "date", "iso8601": "<timestamp>" }`.
XML dates require a complete UTC spelling with fixed two-digit month, day and
@@ -1149,13 +714,7 @@ leap seconds) and out-of-range dates remain explicit unknowns. REA does not
emulate CoreFoundation's normalization of overflowing calendar fields. Invalid
dates returned by other decoders retain `{ "$plist_type": "date", "iso8601": null }`
with a counted limitation. `inspect_plist` and Interface Builder decoding use
the same projection. Binary plist decoding refuses a reachable date whose marker
is not `0x33`; date and numeric payloads must end before the offset table.
Valid overlapping object records retain their independent source values. A valid `0x33` date is
reported with an ISO-8601 timestamp only when its float64 seconds are exactly
the canonical encoding of that millisecond instant. Any other finite binary
date uses `{ "$plist_type": "date", "iso8601": null }` and the counted
limitation; the archive bytes remain the source of the instant.
the same projection.
A negative-zero real is reported as `{ "$plist_type": "real", "value": "-0" }`
because a JSON number cannot carry the sign of zero. `inspect_plist` and
@@ -1200,21 +759,3 @@ skipped uninitialized blocks. A missing match does not establish absence from
uninitialized memory or across block boundaries. See
[Ghidra byte search](ghidra-provider.md#masked-byte-search) for mask and coverage
semantics. This query never executes or modifies target bytes.
Exact-name xref lookup reports distinct candidate addresses as an input ambiguity;
inventory order cannot select one silently. Duplicate rows at one address remain
a singular match, and a missing name remains an unresolved observation.
Hopper procedure names must identify a procedure entry. A non-entry symbol returns
an input diagnostic with its coordinate and containing procedure; an explicit
numeric address still selects its containing procedure. Use
`resolve_containing_procedure` to request containment directly. IDA and Hopper
reject reserved `file-offset:` selectors before name lookup; source-file offset
selection belongs to the Ghidra import mapping.
Native function names carry observed symbol metadata where available. Ghidra uses
its function symbol's source and dynamic flags; Hopper and IDA leave origin
unknown because this adapter has no verified origin API. Comparison and matching
cannot use unknown labels as cross-artifact identity, even when their spelling
looks meaningful. Original labels remain in Evidence. This qualification does not
change address equality in a known common native coordinate context.
Loaded 100 of 4114 files, more files were not shown because too many files have changed in this diff. Show more