morluto/rea

REA

This repository is the REA project this site documents. Install the CLI or MCP server on your machine so a coding agent can trace one feature at a time: REA reads a program you are allowed to open and returns evidence you can verify, separate from guesses. It does not return the vendor's source, and this page does not describe a way around a license.

258,295·15 Oct 2026TypeScriptMIT

English · 简体中文 · 繁體中文 · 日本語 · 한국어 · Türkçe · Русский · Tiếng Việt · ไทย · Deutsch · Español · Français · Українська · Polski · Português (Brasil) · العربية · فارسی · Bahasa Indonesia · Italiano
· Ελληνικά

REA: Reverse Engineer Anything

One MCP for reverse engineering across binaries, applications, and runtime behavior.

See a feature you like. Understand how it works, down to the binary level.

npm version
MCP tool catalog
Node.js requirements
skills.sh
MIT license
Discord

morluto%2Frea | Trendshift

Website · Guides · Showcases

Quick start · How REA works · What you can analyze · Showcases · FAQ · Documentation

npx rea-agents setup

Important

Get REA from morluto/rea,
rea.tools, or the rea-agents npm package.
Follow the installation and release links from these official sources. A similar
repository name or copied README does not establish that a download is official.


REA launching its analysis bridge inside Hopper while inspecting a native binary



Discord
Join the Reverse Engineering Community

Discord · Q&A · Show and Tell


See a feature in an app that you want in your own product? Ask your agent to investigate it with REA. It can inspect the app without its source code, explain how the feature works, show the evidence, and build a version for your project.

REA connects your agent to tools for inspecting native binaries, JavaScript and Electron apps, .NET assemblies, and websites. You can also use the same tools from your terminal. Analysis runs locally, and results include the evidence and limitations behind each conclusion.

Setup registers REA with your agent and installs matching workflow instructions. Native analysis can use an existing Hopper, Ghidra, or IDA installation; setup can optionally install Hopper with approval. Static JavaScript analysis needs no native analysis engine.

Visit the REA website for setup instructions, illustrated guides, and real case studies.

Quick start

On Linux, Ghidra, JADX, firmware tool launches and static JavaScript/Electron
analysis require a procps-compatible ps on REA's PATH for process ownership
checks. Recovery steps are in docs/installation.md under
"Linux owned-process prerequisite".

Set up your agent

With Node.js and npm installed, run:

npx rea-agents setup

Choose your agents, review the proposed changes, and approve them. Setup adds
REA's MCP server and matching workflow instructions, with backups of existing
configuration. Restart your agent afterward.

Setup supports Claude Code, Codex, Cursor, Gemini CLI, Grok Build and
other agents. See
installation and setup for provider configuration and
manual MCP registration.

Ask your agent

Understand how search works in the Notes app, show me the evidence, and build a
similar feature for my project.

Replace Notes with your target app and the feature you want to understand.

Use the terminal

Inspect an extracted JavaScript/Electron app directory or ASAR:

npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json

The result includes modules, imports, Electron boundaries and their evidence.
Replace the path with your target, such as "D:/apps/example" on Windows.

To install the rea command for regular use:

npm install --global rea-agents
rea --help

For native analysis, configure a provider first. See the
CLI and Evidence guide for native commands, provider selection,
snapshots and scripting.

Update REA

REA changes quickly, and new releases include frequent bug fixes. Keep your
installation up to date.

For a CLI installed with npm install --global or bun add --global:

rea update

Bun global updates require Bun 1.2.17 or later. With an older Bun release,
upgrade Bun manually before running rea update.

To refresh your agent registrations and skill, run the setup command printed
by the update.

If you use npx, update your agent setup with:

npx rea-agents@latest setup

Review the setup changes and restart your agent. For one-off CLI commands,
use npx rea-agents@latest followed by the command. With Bun, use
bunx rea-agents@latest in place of npx rea-agents@latest.

How REA works

Your agent calls REA through MCP to inspect the target and trace relevant code.
REA returns findings with their evidence. The agent uses them to ask follow-up
questions, explain the behavior, or write and test an implementation.
CLI commands use the same workflows.

REA investigation flow: your agent asks about a local target, REA inspects and traces it using analysis tools, and the agent uses the returned code, references and unknowns to explain, implement and test.

Open the full-size figure.

What you can analyze

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
Native binaries Pseudocode, assembly, strings, symbols, calls and references Hopper, Ghidra or IDA; native analysis
Offline ELF layout Sections, segments, original symbols/relocations and static mitigation candidates Caller-supplied pwntools on Linux x64; binary diagnostics
Go binaries Embedded compiler version, modules, replacements and build settings Built-in static ELF/PE/thin Mach-O inspection; no Go installation required
Rust binaries Embedded cargo-auditable crate names, versions, sources and dependency edges Built-in static ELF/PE/thin Mach-O inspection; Rust binaries
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
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
JavaScript / Electron Modules, imports, source maps, routes, IPC and native add-on relationships Node.js and npm; Linux: procps-compatible ps; application analysis
Websites Page structure, scripts, network observations and requested screenshots A Chrome-family browser; browser analysis
Saved network captures Requests, responses, exposed payloads and source locations HAR or Fiddler Everywhere SAZ; mitmdump on Linux for native mitmproxy; capture guide
.NET assemblies Metadata, CIL instructions, declared native dependencies and build comparisons None (static only); managed-code guide
Android APKs Manifest declarations, classes, decompiled methods and references Headless JADX and a full JDK on Linux/macOS/Windows x64; Android guide
Android devices Devices, packages, processes, logs, dumps, screen captures, and APK/file transfers Caller-supplied adb (emulator or hardware); ADB guide
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
Android resources Decoded manifest, string tables, locales and version facts Caller-supplied Apktool launcher; resource guide
Flutter apps Dart snapshot hash, per-ABI library digests and engine build provenance None (static APK parsing); Flutter guide
Firmware Regions, extraction results and native-analysis handoffs Binwalk / Unblob on Linux; firmware guide
Recorded packets Selected fields, repeated values, filters and decode-as rules Linux: TShark 4.x + prlimit; capture guide
Packet summaries Endpoints, DNS, TLS SNI, protocol distribution and HTTP requests Caller-supplied tshark and capinfos (Linux-verified); pcap guide
Recorded logic signals Decoder annotations, sample ranges and diagnostics Linux: sigrok-cli 0.7.2 + prlimit; signal recordings
Packages and resources File inventories, digests, plists, Apple bundle anatomy and extracted resources Artifact and JavaScript guide, Apple applications
Process behavior Terminal output, interactions, exit and filesystem observations, and run comparisons Linux/macOS with a native PTY; process capture

Static JavaScript and .NET inspection read the supplied files without running
the application. Runtime capture runs or interacts with the selected target
using your user permissions; each runtime guide describes its effects.

For offline native-binary inspection, REA also supports caller-supplied GNU
objdump and Rizin. See the reverse-engineering tools guide
for provider setup, CLI commands, and Evidence limitations. REA does not install
or update either provider.

Native formats and host support vary by provider. See
Hopper and Ghidra setup, the
IDA guide (including an optional native MCP bridge), the
Kuna decompiler guide, and
experimental Windows Ghidra support.
Ghidra also supports 16-bit DOS analysis.
For large binaries, raise the provider's startup deadline with
REA_GHIDRA_STARTUP_TIMEOUT_MS or REA_HOPPER_STARTUP_TIMEOUT_MS.
For provider selection, see the CLI guide.
Check release availability
for features added since the latest npm release.

Showcases

Illustrations of the DX-Ball sound-pan, Notion clipboard-bridge, and TH04 bullet-ring showcases

DX-Ball: reconstruct a sound-pan calculation

Follow a sound call into its position-to-pan helper, inspect the instructions,
and turn incomplete pseudocode into C. The reconstruction passes 3,205
original-x86 cases and reproduces all 63 compiled function bytes.

Read the case study ·
Reconstruction repository

Notion: trace the Electron clipboard bridge

Find the renderer's clipboard API, follow it through preload and IPC into the
main process, and inspect the rich clipboard format.

Read the case study

TH04: recover a DOS bullet-ring calculation

Inspect the original PC-98 game's 16-bit instructions, recover the fixed and
aimed angle calculations, and compare the reconstructed C++ with the
historical compiler output.

Read the case study ·
Reconstruction repository

If you've used REA on something interesting, we'd love to see it. Share your
case in an issue or a
pull request, including the target,
your question, how REA helped, and what you found.

FAQ

Which agents can use REA?

Any agent that supports local MCP servers. Setup configures the
supported agents; other clients can use
manual MCP registration.

Do I need Hopper, Ghidra or IDA?

Deep native analysis uses one of them. Static JavaScript and .NET inspection
work without a native analysis engine. Setup can install Hopper after approval;
Ghidra and IDA use your existing installations. See provider setup.

Do I need to start Hopper first?

REA starts Hopper when an operation needs it. On macOS, a first-run dialog may
ask you to choose demo mode or activate your license. Complete that prompt in the
same user session before your first CLI/MCP operation or unattended job. Open
Hopper manually if needed. See
Hopper startup and troubleshooting.

What does installing the skill from skills.sh do?

The skill supplies investigation instructions for your agent. Use npx rea-agents setup
to register REA's MCP server and install the matching instructions, then restart
your agent. See skill-only installation.

What code does REA return?

Native analysis returns pseudocode and assembly. JavaScript/Electron analysis
recovers modules and their relationships. Your agent uses these findings to
write and test an implementation; the showcases give worked examples.

Does REA upload my app?

REA analyzes targets locally. Your agent receives the tool results, and its
model provider has its own data policy.

What should I do if I hit a bug?

Update first; a recent release may already fix it.

For a CLI installed with npm install --global or bun add --global:

rea update

For agent setup through npx:

npx rea-agents@latest setup

If you're using an agent, complete the setup refresh and restart
it. Retry the same task. If the problem persists, open an issue
with your REA version, target type, steps to reproduce and error output.

Documentation

Start with the website's worked guides.
For exact options, prerequisites and result contracts:

Report vulnerabilities through SECURITY.md.

Star history

🎉 250,000 GitHub stars — thank you!

Thanks to everyone using REA, reporting bugs, requesting features, testing builds, and contributing fixes.

REA GitHub star history

Disclaimer

See DISCLAIMER.md for responsible-use guidance.

The project's position on illegal or unauthorized use covers all REA versions,
distribution channels, and platforms; each copy retains its applicable licenses.

REA is for lawful research, education, interoperability, software preservation,
and analysis of software you own or are authorized to examine.

Contributing

We'd love your help with REA! Open an issue to
report a bug or suggest a feature, or send a pull request
to improve the code or docs.

See CONTRIBUTING.md for development setup and checks,
testing for verification lanes, and the
architecture map for the project structure.

License

MIT

REA-authored code is licensed under MIT. Third-party material retains its own
licenses and copyright notices; see
Third-party notices for the dependency inventory,
preserved license texts, and external-tool terms.

You must legally obtain and be authorized to use commercial tools, including
IDA Pro / Hex-Rays decompilers, Hopper, and JEB, under their applicable
licenses. REA grants no tool license, key, or activation entitlement. Trials and
demos remain subject to vendor terms, including user, feature, automation, and
output sharing restrictions. Cracked copies, stolen keys, unauthorized key
sharing, and bypassing license restrictions are not authorized. This guidance
does not amend MIT or third-party licenses.


Static Android APK analysis

REA wraps an existing headless JADX engine through src/android/. Its source is
kept as an unmodified, commit-pinned Git submodule; see
upstream provenance. CLI and MCP use the same
application workflow and return artifact-bound Evidence inline.

This family is present on repository main and in npm 4.1.0. Check the
released package boundary and the
connected server's tool list before selecting it.

On Linux, JADX launches an REA-owned process and requires a procps-compatible
ps on REA's PATH; see the shared
Linux process prerequisite.

Optional package delivery

The MCP inspect_android_package request keeps complete output when detail is
omitted or "complete". Select detail: "summary" explicitly to receive all
package, SDK and permission declarations, engine identity and class/resource
counts without decoded manifest text or duplicate raw provider replies. A
retaining session keeps the complete original Evidence and returns a distinct
linked inspect_analysis_view summary. This changes delivery, not JADX analysis.

Use the returned normalized_result.parent_evidence_id with
inspect_analysis_view, source kind retained-evidence, and
view: {kind: "android", facet: "manifest", offset: 0, limit: 4096} to read the
exact decoded manifest without reopening the APK. Offsets and limits count UTF-16
code units; follow coverage.next_offset. The page preserves source_status and
reported_total_bytes: exhausting retained text does not make a partial decoded
manifest complete. Complete source Evidence remains exportable under the existing
session lifetime. Servers without retention reject summary before starting JADX.
One-shot CLI output remains complete. Older servers require the existing complete
request after schema discovery. APK authenticity and runtime behavior remain
unverified.

Supply tools

Use a full JDK 17 or newer (including its compiler module) and
jadx-headless-mcp 0.7.1.
Verify the downloaded JAR's SHA-256 against
REA's pinned release metadata
before using it. The following download and verification commands work for npm
package consumers as well as repository checkouts; the repository-only
npm run fixtures:android helper is not included in the npm package.

On macOS or Linux, choose a destination and verify the pinned digest:

mkdir -p "$HOME/.local/share/rea/android" &&
cd "$HOME/.local/share/rea/android" &&
curl -fL -o jadx-headless-mcp-0.7.1-all.jar https://github.com/1013503897/jadx-headless-mcp/releases/download/v0.7.1/jadx-headless-mcp-0.7.1-all.jar &&
if command -v sha256sum >/dev/null; then
  echo '6e5eacf500b64292bfb73c49797c1958f6ee44646e43e868039ae7feb573ff75  jadx-headless-mcp-0.7.1-all.jar' | sha256sum -c -
else
  echo '6e5eacf500b64292bfb73c49797c1958f6ee44646e43e868039ae7feb573ff75  jadx-headless-mcp-0.7.1-all.jar' | shasum -a 256 -c -
fi &&
export REA_JADX_MCP_JAR="$PWD/jadx-headless-mcp-0.7.1-all.jar"

On Windows, run the equivalent in PowerShell:

$jar = Join-Path $HOME '.local\share\rea\android\jadx-headless-mcp-0.7.1-all.jar'
New-Item -ItemType Directory -Force (Split-Path $jar) | Out-Null
Invoke-WebRequest -Uri 'https://github.com/1013503897/jadx-headless-mcp/releases/download/v0.7.1/jadx-headless-mcp-0.7.1-all.jar' -OutFile $jar
if ((Get-FileHash -Algorithm SHA256 $jar).Hash.ToLowerInvariant() -ne '6e5eacf500b64292bfb73c49797c1958f6ee44646e43e868039ae7feb573ff75') { throw 'JADX JAR SHA-256 does not match REA pinned metadata' }
$env:REA_JADX_MCP_JAR = $jar

REA runs its packaged Java metadata bridge in source-file mode against the
unmodified engine JAR. REA does not install Java, an Android SDK, JADX, a device
service or an emulator. Set the path in the shell or MCP server environment:

export REA_JADX_MCP_JAR=/absolute/path/jadx-headless-mcp-0.7.1-all.jar
# Optional: select an existing JDK rather than java on PATH.
export JAVA_HOME=/absolute/path/existing-jdk

On Windows x64, use a REA package with its matching bundled Windows native
controls including owned protocol stdin support. This Windows boundary is
new on repository main; an older released package can still reject this host.
PowerShell accepts paths containing spaces without additional escaping
inside these environment values:

$env:REA_JADX_MCP_JAR = 'D:\tools\jadx-headless-mcp-0.7.1-all.jar'
$env:JAVA_HOME = 'D:\tools\jdk-21'
rea inspect-android-package 'D:\targets\Example.apk'

REA selects JAVA_HOME\bin\java.exe, or resolves java.exe on the selected
Windows PATH. A missing or mismatched native artifact reports the package
prerequisite rather than attempting an unowned Java launch. Windows arm64 is
outside this provider's current native boundary.

Readiness checks the selected JAR's ZIP directory for the classes consumed by
REA's metadata bridge, as well as Java's compiler and version modules. It does
not load an APK or execute engine code. Invalid archives and missing bridge
classes report JAR-specific recovery; classpath inventory alone does not prove
that every class or engine operation will load successfully. Discovery limits
each ZIP metadata read to 8 MiB before allocation; a larger directory reports
the exhausted read budget and the selected JAR path.

The current metadata bridge is verified on macOS arm64 with OpenJDK 21 and the
public Appium ApiDemos fixture. The POSIX adapter also supports Linux; the new
bridge has not yet undergone real verification on Linux/JDK 25.
Windows x64 uses an owned Job Object with bidirectional stdio, atomic job
assignment and kill-on-owner-close. Real Windows verification uses JDK 21 and
the pinned JADX 0.7.1 JAR; see the real verification lane below.

Answer an analyst question

rea inspect-android-package /path/Example.apk
rea search-android-classes /path/Example.apk MainActivity
rea inspect-android-class /path/Example.apk example.MainActivity
rea inspect-android-method /path/Example.apk example.MainActivity onCreate
rea inspect-android-method /path/Example.apk example.MainActivity select --overload-index 1
rea trace-android-references /path/Example.apk example.MainActivity
rea trace-android-references /path/Example.apk example.MainActivity --method-name onCreate
rea trace-android-references /path/Example.apk example.MainActivity --method-name select --overload-index 1

Equivalent MCP methods are inspect_android_package, search_android_classes,
inspect_android_class, inspect_android_method and trace_android_references.
Every request includes path. Class/method operations use class_name,
method_name and optional overload_index as applicable. For example:

{
  "name": "inspect_android_method",
  "arguments": {
    "path": "/path/Example.apk",
    "class_name": "example.MainActivity",
    "method_name": "select",
    "overload_index": 1
  }
}

Use inspect_android_class to choose an overload. REA rejects an ambiguous
method request rather than silently selecting the first match. Method reference
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.

The engine's smali fallback also joins same-name overloads; REA rejects that
fallback for an overloaded method instead of attributing all bodies to one
selected overload. Native/abstract methods with no decompiled body report
body_status: "not_available" without inferring which implementation flag caused it.

Static initializers (<clinit>) return smali with the
static_initializer_disassembly marker. Java decompilation can move their
instructions into field declarations, leaving an empty or incomplete method
body. The selected method's disassembly preserves those instructions and retains
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. 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 consumes all pages and checks their counts. An empty query
    inventories all class-name rows; class names are decompiler representations.
    Repeated names remain repeated in matches so the inventory row count and
    producer evidence stay visible. duplicate_names identifies matching names
    with multiple rows and their occurrence counts; a name alone does not
    distinguish which provider class node a later lookup selects.
  • Class inventories read parsed metadata without generating source and return
    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.
    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
    are tied to this artifact and engine, not stable identities across builds.
  • Method inspection reports Java or smali, fallback markers and complete/partial
    returned text. Decompiled source is a derived representation. A complete text
    result does not establish complete semantics or a byte-identical reconstruction.
  • Reference results are incoming static relationships between provider nodes.
    reference_unit=using_node means total counts containing JADX nodes, not
    individual invoke instructions; two calls from one method can appear as one
    entry. coverage=complete covers this node inventory only. For a method
    reference whose using node has raw code, direct_invokes lists matching DEX
    invoke instructions with their DEX entry, raw source method ID, code-unit
    offset, method index and opcode. An empty array means no direct invoke was
    matched in that node; null means raw code was unavailable or the using node
    was not a method. This list does not establish complete call-site coverage
    outside JADX's using-node inventory or through reflection and dynamic loading.
    A virtual or interface invoke also does not establish its runtime implementation.
    Source lines and the containing node's own dex_offset remain unknown.
    Method-reference results also carry the selected dex_descriptor;
    class-reference results report null.
    These relationships are not observed runtime calls.
  • Original requests, APK SHA-256, actual engine JAR SHA-256 and raw producer
    responses remain in Evidence. Source revision is populated only for bytes
    matching the audited release JAR.

If a later step fails, the error's details.partial_observation retains the
selected and resolved APK paths, digest, observed provider identity and versions,
and each upstream request and reply collected before the failure. Failed replies
remain available even when their payload cannot be interpreted. Cancellation
also stops target hashing before provider admission.

These operations do not execute the APK or provide split APK/AAB handling,
signature validation, full resource-table semantics, native-library analysis or
Android runtime capture. An .aab suffix is a ZIP archive, not an APK, so
Android tools and the application graph do not treat it as a standalone APK.
Existing artifact inventory/extraction tools can supply archive evidence.
Inventory records an APK root only for ZIP bytes whose name ends in .apk.
The projection adds a limitation when no root AndroidManifest.xml was
observed, because such a ZIP may not be an APK. Nested members take a ZIP or
ASAR family from their bytes; an unread member keeps an unknown format.
Inventory keeps ZIP names that differ only in case and records that a
case-insensitive destination cannot store both spellings. Extraction fails
only when that destination directory cannot keep both names. Project that
Evidence with the execution-free inventory tool:

rea project-android-application-graph '{"inventory_evidence":[<inventory_artifact Evidence>]}'
{
  "name": "project_android_application_graph",
  "arguments": {
    "inventory_evidence": ["<inventory_artifact Evidence>"]
  }
}

The projection reports exact component paths and hashes, runtime-family hints,
and path-based bridge hypotheses. It does not decode DEX or claim observed
runtime calls. Framework hints match complete native library names, 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 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
candidate-array size before creating candidate objects, then retains a
deterministic prefix within a 2 MiB UTF-8 JSON budget. If candidates are omitted,
bridge_candidate_coverage reports total, emitted, and omitted counts; overall
coverage is partial while component arrays still include every component
from the supplied inventory pages. A path pair does not establish that a
managed declaration names, loads, or calls the native library.

Resource and lifecycle limits

REA retains one serialized session with private, immutable APK, engine JAR and
metadata-bridge copies. Matching requests reuse the loaded engine. APK/engine
digests and JVM configuration are checked before reuse; changed inputs retire
the previous session. Sessions are cleaned after 60 seconds of inactivity, on
target/configuration changes, failure, timeout, cancellation, MCP disconnect or
server shutdown. One-shot CLI commands always join cleanup before returning.
REA never writes to the original APK or launches target code.

On Windows, stdin writes preserve stream backpressure and run outside the Node
event loop. Cancellation terminates the retained job, including descendants,
before workspace cleanup. On Windows, a detached REA helper owns the private
workspace's native directory handle. An abrupt CLI/server owner termination
closes the Java job and the helper's input pipe; the helper then removes the
workspace by handle. The real Windows lane checks both effects. If the helper
also dies or the host loses power before it finishes, private files can remain.

If Java cannot start the bridge, REA reports the selected executable and retained
startup diagnostics. Select a full JDK through JAVA_HOME or PATH, and use
java --list-modules to confirm jdk.compiler is present. A runnable JRE alone
cannot compile the bridge. Missing or ambiguous class and method selections are
input failures; discover names with search_android_classes and
inspect_android_class before correcting the selected argument.

If process cleanup cannot be confirmed, REA retains the workspace and reports its
location. That provider instance blocks subsequent engine launches until the
caller resolves the reported resources and starts a fresh instance.

The provider uses one JADX analysis worker independently of JVM processor and
garbage-collector settings. REA supplies no default heap or visible-CPU override;
Java uses its normal ergonomics and inherits caller JVM settings. Optional
provider settings add explicit JVM arguments:

export REA_JADX_HEAP_MIB=8192
# Optional, independent of the single JADX worker:
export REA_JADX_ACTIVE_PROCESSOR_COUNT=4
# Or select settings through Java's standard environment options:
export JAVA_TOOL_OPTIONS=-Xmx8g

Both REA settings must be positive safe integers; processor counts must also fit
the JVM's signed 32-bit range. REA preserves
JAVA_TOOL_OPTIONS, JDK_JAVA_OPTIONS and _JAVA_OPTIONS rather than replacing
them. Java's standard precedence applies: explicit command-line heap settings
override JAVA_TOOL_OPTIONS, while _JAVA_OPTIONS can override command-line
settings. Evidence reports the worker's observed Runtime.maxMemory() rounded
down to MiB and retains exact bytes, Java version and visible processors in its
raw runtime response. The observed allocatable heap can differ from configured
-Xmx, especially with Serial GC. Heap is not a total RSS cap: the JVM also
uses native memory. Independent REA instances have independent queues. Whole
operations have a 120-second execution deadline after reaching the queue's
front; upstream decompilation is limited to 90 seconds.

Decoded manifest and method text have a 1 MiB upstream byte budget and report
truncation. Class-name pages use indexed slices of up to 4,096 names without changing the
complete, case-sensitive search contract. A protocol frame above 8 MiB, or combined stdout/stderr above
32 MiB per operation, fails with a diagnostic instead of returning an incomplete inventory
as complete. These are memory-safety boundaries, not undocumented result caps.

Real verification

Download the explicit public fixtures once; this installs no tools:

npm run fixtures:android
export REA_JADX_MCP_JAR="$PWD/_reference/apk-integration/jadx-headless-mcp-0.7.1-all.jar"
export REA_ANDROID_TEST_APK="$PWD/_reference/apk-integration/ApiDemos-debug.apk"
npm run verify:android

The lane also uses the selected full JDK's jlink to create a disposable JRE
without the compiler module. It checks real Java startup failures, missing
selectors, recovery after failures, and CLI cancellation with owned cleanup.
The original APK and host Java configuration remain unchanged.

The same lane runs on Windows x64 with the bundled native controls and a full
JDK. It checks CLI/MCP results, real MCP cancellation and disconnect, and Java
exit and workspace removal after abrupt CLI owner termination. Windows forced
termination is checked separately from the POSIX CLI SIGTERM handler. A
synthetic two-owner test confirms that one owner's exit does not remove the
other owner's workspace. The lane does not claim a structured cancellation
result or power-loss recovery.

The download script verifies fixed SHA-256 values, reuses matching files and
refuses to overwrite a different existing file. APK/JAR files live under ignored
_reference/ and are excluded from package contents. The lane exercises actual
REA CLI and MCP results with the real engine, separately from synthetic protocol
regressions. It needs no Gradle build, emulator, Android SDK or Ghidra.

Direct invoke sites retain exact DEX code-unit offsets separately from each
containing node's unknown dex_offset. direct_invokes_exhaustive: false
means instruction rows were bounded by the local bridge frame budget; null
means the producer did not establish instruction coverage. Using-node totals
and coverage do not imply complete instruction evidence.


Apple Application Inventory Projection

REA can project one or more authenticated inventory_artifact Evidence records
for the same IPA, macOS .app directory, ZIP, or DMG into an Apple application
inventory. The projection is execution-free: it reports exact paths, content
hashes, detected formats, bundle anatomy, runtime-family hints, and path-based
bridge hypotheses. It does not parse plist or CMS semantics or claim observed
runtime calls.

Framework hints match complete path segments such as React.framework,
ReactNative.framework, hermes.framework, Flutter.framework, and
UnityFramework.framework. Substrings in Preact.framework,
Community.framework, or MyApp.framework do not identify those families.
A generic App.framework also does not identify Flutter by itself. Runtime
families remain inventory-derived hints, not observed loading. Generic framework
and dylib paths do not establish Swift or Objective-C; this projection does not
decode language metadata. A bridge basis is inferred from the native path and
repeated for each paired script, rather than proving a particular binding.

rea project-apple-application-graph '{"inventory_evidence":[<inventory_artifact Evidence>]}'
{
  "name": "project_apple_application_graph",
  "arguments": {
    "inventory_evidence": ["<inventory_artifact Evidence>"]
  }
}

The result includes every component in each inventory category. Bridge
hypotheses are the path-based JavaScript/native pairs within each application
root. To keep their Cartesian expansion bounded, the projection limits the
serialized candidate array to 2 MiB. It computes the pair count and
exact UTF-8 JSON size before creating candidate objects, then retains a
deterministic prefix when the byte budget is exceeded. The result's
bridge_candidate_coverage reports total, emitted, and omitted pair counts;
overall coverage becomes partial and a limitation explains the omission.
Duplicate inventory pages are merged by artifact identity and occurrence path;
their Evidence IDs remain attached as source Evidence.

Content nodes identify bytes by SHA-256 and report only content-derived formats.
Each occurrence reports its own artifact_kind, artifact_format, and executable
permission. Identical bytes can therefore appear as a script, resource, or native
addon at different paths without overwriting another occurrence's role. Package
formats such as APK and IPA belong to the occurrence; their ZIP bytes share a
content format. Graph and directory identities use locale-independent Unicode
ordering.

For IPA inventories, archive components outside every application root remain
in the component lists. Their JavaScript and native candidates are grouped with
other unrooted components, preserving the inventory projection's path pairing
semantics without attributing them to an application bundle.

Coverage is complete-within-inventory when the supplied pages reconstruct the
complete authenticated inventory, and partial otherwise. A partial result
states that absence is unknown. Component arrays remain complete when candidate
hypotheses are omitted; component arrays still include every component from
the supplied inventory pages, and overall coverage is partial in that case.

Application roots

Inventory root Application root
IPA Each Payload/<Name>.app
.app directory ., the inventoried bundle itself
Directory, ZIP, or DMG containing apps Each outermost <Name>.app followed by Contents/

ZIP archives made with ditto -c -k --keepParent report <Name>.app. A DMG
reports <image>.dmg/<Volume>/<Name>.app; DMG child inventory needs the native
macOS mounting adapter. When a DMG has only its root identity, the projection is
partial and says that bundle absence is unknown. Other inventory formats, such
as APK or ASAR, are rejected as input errors.

An inventoried directory whose name ends in .app is its own root even when its
inventory is partial or lacks Contents/. For directory, ZIP, and DMG
inventories, components outside every application root are not attributed to
the application. This covers installers, disk-image extras, and AppleDouble
sidecars, which therefore stay out of runtime families and bridge candidates. A
limitation reports how many inventoried entries were left out. IPA projection
still lists every archive component, including SwiftSupport/.

platforms lists macos for application roots with Contents/. It lists ios
for shallow application roots that have an Info.plist. An application root
with neither, such as an empty inventory, has no platform.

macOS bundle anatomy

bundles lists each application root and every nested bundle under it. Each
bundle reports its parent_path, layout, info_plist_path,
executable_candidates, and signing_paths.

Layout Meaning Content directory
macos-deep Has Contents/ Contents/, executables in Contents/MacOS/
versioned-framework Has Versions/ Each real Versions/<version>/
shallow Neither The bundle directory itself

Roles are path conventions (role_basis: "path-convention"):

Role Path
app-extension *.appex
xpc-service *.xpc, including XPC services inside frameworks
framework *.framework
system-extension *.systemextension
driver-extension *.dext
plug-in PlugIns/*.bundle, *.plugin, *.qlgenerator, *.mdimporter
resource-bundle Resources/*.bundle, such as SwiftPM resource bundles
bundle Any other *.bundle
login-item Contents/Library/LoginItems/*.app
helper-application Contents/Helpers/*.app
nested-application Any other nested *.app

The components also list:

  • privileged_helpers: files directly in Contents/Library/LaunchServices/,
    where SMJobBless helpers live;
  • launchd_plists: .plist files directly in Contents/Library/LaunchAgents/
    (domain: "agent") or Contents/Library/LaunchDaemons/ (domain: "daemon"),
    as used by SMAppService;
  • helpers: files directly in Contents/Helpers/.

Symlinks are reported by path in symlinks. Their targets are not inventoried,
so a versioned framework is described through its real Versions/<version>/
directories rather than Versions/Current. A framework's info_plist_path is
null, with a limitation explaining why, when it has more than one real
Versions/<version>/ directory (even if only one of them holds a plist,
because Versions/Current decides which one applies) or when its single
version directory has no Resources/Info.plist.

AppleDouble sidecar files (._* and __MACOSX/) are inventory facts, but they
describe neighbouring files. They are excluded from roots, bundle roles, and the
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.
Use inspect_signature on an executable candidate for code-signing claims.

Dylib load resolution

trace_dylib_resolution answers which file each Mach-O dylib load reaches. It
runs on the active Mach-O, or on every executable in the active .app. Each
executable in a bundle (the main app, XPC services, app extensions, login items,
privileged helpers, and helper tools) is a separate process root with its own
@executable_path.

rea trace-dylib-resolution MyApp.app --json
rea trace-dylib-resolution MyApp.app --root Contents/MacOS/MyApp --architecture arm64 --json
{
  "name": "trace_dylib_resolution",
  "arguments": { "roots": ["Contents/MacOS/MyApp"], "architecture": "arm64" }
}

Load commands are parsed in TypeScript from Mach-O and FAT headers, including
the dylib_use_command encoding newer linkers emit for flags such as delayed
initialization. Nothing is executed and no Apple tool is required, so the tool
also works off macOS.

Resolution follows dyld:

  • @executable_path is the directory of the process root's executable. It is
    undetermined when the root is a library.
  • @loader_path is the directory of the image that holds the load command, or
    of the image that owns the LC_RPATH entry being expanded.
  • @rpath tries each LC_RPATH of the loading image first, then those of the
    image that loaded it, up to the process root.
  • Dependencies load dependents-first in load-command order, and each image
    loads once per process. A later @rpath request whose install name matches an
    already loaded image reuses it.
  • Symlinks such as Versions/Current are followed inside the analyzed root.
    An escape through .. or an absolute link is escapes-target.

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,
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 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 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 such as /usr/lib/libSystem.B.dylib are
outside-target. On macOS 11 and later most of them live in the dyld shared
cache rather than on disk, so REA does not check them against the host.

findings are derived, not observed:

  • required-load-unresolved, weak-load-unresolved, and lazy-load-unresolved.
    LC_LAZY_LOAD_DYLIB dependencies are resolved but not traversed, because dyld
    loads them on first use; an unresolved one is not a launch failure.
  • earlier-rpath-candidate-absent: a compatible Mach-O placed at an earlier
    modeled search path could take precedence, subject to unmodeled search inputs
    and code-signing library validation. These findings do not establish which
    image dyld will load; use inspect_signature for signing metadata.
  • dyld-environment-present: preserves observed LC_DYLD_ENVIRONMENT entries.
    Only an executable root supplies process settings; entries on library and
    other non-executable roots do not alter resolution or coverage. Applicable
    executable image-selection overrides are not modeled.

Empty dyld settings have different consumers. Modern dyld appends a slash and
image name to ordinary and fallback directory entries, so an empty entry can
search /child.dylib or /Foo.framework/Foo. REA retains uncertainty for those
unchecked paths; an empty fallback does not preempt an ordinary-search hit.
Empty versioned-directory lists do not scan a directory, and empty suffixes
only repeat the original candidate, so they do not add search uncertainty.
Reported command values are retained in all cases.

verify:macos-bundle checks the parser against otool -l for every traced
image. It also compares the predicted load order of two process roots with the
images dyld actually loads (DYLD_PRINT_LIBRARIES).


Offline binary diagnostics

inspect_binary_layout / inspect-binary-layout answers what an explicit
file contains: section/segment layout, original symbols/relocations, static
linkage names and mitigation indicators. It works without an active Hopper,
Ghidra or IDA target. CTF is one possible use; crash triage, compatibility and
ordinary binary inspection use the same modular capability.

Bring your own engine

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:

REA_PWNTOOLS_PYTHON=/absolute/isolated-env/bin/python \
  rea inspect-binary-layout ./selected.elf --json
{
  "name": "inspect_binary_layout",
  "arguments": { "path": "/artifacts/selected.elf" }
}

REA never installs Python, packages or GDB, changes a user init file, launches
the selected object as a host process, or requests runtime library resolution. The Python process
uses isolated mode and an owned cache. Exact upstream profiles are recorded in
upstream provenance.

Interpreting results

  • Artifact path, SHA-256 and size identify original selected bytes. All parsing
    reads an owned stable snapshot; the original is unchanged.
  • Addresses, offsets, lengths and flags are hexadecimal strings. A linked
    address is not a runtime address. Runtime load base remains null. A zero
    EXEC/DYN entry means absent; a relocatable entry is not applicable.
  • Each symbol retains its table and entry index. Its reported value can mean
    undefined, alignment, absolute value, no address, unknown section index,
    section offset, TLS offset or linked virtual address. Duplicate names remain
    separate entries. SHN_XINDEX remains unresolved in the selected upstream
    representation; its external index table is not silently treated as resolved.
  • Relocatable objects have section-relative relocations and no executable entry
    claim. Signed REL/RELA relocation addends are decimal strings. Packed RELR
    tables retain full original bytes and upstream-decoded offsets as derived
    evidence, with per-offset packed-word locations and implicit addends unknown.
    Overall relocation inventory completeness remains unknown.
    A relocatable SHN_UNDEF target or inactive SHT_NULL header remains an explicit
    unknown with the original reported section index and offset.
  • REL/RELA symbol references retain their original table and entry indices.
    Positive indices resolve through a validated symbol table; symbol index zero
    means a zero symbol value without a table lookup, including when no table is
    linked. Malformed references fail with the affected section and symbol index.
  • Section-name and symbol tables require declared SHT_STRTAB links.
    SHN_UNDEF explicitly means no section names: display is empty, raw name and
    location unknown, with the original name offsets retained. Extended section-name
    indices resolve through section zero and undergo the same validation.
  • Symbol tables require a declared SHT_STRTAB link. Name offsets and terminators
    must stay inside the declared string table; malformed references fail before
    unrelated bytes can become names. Dynamic dependencies use DT_STRTAB/DT_STRSZ
    with a unique file-backed mapping; ambiguous or unbacked mappings are
    explicitly unsupported.
  • Name display strings use UTF-8 replacement for opaque bytes, including
    sectionless dependency names and interpreter paths. Raw name
    bytes and string-table ranges retain observed identity where resolvable;
    ranges include the terminating NUL and base64 bytes exclude it.
  • NOBITS and NULL sections provide no file bytes. Reported file-backed ranges
    are checked against snapshot size again at the public boundary. PT_NULL payload
    fields are unused: reported numbers remain, file backing is none and interpreted
    permissions are null. Segments with zero file size also have no file bytes.
  • Sectionless dynamic images still report dependency names with raw bytes and
    file locations where uniquely mapped. Missing symbol/relocation section tables
    do not establish that those runtime tables are absent.
    Upstream RELRO/canary heuristics rely on sections and can be incomplete in
    this case; their reported candidates retain an explicit coverage limitation.
  • DT_NEEDED/PT_INTERP are reported names, not resolved runtime paths. GOT/PLT
    maps are derived convenience views and may collapse aliases. PLT inference
    can emulate selected instructions in Unicorn; its outputs are static candidates.
    Exec syscall tracing observes host process launches. Map completeness
    remains unknown and original upstream warnings are returned inline.
  • Canary, PIE, NX, executable-stack and RELRO are static inferences. NX and
    executable-stack are separate upstream indicators: on x86-64 an executable
    stack can accompany an unknown (null) NX indicator. ET_DYN can be a shared
    library, and an absent canary symbol does not prove every function unprotected.

When complete layout Evidence is already retained, inspect_analysis_view /
inspect-analysis-view projects a summary, the mitigations or linkage facet,
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.

Over MCP, inspect_binary_layout with "detail": "summary" retains the
complete layout Evidence in the session and returns only its summary view.
Its normalized_result.parent_evidence_id names the retained layout for later
section, symbol, mitigation or linkage views. The default complete detail and
CLI output are unchanged.

Complete results have a 32 MiB input, 64 MiB reply, 1 MiB combined diagnostics
and 30-second owned command deadline. REA_PWNTOOLS_MAX_INPUT_BYTES raises the
input bound alone, up to 1 GiB; the reply and deadline bounds are unchanged. The
reply budget applies to the decoder record; the CLI/MCP Evidence envelope adds
copies and encoding overhead. The
pinned MCP SDK defaults to a 10 MiB receive buffer. Large complete results need
a caller-configured StdioClientTransport({ maxBufferSize: ... }) and a matching
server REA_MCP_MAX_RESPONSE_BYTES. See MCP delivery budgets.
These settings affect delivery, not decoder coverage or limits. The large
extended-index verification uses 256 MiB and an explicit five-minute request
timeout in its separate opt-in MCP lane. REA does not silently truncate the result. Python lowers resource soft limits to at most 3 GiB virtual address space,
30 CPU seconds and 64 MiB file output. Inherited tighter soft/hard limits are
retained, and the effective values are returned in the result limitations. Virtual address space is not RSS;
Unicorn needs a large virtual map. Limits fail with no partial success, and
owned process/root cleanup completes independently of cancellation.

Core files, GDB session/state/control, optional pwndbg enrichment and instruction
inspection are separate increments tracked by #969. This increment makes no
real support claim for those features or other operating systems/architectures.

Typed decoder failures retain bounded captured_output (stdout, stderr and an
explicit truncation flag) alongside the original failure category and reason.
Successful diagnostics also exposes the supervisor's truncated flag. Output
beyond the complete diagnostic budget is rejected, including when the decoder
writes a valid reply; cleanup and late cancellation reuse the observed flag.
Memory allocation failures use resource_constraint, with effective resource
limits (or an explicit unknown) and memory-specific recovery guidance. A
MemoryError alone does not establish the exact failed allocation or that a
particular budget was exhausted. Invalid undersized symbol entries are rejected
before REA reports source ranges for them.
The bridge reserves 1 MiB to report allocation failures. If even error reporting
or serialization fails with MemoryError, its reserved exit status preserves the
resource classification with unknown effective limits.
Reserved exits require a matching private marker written by the bridge's actual
failure branch. Bare launcher exits or missing/unwritable markers retain process
diagnostics with the resource cause unverified.
Signal termination alone is never classified as observed memory exhaustion.
The small bootstrap also covers catchable MemoryError during adapter imports,
compilation and initialization. Python interpreter startup failures before the
bootstrap runs retain their observed process diagnostics without guessed causes.
SIGXCPU retains a CPU resource diagnostic and CPU-specific recovery advice.
Observed EFBIG writes or SIGXFSZ termination retain a file-size resource
diagnostic and file-size-specific recovery advice. If a tight file limit also
prevents writing the limit record, effective values remain unknown.
The bridge records actual limits in owned storage before analysis; missing or
malformed limit reports remain unknown with their read failure preserved. A
received signal alone does not establish its exact cause. Dynamic tags require
a complete DT_NULL inside PT_DYNAMIC; interpreter names and ranges end at the
first NUL, with any padding still represented by the original segment range.

Portable PE resources

inspect_pe_resources / inspect-pe-resources inspects an explicit PE32 or
PE32+ file without an active target, native engine, Python, or Windows SDK:

rea inspect-pe-resources ./selected.exe --json
{
  "name": "inspect_pe_resources",
  "arguments": { "path": "/artifacts/selected.exe" }
}

The complete Evidence identifies the stable artifact snapshot by path, byte
count and SHA-256. Resources retain separate type, name and language identities:
numeric IDs are distinct from UTF-16 names, whose original bytes are also
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.

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
remain explicit. Encoded dimensions are preserved (zero denotes 256 pixels).
This is static reference inspection; no image decoding, language fallback,
resource loading or selected-file execution occurs.

An absent resource data directory is a complete empty inventory. Malformed
headers, truncated tables, cycles, duplicate sibling identities, unbacked or
ambiguous RVA mappings, nonidentical overlapping ranges and unsupported tree
shapes fail explicitly, rather than returning partial inventories. Exact shared
payload ranges are supported. The supported tree has three levels: type, name,
and language. Other resource payload formats remain opaque.

max_file_bytes defaults to 64 MiB (maximum 512 MiB); max_entries defaults to
16384, the largest stock Windows resource image measured with headroom
(maximum 65536). CLI options are --max-file-bytes and --max-entries.
Metadata is bounded to 8 MiB; exceeding any budget fails without truncation.
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.

Read the original · README · Android · Apple · Binary diagnostics

Repository

https://github.com/morluto/rea · Library