Compare commits

...
Author SHA1 Message Date
Kaoru0822andClaude Opus 5.5 0c69564297 fix(electron): leave the preload unknown after a later dynamic override
A BrowserWindow webPreferences literal kept an earlier literal preload
after a later preload with a non-literal value, a spread, a computed key
or an accessor could replace it, so the graph associated the window with
the overwritten script. Derive the effective preload from the last
definition of the property, as Electron receives it.

Closes #1237

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:55:33 +00:00
YsopwnableandGitHub 0c6852b392 docs(ghidra): explain supported heap and CPU controls (#1278)
Explain consumed heap settings, owned JVM environment overrides, CPU affinity and memory measurements in the Ghidra setup guide.

Closes #1255
2026-10-09 21:20:33 +08:00
YsopwnableandGitHub f085589957 fix(ci): pass selected environment to Windows Ghidra fixture (#1281) 2026-10-09 13:05:54 +00:00
morluto d6d1cd2485 test: remove duplicate workflow examples and self assertions 2026-10-09 21:01:53 +08:00
morluto af7a439633 fix(windows): retain runtime authority after cleanup failure 2026-10-09 21:01:53 +08:00
morluto 81b70bc358 fix(managed): preserve literal library names during native matching 2026-10-09 21:01:52 +08:00
morluto 5eb0049a9f fix(process): retain known events from incomplete captures 2026-10-09 21:01:52 +08:00
morluto b7b4333703 fix(reference)!: represent unreadable symlink targets explicitly
BREAKING CHANGE: Historical source symlinks with unreadable or unknown target_state require target: null. Placeholder target strings are rejected.
2026-10-09 21:01:52 +08:00
morluto 0696bde266 fix(providers): preserve observations and primary cleanup failures 2026-10-09 21:01:52 +08:00
morluto 88640f60fe fix(browser): retain scenario resources until cleanup succeeds 2026-10-09 21:01:52 +08:00
YsopwnableandGitHub 636b245f1b fix(android): support owned JADX stdio on Windows (#1276)
Support owned JADX stdio on Windows x64, preserve protocol input errors through cancellation, and verify native stdin and Android CLI/MCP lifecycle on the real Windows host.

Closes #1262
2026-10-09 20:34:42 +08:00
morluto b71a591a60 test: consolidate redundant assertions into boundary workflows
Remove getter, enum, cloned-projection, cache-identity, and obsolete profile assertions along with the shadow JSON Schema engine and dead test-only APIs. Retain producer, filesystem, SDK, lifecycle, integrity, and malformed-format coverage.

Document the priority of real end-to-end workflows, boundary integration, and producer goldens.
2026-10-09 20:26:46 +08:00
morluto 2f02eff988 fix(managed): separate metadata and signature completeness 2026-10-09 20:18:52 +08:00
morluto 5510b27d7f refactor(domain)!: remove obsolete comparison representations
Compare complete function dossiers directly, remove unreachable truncation and collection wrappers, and preserve deterministic Unicode ordering. Require real external reference targets instead of display placeholders.

BREAKING CHANGE: function comparison results no longer advertise the unreachable truncated state or counter.
2026-10-09 20:18:52 +08:00
morluto 475a0f84da fix(javascript)!: preserve property paths and uncertain slot presence
Use one explicit object/array slot model and JSON-pointer property identity across mutation analysis, return projection, and semantic graphs. Require observed presence for discriminants and resolve writes only through retained object or array receivers.

BREAKING CHANGE: projected return fields require explicit presence and semantic object operations carry full property paths.
2026-10-09 20:18:52 +08:00
morluto 63bf91405c refactor!: enforce canonical evidence and provider boundaries
Deliver complete Evidence directly through MCP with required nullable analysis profiles and immutable per-server delivery settings. Bind selected environments at construction and require typed cleanup outcomes with retained retry ownership.

Remove test-only routing, duplicate session and snapshot metadata, producer-field repairs, and ambiguous provider arguments. Separate artifact byte identity from occurrence classification and retain typed source-map and partial native facts across CLI, MCP, and verifier consumers.

BREAKING CHANGE: obsolete Evidence envelopes, session aliases, incomplete snapshots, and missing normalized producer facts are no longer accepted.
2026-10-09 20:18:52 +08:00
morluto 2a7746998d fix(process)!: require current captures and preserve cleanup failures
Remove v3 capture conversion and require explicit executable identity and event journals. Preserve cancellation, execution diagnostics, snapshots, and cleanup outcomes from preparation through CLI delivery.
2026-10-09 20:12:07 +08:00
morluto abc4f47683 build: separate runtime artifacts and supervise command ownership 2026-10-09 20:11:17 +08:00
YsopwnableandGitHub c8544b1574 fix(process): preserve unknown filesystem absence in partial captures
Separate exhausted path enumeration from hash coverage and preserve known
effects outside incomplete roots or unfollowed symlink subtrees. Return
explicit unknown effects through capture Evidence and comparison workflows.

Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
2026-10-09 12:09:24 +00:00
YsopwnableandGitHub 4578647c59 Merge pull request #1272 from morluto/docs/website-calculator-continuation
docs(website): continue the Calculator example with reconstruction
2026-10-09 19:57:38 +08:00
morluto cc39fc53b8 feat(website): add Discord link beside GitHub badge 2026-10-09 19:54:34 +08:00
YsopwnableandGitHub 13c3b68730 Merge pull request #1270 from morluto/fix/batch-procedure-identity
fix(native): distinguish batch selectors from resolved entries
2026-10-09 19:39:48 +08:00
N0zoM1z0 18a1e8cd0e docs(website): continue the Calculator example with reconstruction 2026-10-09 19:38:17 +08:00
N0zoM1z0 933fcb33c1 test(native): assert batch identity in production registration 2026-10-09 19:27:27 +08:00
YsopwnableandGitHub f6fe575094 fix(process): await cleanup on CLI and MCP cancellation
Propagate CLI interrupts to capture and interrupt post-exit settlement
without cancelling owned-resource cleanup or discarding prior observations.

Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
2026-10-09 11:26:41 +00:00
N0zoM1z0 8342e3a78f chore: merge current main into batch identity fix 2026-10-09 19:13:13 +08:00
N0zoM1z0 bfaa5a6c2c fix(native): distinguish batch selectors from resolved entries 2026-10-09 19:09:30 +08:00
YsopwnableandGitHub b189c3ceb1 Merge pull request #1268 from morluto/fix/ghidra-return-flow
fix(ghidra): preserve decoded return flow evidence
2026-10-09 19:04:47 +08:00
N0zoM1z0 bcc8a773e2 fix(ghidra): preserve decoded return flow evidence 2026-10-09 18:53:28 +08:00
YsopwnableandGitHub 6cc817e8bd Merge pull request #1266 from morluto/feat/website-apk-showcase
feat(website): add an Android showcase and clearer project prompts
2026-10-09 18:46:52 +08:00
keemsisiandGitHub 9426c3f1bb perf(hopper): resolve string search records once per query (#1229)
* perf(hopper): resolve string search records once per query

search_strings called _string_inventory(document) once per match, and each
call sorts every string address and rebuilds the record dict, so a query cost
O(matches x strings log strings). Resolve the inventory once per search.
Warm search, 50% matches, fake document, best of 7: 1000 strings 186 -> 0.5 ms,
4000 strings 3161 -> 2.1 ms, 8000 strings 13121 -> 4.9 ms; output byte-identical.

* perf(hopper): skip record resolution for empty string searches

Resolving the full string inventory for a query with no matches added a sort
of every string address that the previous code never performed. Return early
when nothing matches. Zero-match search on a warm document, best of 7:
64000 strings 35.9 ms before this change, 4.0 ms after (main: 3.9 ms).
2026-10-09 18:39:19 +08:00
N0zoM1z0 5691606aa9 fix(website): clarify Android host and JDK requirements 2026-10-09 18:31:22 +08:00
45e3edc17f feat(javascript): report observed export property presence (#1160)
* feat(javascript): report observed export property presence

Extend compare_javascript_export_shapes so each retained return variant
lists observed property names and each change reports presence independently
of value resolution. Complete parent coverage classifies one-sided names as
added or removed even when static values stay unknown.

Fixes #725

* test(javascript): match unknown export-shape values by availability

Nested availability objects include a reason when values stay unresolved.
Assert availability through objectContaining so presence-only added and
removed rows still verify unknown values.

* fix(javascript): preserve uncertain export property presence and locations

* fix(javascript): normalize presence and preserve local property coverage

* fix(javascript): require present discriminants for return pairing

* fix(javascript): normalize retained shapes before comparison

* refactor(javascript): share return projection with executable examples

* fix(javascript): localize uncertainty for missing mutated slots

* fix(javascript): bind advertised export examples to exact artifacts

* fix(javascript): keep uncertain property flow as candidates

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-09 18:30:31 +08:00
YsopwnableandGitHub 04aef6df33 Merge pull request #1265 from morluto/fix/snapshot-receipt-counts
fix(snapshot): report query workflow and Evidence counts separately
2026-10-09 18:29:02 +08:00
YsopwnableandGitHub 552634d634 Merge pull request #1264 from morluto/perf/snapshot-materialization
perf(snapshot): avoid redundant result copies and serialization
2026-10-09 18:19:59 +08:00
N0zoM1z0 0e779533a9 fix(snapshot): report query workflow and Evidence counts separately 2026-10-09 18:13:50 +08:00
YsopwnableandGitHub c5c75c38c3 Merge pull request #1263 from morluto/fix/process-normalization-fidelity
fix(process): preserve numeric output and original PTY evidence
2026-10-09 18:07:22 +08:00
N0zoM1z0 569d2bc6b9 perf(snapshot): avoid redundant result copies and serialization 2026-10-09 18:07:21 +08:00
889fb374df fix(javascript): match source-map originals that name parent directories (#1215)
* fix(javascript): match source-map originals that name parent directories

* fix(javascript): resolve parent segments against the source map's directory

* fix(javascript): resolve source originals against their map location

* fix(javascript): preserve URI and empty source map path semantics

* fix(javascript): separate source URL paths from opaque URI payloads

* fix(javascript): preserve filesystem names during URI classification

* style(javascript): format source URI presence guard

* fix(javascript): resolve source map references with URL semantics

* fix(javascript): preserve literal UNC source map paths

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-09 18:04:25 +08:00
YsopwnableandGitHub 24a3aa08d1 fix(session): retain failed provider cleanup for lifecycle retries (#1260)
Retire inactive clients until cleanup succeeds and block replacement startup
while owned resources remain. Preserve startup/cancellation failures alongside
cleanup diagnostics and retain explicit document choices across retries.

Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
2026-10-09 17:56:17 +08:00
N0zoM1z0 00d8e44cbf fix(process): preserve numeric output and original PTY evidence 2026-10-09 17:55:28 +08:00
N0zoM1z0 3fb9ffaa4d docs(website): invite deeper projects and reader feedback 2026-10-09 17:48:59 +08:00
Kaoru0822andGitHub db0afce5f3 Merge pull request #1259 from morluto/fix/cli-json-input-read-cause
fix(cli): list the read failure cause for an unreadable JSON input file
2026-10-09 02:47:42 -07:00
Kaoru0822andGitHub 5612496f6d Merge pull request #1258 from morluto/fix/inspect-plist-default-remediation
fix(native): name the plist selection that works when Info.plist is missing
2026-10-09 02:47:30 -07:00
Kaoru0822andGitHub fe5291e916 Merge pull request #1239 from morluto/fix/javascript-input-selection-errors
fix(javascript): classify application input selection failures by cause
2026-10-09 02:47:09 -07:00
Kaoru0822andGitHub d4a598e936 Merge pull request #1256 from morluto/fix/evidence-file-selection-code
fix(errors): report caller-selected evidence path failures as invalid input
2026-10-09 02:46:04 -07:00
Kaoru0822andGitHub ddaa18301a Merge pull request #1240 from morluto/fix/web-script-export-capture-selection
fix(browser): classify export-web-scripts capture selection failures by cause
2026-10-09 02:45:47 -07:00
morluto d6832c3632 docs: require focused brittleness cleanup and executable examples 2026-10-09 17:41:14 +08:00
Kaoru0822andClaude Opus 5.5 9b6950373c fix(native): name the CLI flag that selects another plist
rea inspect-plist takes the target as its positional path and selects a
plist with --relative-path, so "Pass path" pointed CLI callers at the
wrong argument. Name the MCP path input and the CLI flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:32:24 +00:00
Kaoru0822andClaude Opus 5.5 6020df53d9 fix(cli): list the read failure cause for an unreadable JSON input file
A CLI workflow given a JSON input file that cannot be read (missing, a
directory, or permission denied) returned invalid_request with
input_reason "read-failed" but no details. The remediation "Correct the
listed arguments and retry." therefore listed nothing, and the system
error that separates a typo from a permission denial was dropped.

Add an invalid_value issue naming the system error code and the path.
The envelope, code and input_reason are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:30:15 +00:00
Kaoru0822andClaude Opus 5.5 a6fdf426bd fix(native): name the plist selection that works when Info.plist is missing
inspect_plist on a target without a readable default Contents/Info.plist
(for example a bare Mach-O) reported the generic "This analysis is
unavailable for the current target. Choose another analysis or target."
as message and remediation. The specific reason, including "Pass path to
inspect another plist", appeared only in details.reason, although
selecting another plist in the same target is the available workflow.

Pass that reason as the refusal's user message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:26:18 +00:00
Kaoru0822andClaude Opus 5.5 db177c91b8 fix(errors): report caller-selected evidence path failures as invalid input
An EvidenceFileError for a missing evidence file, a selected path that is
not a regular file, or an output that already exists projected code and
category execution_failure. The message already told the caller to
correct the path, but agents branching on code or category saw a tool
failure.

Project those three reasons as invalid_request with category
invalid_input. An unreadable file (io) and invalid JSON keep their
codes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:23:00 +00:00
Kaoru0822andClaude Opus 5.5 d186ca7e41 test(javascript): expect the invalid input_path code in the streamed CLI failure test
The acceptance test uses a missing input path only to produce a streamed
JSON failure with a nonzero exit. The missing path is now an invalid
input_path rather than an artifact io failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:20:16 +00:00
YsopwnableandGitHub aa5750c263 fix(mcp): emit portable Unicode NUL patterns (#1230)
Preserve NUL rejection while avoiding the short escape rejected by strict
schema consumers. Guard the entire advertised input/output catalog and all
three JavaScript regexp modes.

Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
2026-10-09 17:18:17 +08:00
Kaoru0822andClaude Opus 5.5 8c5e34cd37 fix(browser): classify export-web-scripts capture selection failures by cause
export_web_scripts reported a missing capture, a directory selected as
the capture, and an unreadable capture as an artifact io failure ("Script
export failed for <capture> -> <output>: ENOENT ..."), unlike its sibling
inspect_web_network_capture, which already reports these as invalid
input or access_denied.

Read the selected capture in its own step: ENOENT, ENOTDIR and EISDIR
become an invalid capture_path issue, and EACCES or EPERM becomes
access_denied. Failures while writing the export keep their existing
classification.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:16:59 +00:00
N0zoM1z0 0270dd5739 feat(website): add an Aegis APK code-generation showcase 2026-10-09 17:15:07 +08:00
Kaoru0822andClaude Opus 5.5 0a14bbbf05 fix(javascript): classify application input selection failures by cause
analyze_javascript_application reported every input-selection problem as
an artifact fault:

- a missing input path: "Artifact could not be read or written", with no
  path or reason;
- an unreadable selected directory: the same generic io failure;
- a regular file that is neither a directory nor an .asar, and a
  --artifact-format that contradicts the selection: "Artifact is invalid
  or has changed. Get a fresh copy and try again."

Report a missing path as an invalid input_path, an unreadable selection
as access_denied, a non-directory non-.asar file as unsupported_target
with the accepted inputs as remediation, and a contradicting format as an
invalid format with the observed kind as expected value. Failures inside
a selected artifact keep their existing classification.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:13:17 +00:00
N0zoM1z0 f6f5c16097 docs(website): introduce the worked homepage examples 2026-10-09 16:52:45 +08:00
Kaoru0822andGitHub f4c99ab8a5 Merge pull request #1228 from morluto/fix/keyed-archive-host-remediation
fix(artifacts): name only host-available workflows for non-archive plists
2026-10-09 01:48:51 -07:00
954badbaaa refactor(schemas): share portable identifier and base64 patterns (#1227)
* refactor(schemas): share identifier and canonical base64 patterns

* test: hold the offset datetime schema to the validator it replaces

The module documents that its accepted set matches z.string().datetime({ offset: true }), and nothing in the suite checked that claim. Adds table-driven accept and reject cases (leap years including the 1900/2000 century pair, month, day, hour and offset ranges, fractional and omitted seconds, missing offset, basic format) plus a differential assertion that classifies every case exactly as the replaced validator does, and asserts the emitted pattern compiles under u and v while the replaced pattern does not compile under v.

---------

Co-authored-by: felipeatp <felipe.troncosopt@gmail.com>
2026-10-09 16:47:09 +08:00
Kaoru0822andClaude Opus 5.5 22958558ea fix(artifacts): name only host-available workflows for non-archive plists
inspect_keyed_archive runs on every host, but its remediation for an
ordinary property list pointed to inspect_plist, which only macOS
supports. On Linux that sent callers to a workflow the capability layer
rejects.

The domain error now names what the file is. The artifact reader picks
the remediation from the provider's host: inspect_plist on macOS, and
elsewhere the selection of an NSKeyedArchiver archive, with a note that
inspect_plist requires macOS. decode_interface_builder, which is
portable, stays the NIBArchive remediation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:40:45 +00:00
Kaoru0822andGitHub fef38e4d22 Merge pull request #1226 from morluto/fix/keyed-archive-kind-refusal
fix(artifacts): report non-archive keyed-archive selections as unsupported targets
2026-10-09 01:37:29 -07:00
Kaoru0822andGitHub 136d4aab18 Merge pull request #1225 from morluto/fix/exec-stop-timeout-validation
fix(process): refuse malformed timeouts for stoppable commands
2026-10-09 01:33:25 -07:00
Kaoru0822andGitHub 68cb02fe8b Merge pull request #1224 from morluto/fix/adapter-host-refusal-messages
fix: name host and tool requirements in optional adapter refusals
2026-10-09 01:33:15 -07:00
Kaoru0822andClaude Opus 5.5 8c5088c8cd fix(artifacts): report non-archive keyed-archive selections as unsupported targets
inspect_keyed_archive reported a selected ordinary property list or a
compiled NIBArchive as a damaged artifact: "Artifact is invalid or has
changed. Get a fresh copy and try again." The file is intact; it is
another kind of file, and the workflow that reads it (inspect_plist or
decode_interface_builder) appeared only in details.detail.

Report a NIBArchive, or a plist whose $archiver is not NSKeyedArchiver,
as unsupported_target with that workflow as the remediation. An
NSKeyedArchiver archive that fails to decode stays a format failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:28:21 +00:00
Kaoru0822andClaude Opus 5.5 3d328193d6 fix(process): apply one timeout rule to both execFileOutput modes
Node's execFile timeout contract differs by supported runtime: Node 22
and 24 accept any non-negative integer (their timers clamp anything
above 2^31-1 to 1 ms), while Node 26 caps it at Number.MAX_SAFE_INTEGER.
Mirroring either left stoppable and plain commands disagreeing on one
runtime. Validate a safe non-negative integer before spawning in both
modes, so every supported runtime gives the same answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:22:09 +00:00
felipeatpandGitHub f0da05f67f fix: emit v-mode-compatible patterns for tool schemas (#1220)
* fix: emit v-mode-compatible patterns for tool schemas

Character classes in emitted patterns carried unescaped '-', '/' and '['. Annex-B and u-mode accept them, so REA's own Zod validation (u) and every u-based validator never saw a defect, but v-mode rejects ClassSetSyntaxCharacters and Ruby rejects [^s[]]. With 6.1.0 tools/list, 117 pattern fields across 11 tools were rejected under v.

The characters are escaped as \x2d, \x2f, \x5b and \x5d rather than \-/\//\[, because oxlint --fix removes an escaped '-' inside a character class as a useless escape, so an escape-only change does not survive the pre-commit hook. \x2d-style escapes are valid under Annex B, u and v, are matched identically, and are not useless-escape candidates.

22 pattern literals in 18 files are touched; no other token changes.

* fix: replace the Zod offset pattern and guard the advertised catalog

The offset datetime site projected Zod's offset class as [ +- ], which no escape in this repository's sources can reach, so evaluate_reconstruction_coverage stayed unreachable for exactly the clients this change targets. It now uses offsetDateTimeSchema in src/domain/offsetDateTime.ts: the same accepted set with the offset class escaped as [ +\x2d ].

Adds tests/boundary/mcp/advertisedPatternsVMode.test.ts, which performs the real tools/list projection, traverses every advertised input and output schema, compiles each pattern under v, and asserts the inventory it walked so a silent traversal collapse fails instead of passing vacuously.
2026-10-09 16:20:57 +08:00
Jay HemnaniandGitHub 01174c4a81 fix(javascript): report located sources with differing digests as modified (#1216) 2026-10-09 16:20:42 +08:00
Kaoru0822andClaude Opus 5.5 6489d58851 fix(javascript): keep Wakaru version stderr out of the refusal message
The version-mismatch reason ends with the tool's complete stderr, which
is too long for a caller-facing message. The message now names the
expected and reported version and exit code; details.reason keeps the
stderr.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:16:45 +00:00
Kaoru0822andClaude Opus 5.5 59c91c88fe fix(process): refuse malformed timeouts for stoppable commands
Since a stoppable command keeps its own deadline, execFile no longer
validated its timeout. A negative, NaN, fractional or infinite value
then started the command with no deadline (or one setTimeout coerced)
instead of failing with ERR_OUT_OF_RANGE as execFile does.

Apply execFile's unsigned-integer contract before spawning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:15:03 +00:00
c89b3a00dc test(artifacts): read /bin/ls slice layout from its FAT table (#1223)
The real-lipo slice test pinned the byte offsets and sizes of one macOS
release's /bin/ls, so it failed on any host whose /bin/ls differs
(macOS 26.7.1: x86_64 48128 bytes, arm64e 88672, versus the pinned 48112
and 89088). Read the expected layout from the binary's FAT header
instead.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:12:27 +08:00
Kaoru0822andClaude Opus 5.5 170d0bf9e2 fix: name host and tool requirements in optional adapter refusals
Host and tool-configuration refusals from the pwntools, EVMole, firmware,
mitmproxy, Wakaru, LLDB and xcrun adapters carried no userMessage, so CLI
and MCP callers saw the generic "This analysis is unavailable for the
current target. Choose another analysis or target." as both message and
remediation. Choosing another target cannot satisfy a Linux-only profile
or an unset REA_*_COMMAND. The actual requirement appeared only in
details.reason.

Pass each refusal's reason as its userMessage, as the adapters' other
configuration refusals (EVMole limits, JADX, pwndbg) already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:12:25 +00:00
6935dc7fe1 fix(ghidra): classify modern Swift manglings in swift type analysis (#1208)
* fix(ghidra): classify modern Swift manglings in swift type analysis

Categorize modern Swift 4.2+ manglings ($s, _$s) instead of dropping them
in analyze_swift_types. The classifier only accepted the legacy _Tt marker,
so procedure names reported by current providers produced an empty inventory.
Legacy _Tt families and behavior are unchanged. Fixes #1187.

* fix(swift): retain provider aliases and category unknowns

---------

Co-authored-by: Saim Shafique <sx4imm@gmail.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-09 16:10:55 +08:00
Kaoru0822andGitHub 04a516b1e3 Merge pull request #1222 from morluto/fix/ghidra-relative-java-probe
fix(ghidra): do not run Java from a relative JAVA_HOME in doctor and setup
2026-10-09 01:04:53 -07:00
Kaoru0822andGitHub 4b38d18995 Merge pull request #1221 from morluto/fix/exec-stop-signal-timeout
fix(process): stop timed-out commands with the caller's stop signal
2026-10-09 01:04:21 -07:00
Kaoru0822andClaude Opus 5.5 c3f169823f fix(ghidra): do not run Java from a relative JAVA_HOME in doctor and setup
The installation inspection rejected a relative JAVA_HOME, but only after
it had already probed `<JAVA_HOME>/bin/java` with spawnSync. `rea doctor`
and `rea setup` therefore ran whatever `jdk/bin/java` (or `bin/java` for
an empty JAVA_HOME) the current directory held, while reporting the
setting as rejected.

Skip the probe when JAVA_HOME is not absolute on the inspected platform.
The configuration check still reports the rejection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 07:59:38 +00:00
Kaoru0822andClaude Opus 5.5 063de5014f fix(process): stop timed-out commands with the caller's stop signal
execFileOutput forwarded stopSignal only together with an AbortSignal, so
a caller with just a timeout (the native UI helper's swiftc build) still
got Node's SIGTERM. When the timeout did send the stop signal, nothing
killed a process that ignored it, and a process that exited cleanly
after the signal resolved as a success.

A stoppable command now owns its timeout: it sends the stop signal,
kills the process after the grace period, and rejects with ETIMEDOUT
and killed: true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 07:57:14 +00:00
morluto e6905c18e0 fix(website): identify publication verifier requests 2026-10-09 15:46:20 +08:00
YsopwnableandGitHub 9405bd660d fix(javascript): allow cancellation during semantic graph commitment (#1218)
Move whole-graph commitment and batched integrity checks into owned domain steps while retaining eager input capture, canonical IDs, full validation and completion-only immutable proofs.

Verified complete real Notion identity parity and actual stdio MCP cancellation, Evidence retention and connection reuse.

Refs #1065.
2026-10-09 15:15:17 +08:00
27a9ceade7 fix!: restore caching, preserve failures, and verify fixture claims (#1211)
Restore profile-bound Ghidra snapshot reuse, preserve direct-analysis results through cleanup failures, and grade configured agent fixture claims against returned evidence.

Register the compiled agent-evaluation fixture entry for static analysis.

Co-authored-by: YouWish <29498139+Y0UW1SH@users.noreply.github.com>
Co-authored-by: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com>
2026-10-09 14:49:09 +08:00
YsopwnableandGitHub c399670653 docs: keep the 30k milestone in Star History (#1217) 2026-10-09 14:45:59 +08:00
YsopwnableandGitHub 863674b2e9 feat(website): add homepage navigation and faster reading paths (#1213)
feat(website): add homepage navigation and faster reading paths
2026-10-09 14:33:47 +08:00
YsopwnableandGitHub c2d8146f94 docs: clarify REA has no affiliated cryptocurrency or token (#1214) 2026-10-09 14:25:32 +08:00
N0zoM1z0 c94f06d98f feat(website): add homepage navigation and faster reading paths 2026-10-09 14:03:00 +08:00
Kaoru0822andGitHub 5b769d3a78 Merge pull request #1201 from morluto/fix/ghidra-runtime-cleanup-retry
fix(ghidra): keep and name a runtime directory that close could not remove
2026-10-08 22:49:12 -07:00
Kaoru0822andGitHub 6987cb066a Merge pull request #1207 from morluto/docs/celebrate-30000-stars
docs: celebrate 30,000 GitHub stars 🎉
2026-10-08 22:48:54 -07:00
YsopwnableandGitHub 7e9c91199e fix(cli): resolve xrefs symbol selectors after address validation (#1209)
* fix(cli): resolve xrefs symbol selectors after address validation

* style(cli): format xrefs fallback call
2026-10-09 13:28:48 +08:00
N0zoM1z0 9db1fd0bd7 docs: thank feature request contributors 2026-10-09 13:02:26 +08:00
YsopwnableandGitHub 796b65e1cd ci(website): allow explicitly requested Pages-only publication (#1205)
ci(website): allow explicitly requested Pages-only publication
2026-10-09 13:00:26 +08:00
N0zoM1z0 beca9977eb docs: celebrate 30,000 GitHub stars 🎉 2026-10-09 12:58:16 +08:00
YsopwnableandGitHub 6051cb2c0c fix(setup): preserve initial backups and Codex comments (#1204) 2026-10-09 12:57:00 +08:00
N0zoM1z0 864160d885 ci(website): allow explicitly requested Pages-only publication 2026-10-09 12:46:17 +08:00
db675f1d52 feat(mcp): select views of retained analysis results (#1180)
Add explicit summary, facet, item and paged views of retained analysis Evidence without repeating analysis. Preserve captured observations, graph validation, artifact identity, coverage and complete default results.

Fixes #1050

Verified with focused source, CLI, pinned-SDK schema and MCP tests, serial static checks, and one complete native Windows Notion ASAR workflow. Real Hopper/Ghidra verification was not performed for this provider-independent projection change.

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-10-09 12:45:48 +08:00
911152a04d fix(cli): complete cleanup after relayed interrupts (#1202)
* fix: Handle relayed SIGINT gracefully in withProcessCancellation

Changes `process.once` to `process.on` for SIGINT in `DirectAnalysis.ts`.
When REA is launched via npx, a single process-group SIGINT triggers two
SIGINT events (one direct, one relayed). Previously, the first event would
consume the handler and the second would abruptly terminate the Node process,
abandoning Ghidra resource cleanup.

This change ensures subsequent SIGINTs idempotently request cancellation
without crashing the process, matching the behaviour of `withCommandCancellation`.

* fix(cli): keep termination guards through provider cleanup

* test(cli): exercise repeated analysis signals on macOS

---------

Co-authored-by: N0zoM1z0 <1705004398@qq.com>
2026-10-09 12:45:23 +08:00
YsopwnableandGitHub 0fec65d06b feat(website): add beginner examples and synchronized publishing (#1203)
feat(website): add beginner examples and synchronized publishing
2026-10-09 12:41:51 +08:00
Kaoru0822andGitHub cf415619ca Merge pull request #1194 from morluto/fix/darwin-helper-compile-tmpdir
fix(process): stop cancelled Swift helper builds before removing their files
2026-10-08 21:21:32 -07:00
Kaoru0822andGitHub cfb49eb8a8 Merge pull request #1198 from morluto/fix/ghidra-extension-unsupported-host
fix(ghidra): report NativeAOT host and target refusals as unsupported, not failures
2026-10-08 21:21:18 -07:00
Kaoru0822andGitHub 8998438c23 Merge pull request #1200 from morluto/fix/doctor-setup-relative-provider-paths
fix(ghidra): reject relative installation and Java paths in doctor and setup
2026-10-08 21:21:04 -07:00
Kaoru0822andGitHub c3acc34475 Merge pull request #1199 from morluto/fix/config-error-names-setting
fix(config): name the rejected setting and its constraint in configuration errors
2026-10-08 21:20:48 -07:00
N0zoM1z0 761881cec3 ci(website): publish Cloudflare and Pages from one artifact 2026-10-09 12:19:38 +08:00
Kaoru0822andClaude Opus 5.5 b79d379c25 fix(ghidra): keep and name a runtime directory that close could not remove
When removing Ghidra's private runtime directory failed, close_binary
reported only a generic `provider-client` resource, and a later close,
after the permission was restored, returned success while the directory
and its contents remained. The client dropped its reference to the root
before removing it, and PrivateRuntimeRoot cached the rejected removal.

Keep each runtime or socket root until its removal succeeds, report a
failure as a ProviderCleanupError naming the leftover paths, let
PrivateRuntimeRoot retry a failed removal, and pass such typed cleanup
failures through closeAnalysisClient instead of replacing them.

Fixes #1190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 04:19:10 +00:00
YsopwnableandGitHub 6fbdbd6820 fix(ghidra): resolve secondary symbols at function entries (#1195)
* fix(ghidra): resolve secondary symbols at function entries

* test(ghidra): derive interior bytes from decoded instructions
2026-10-09 12:19:07 +08:00
Kaoru0822andClaude Opus 5.5 53568568bf fix(ghidra): reject relative installation and Java paths in doctor and setup
A relative GHIDRA_INSTALL_DIR (or JAVA_HOME) that resolved from the current
directory passed doctor's Ghidra inspection, so doctor reported a healthy
provider and setup wrote the relative path into the MCP registration, but
REA's configuration parser rejects relative paths, so `providers`,
`capabilities` and the registered MCP server all failed.

The installation inspection's configuration check now applies the same
absolute-path rule, judged by the inspected platform's path syntax, and
names the relative setting. Doctor reports the provider unavailable with
that reason, and setup, which registers only an available inspection's
environment, no longer writes the path.

Fixes #1183

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 04:13:40 +00:00
Kaoru0822andGitHub f36c10f9e8 Merge pull request #1197 from morluto/fix/target-admission-access-denied
fix(domain): report target read-permission denials as access_denied
2026-10-08 21:13:32 -07:00
Kaoru0822andGitHub d39a450bdf Merge pull request #1196 from morluto/fix/target-truncated-headers
fix(native): refuse truncated executable headers at target admission
2026-10-08 21:13:15 -07:00
Kaoru0822andGitHub 98b07d07dd Merge pull request #1182 from morluto/fix/doctor-hopper-discovery-readonly
fix(doctor): find the Homebrew Hopper cask without writing Homebrew caches
2026-10-08 21:13:00 -07:00
Kaoru0822andClaude Opus 5.5 f198993f41 fix(ghidra): keep the NativeAOT target refusal's own recovery advice
An unsupported-target error always projected the generic "select a target"
remediation, which hid the adapter's available workflows (omit
REA_GHIDRA_NATIVEAOT_JAR for ordinary analysis, or use
inspect_managed_artifact). AnalysisUnsupportedTargetError accepts a
refusal-specific remediation, as AnalysisResourceConstraintError does, and
the NativeAOT refusal supplies its reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 04:09:50 +00:00
N0zoM1z0 54c0ed42aa chore: merge main before website publication 2026-10-09 12:09:29 +08:00
Kaoru0822andClaude Opus 5.5 0a0244db42 fix(config): name the rejected setting and its constraint in configuration errors
Every configuration failure projected the same message, "REA configuration
is invalid. Run `rea doctor` and fix the reported setting.", with no
details. The environment parser discarded the issue that names the variable
(for example `REA_GHIDRA_NATIVEAOT_JAR must be absolute`), and doctor can
report healthy for that environment, so neither the CLI, the MCP startup
message, nor doctor told callers which setting to correct.

ConfigurationError now carries the rejected settings and their constraints
(never their values). The projection names them in its message, lists them
in `details.settings`, and asks the caller to correct those settings where
REA is started. Errors without a named setting keep the previous message.

Fixes #1184

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 04:08:43 +00:00
Kaoru0822andClaude Opus 5.5 9f5dc52be8 fix(hopper): keep the OS error when the FAT64 source cannot be reopened
resolveHopperMachOImage built its BinaryTargetError without the open
failure as its cause, so a target that became unreadable after admission
still projected as `target_unavailable` instead of `access_denied`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 04:04:42 +00:00
Kaoru0822andClaude Opus 5.5 42c276af1e fix(ghidra): report NativeAOT host and target refusals as unsupported, not failures
With REA_GHIDRA_NATIVEAOT_JAR set on a host other than Linux, Ghidra
analysis was refused before Ghidra started, as intended, but the refusal
was a ProviderAdapterError: `execution_failure` with advice to retry and
run doctor, neither of which can change the host.

Adapters now say whether a refusal concerns the host or the target. A host
refusal is `capability_unavailable` whose message and remediation are the
adapter's reason (omit the JAR for ordinary analysis); a target refusal is
`unsupported_target` with the target path. Missing or malformed JARs on a
supported host remain provider failures.

Fixes #1185

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 04:03:10 +00:00
N0zoM1z0 291bc6fb70 feat(website): show manual analysis beside a one-prompt workflow 2026-10-09 12:02:58 +08:00
Kaoru0822andClaude Opus 5.5 facaae865a test(native): build the .app fixture executable with a complete Mach-O header
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:59:19 +00:00
Kaoru0822andClaude Opus 5.5 d0ff57a45c fix(domain): report target read-permission denials as access_denied
Opening a target the current user cannot read returned
`target_unavailable`, the same code as a missing target, although the
reason named EACCES. Clients that branch on the error code could not choose
read-access recovery, contrary to the boundary contract that separates host
permission denial from unavailable targets.

A BinaryTargetError whose cause is an OS EACCES or EPERM now records that
system code and projects as `access_denied` with `system_code`, the
filesystem-read boundary, and read-access remediation, while keeping its
specific reason and path. This covers target files, app bundle paths and
Info.plist reads, and selected plist reads, since every site passes the OS
error as the cause.

Fixes #1188

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:56:38 +00:00
Kaoru0822andClaude Opus 5.5 0b04d384cc fix(native): refuse truncated executable headers at target admission
Target admission accepted a thin Mach-O after reading only its magic and
CPU type, so a 12-byte prefix opened as a valid arm64 executable and the
first provider query failed as a generic execution failure. ELF headers
were likewise accepted after 20 bytes, and a FAT file whose selected slice
lies beyond its end was admitted.

Require the complete fixed header (Mach-O 28 or 32 bytes, ELF 52 or 64),
Mach-O load commands that fit in the file, and a selected FAT slice within
the file, checking commitments against the file size rather than the
probed prefix. Each refusal names the failed constraint.

Fixes #1189

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:51:37 +00:00
Kaoru0822andGitHub e6abaefbbc Merge pull request #1179 from morluto/fix/session-client-metadata-2025
fix(server): report 2025-era client identity and features in binary_session
2026-10-08 20:51:34 -07:00
Kaoru0822andGitHub f5539cfc2f Merge pull request #1178 from morluto/fix/codex-toml-preserve-comments
fix(setup): keep Codex TOML comments when adding or removing REA
2026-10-08 20:51:15 -07:00
2a620c06cd fix(javascript): resolve package exports targets only to the exact file (#1181)
A selected package exports target names one file, and Node tries no
extension or directory index for it. REA still resolved it like a relative
specifier, so `exports: "./file"` with only `file.js` present, or
`"./folder"` with only `folder/index.js`, reported a resolved import that
Node rejects under both import and require.

Resolve an exports target only to its exact file and otherwise report
not-found with a limitation naming the target. Legacy main entries keep
their extension and index fallbacks.

Fixes #1174

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:49:32 +08:00
N0zoM1z0 e750c262fb feat(website): clarify reverse engineering and showcase goals 2026-10-09 11:46:06 +08:00
Kaoru0822andClaude Opus 5.5 c717204795 fix(process): stop cancelled Swift helper builds before removing their files
Cancelling the macOS process-ownership helper build left swiftc object files
in TMPDIR. Node's execFile sends SIGTERM on abort and rejects before the
child exits, and swift-driver runs each frontend job in its own process
group: on SIGTERM the driver exits while a job keeps writing, so REA removed
its root while objects were still being written outside and inside it.

Add an opt-in `stopSignal` to execFileOutput that stops the process with the
caller's signal on abort or timeout and settles only after it exits. Swift
helper builds stop swiftc with SIGINT, on which swift-driver stops its jobs
and deletes their objects, and point TMPDIR inside the root cleanup removes.

Fixes #1173

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:43:43 +00:00
YsopwnableandGitHub e1afbc1914 perf(mcp): reuse input schemas without losing caller guidance (#1162) 2026-10-09 11:38:07 +08:00
Kaoru0822andClaude Opus 5.5 a503376b2f fix(doctor): find the Homebrew Hopper cask without writing Homebrew caches
`rea doctor`, documented as strictly read-only, ran
`brew --prefix --cask hopper-disassembler` when Hopper was not in a
standard location. Current Homebrew rejects that option combination, but
any brew command naming a cask first fetches the cask API, so each doctor
run wrote about 600 files (36 MB) into the user's Homebrew cache.

Ask brew only for its Caskroom, which brew answers without loading the
cask API, and read the application directory Homebrew recorded in the
Hopper cask's install receipt.

Fixes #1165

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:28:25 +00:00
Kaoru0822andGitHub 4fb2f3e6ed Merge pull request #1177 from morluto/fix/uninstall-preflight-client-config
fix(setup): stop uninstall before removals when a client config is unsafe
2026-10-08 20:22:34 -07:00
Kaoru0822andGitHub d8e314cb24 Merge pull request #1176 from morluto/fix/session-availability-active-target
fix(session): require an open target for session-bound native and artifact tools
2026-10-08 20:22:23 -07:00
N0zoM1z0 19c52f5e3f feat(website): lead with the playable dinosaur example 2026-10-09 11:21:18 +08:00
Kaoru0822andClaude Opus 5.5 b8312fa8d1 fix(server): report 2025-era client identity and features in binary_session
binary_session read the client's identity, protocol revision, and
capabilities only from the 2026-era per-request envelope. A 2025-era
connection sends none, so every such client was reported as `client: null`
with no negotiated protocol and no features, although it declared them in
`initialize`.

When a request carries no envelope, read the values the SDK keeps from the
connection's initialize handshake. An envelope still takes precedence.

Fixes #1167

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:20:43 +00:00
Kaoru0822andClaude Opus 5.5 fd975c75fa fix(setup): stop remaining uninstall removals after a client fails
A configuration that passes the preflight can still fail when uninstall
rereads it to edit, for example when it changes concurrently. Rereading
keeps that edit from overwriting the newer file; stopping at the failure
keeps later clients, the skill, and REA data from being removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:17:11 +00:00
Kaoru0822andClaude Opus 5.5 e9c48a61e4 fix(setup): keep Codex TOML comments when adding or removing REA
Setup and uninstall rewrote the whole Codex config.toml with a fresh TOML
serializer, which dropped every comment and respelled unrelated values,
although the plan promises to preserve unrelated configuration. Grok Build
already edited only REA's server tables.

Share that table editor with Codex, without Grok's root
disabled_mcp_servers handling, keep the original final line break, and
fall back to the full rewrite only when the edited text would not parse to
the requested document.

Fixes #1163

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:15:18 +00:00
b4927a0a4f fix(cli): advertise defaulted inputs as optional in --schema and help (#1175)
Incur derives `--schema`, the `--llms` manifests, and help synopses from
Zod's output view, where a defaulted field always has a value, so every
defaulted argument and option was listed as required even though the CLI
accepts its omission. Wrap each field that fills an omitted value as
optional after registration, keeping its default, constraints, metadata,
and parsed value.

Fixes #1169

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:11:11 +08:00
Kaoru0822andClaude Opus 5.5 5cc841d5c1 fix(setup): stop uninstall before removals when a client config is unsafe
The installation guide says malformed client configuration stops
`rea uninstall`, but uninstall reported that client as failed and still
removed other clients' registrations, the bundled skill, and with
--purge-data REA's cache and state.

Inspect every client configuration first, using the conditions setup
already treats as blocking (unsafe path, unreadable file, malformed
document), and stop before any removal when one fails. Purge paths that are
symbolic links are still retained and reported per item, as the guide now
states.

Fixes #1164

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:07:40 +00:00
N0zoM1z0 11b9f0346c feat(website): explain REA with Calculator and a playable dinosaur lab 2026-10-09 11:06:05 +08:00
Kaoru0822andClaude Opus 5.5 cf77d073fc fix(session): require an open target for session-bound native and artifact tools
binary_session reported native and artifact operations available whenever
their provider declared them, although these operations run on the active
target: in a closed session every call failed with target_unavailable, and
Mach-O-only operations stayed available with a ZIP or ELF target open.

Report target_required for every session-bound contract kind in a closed
session, and target_unsupported for Mach-O-only operations unless the open
target is a Mach-O executable. A host limitation is reported before target
selection, since opening a target cannot fix it; composed operations keep
deciding from their requirements.

Fixes #1166

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:02:40 +00:00
Kaoru0822andGitHub de5bfec79f Merge pull request #1172 from morluto/fix/macho-arm64e-variant-slices
fix(artifacts): match lipo arm64e variant names to their FAT slices
2026-10-08 19:42:05 -07:00
Kaoru0822andGitHub 37025cc09f Merge pull request #1171 from morluto/fix/extraction-preflight-cancellation
fix(artifacts): keep cancellation and the caller path in extraction refusals
2026-10-08 19:38:56 -07:00
Kaoru0822andClaude Opus 5.5 0b32577a90 fix(artifacts): match lipo arm64e variant names to their FAT slices
Newer lipo names arm64e ABI variants `arm64e.<variant>`: `arm64e.v1` for
pointer-auth ABI version 1 on freshly built universal binaries, and
`arm64e.x1` on recent macOS system binaries such as /bin/ls. The
structural FAT parser names the same slice `arm64e`, so the exact name
comparison failed and inspect_artifact reported a valid, unmodified
universal binary as an integrity mismatch ("Artifact is invalid or has
changed").

Accept `arm64e.<variant>` for an `arm64e` FAT slice. Offset, size, CPU
type, subtype (including the pointer-auth version bits when lipo prints
them) and alignment are still compared field by field. The slice keeps
lipo's name in its logical path.

Fixes #1168

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 02:37:03 +00:00
Kaoru0822andClaude Opus 5.5 eacf8461da fix(artifacts): keep cancellation and the caller path in extraction refusals
Two follow-ups to #1161 from its post-merge review:

- The new format preflight ran before scanArtifactInventory, which was
  the first cancellation check. An already-cancelled extraction of a
  DMG or PKG therefore reported unsupported_target instead of
  cancellation. Check the signal before the preflight.
- The unsupported-format and encrypted-entry refusals reported the
  realpath. When the caller selected the target through a symlink,
  junction, or other alias, the diagnostic named a different path.
  Report the caller's input path; I/O still uses the canonical path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 02:33:16 +00:00
Kaoru0822andGitHub b34bdcdb28 Merge pull request #1161 from morluto/fix/artifact-unsupported-target-errors
fix(artifacts): report wrong target kinds as unsupported targets
2026-10-08 19:30:54 -07:00
Kaoru0822andClaude Opus 5.5 de43da71ae fix(artifacts): report encrypted archive entries as unsupported extraction
extract_artifact on a ZIP containing a password-protected entry failed
the whole extraction with "Artifact is invalid or has changed. Get a
fresh copy" and named only an opaque occurrence ID. The archive was
intact; REA does not decrypt entries, so complete extraction is
unsupported for this target.

Raise AnalysisUnsupportedTargetError naming the encrypted entry's path.
Other unextractable occurrences keep their format failure but now name
the logical path alongside the occurrence ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 02:23:44 +00:00
Kaoru0822andGitHub 6a1e558db3 Merge pull request #1159 from morluto/fix/dylib-resolution-root-input-errors
fix(apple): report unusable dylib-resolution roots as invalid input
2026-10-08 19:23:22 -07:00
Kaoru0822andClaude Opus 5.5 8e74c9c62f fix(artifacts): refuse unsupported extraction formats before scanning
extract_artifact on a format without an extraction reader, such as a
DMG, first ran the full inventory scan (mounting the image) and then
failed with an `unavailable` artifact failure: "Check artifact support
and required tools". The target format, not the host, is unsupported.

Check the format before any inventory or output work and raise
AnalysisUnsupportedTargetError, which the provider now keeps, as it does
for the other target-kind checks in this PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 02:21:22 +00:00
Kaoru0822andClaude Opus 5.5 44d5b4b25e test(artifacts): assert asset-catalog target refusal only on macOS
Outside Darwin the session reports inspect_asset_catalog as unavailable
on the host before the provider checks the target kind, so the Linux
test shards would see capability_unavailable rather than
unsupported_target.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 02:21:21 +00:00
Kaoru0822andClaude Opus 5.5 637bcb5e41 fix(artifacts): report wrong target kinds as unsupported targets
decode_interface_builder, inspect_asset_catalog, inspect_keyed_archive
and trace_dylib_resolution refused a target of the wrong kind (for
example a plain Mach-O for the asset catalog, or a plist for dyld
tracing) as an `unavailable` artifact failure: "Artifact processing is
unavailable for the current target or host. Check artifact support and
required tools." Nothing was missing on the host; the operation does not
support that kind of target.

Raise AnalysisUnsupportedTargetError for these checks and let
translateFailure keep it. The result is now `unsupported_target`, naming
the operation, the target path and the requirement, with advice to pick
a supported target or another operation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 02:16:57 +00:00
YsopwnableandGitHub 33d5ae0b52 fix(hopper): reserve Linux demo application before launch (#1158)
* test(hopper): give locals oracle explicit stack storage

* fix(hopper): reserve Linux demo application before launch

* fix(hopper): serialize stale lease recovery
2026-10-09 10:14:25 +08:00
Kaoru0822andClaude Opus 5.5 24a30fd91c fix(apple): report unusable dylib-resolution roots as invalid input
trace_dylib_resolution blamed the artifact for the caller's root
selection. A missing root, a directory, or a symlink leading outside the
bundle got "Artifact contains an unsafe or conflicting internal path ...
correct the artifact". A non-Mach-O file such as Contents/Info.plist got
"Artifact is invalid or has changed. Get a fresh copy". Any schema
failure, including a bad architecture, was reported as "roots must be
normalized paths".

Report a caller-selected root that cannot be traced as an
invalid_request issue on roots[i] with the failed constraint, and
project schema failures through their real issues. The normalized-path
rule now carries a readable message. Roots REA derives itself (bundle
executables or the active target) keep reporting artifact failures.
This matches what #1070 did for inspect_keyed_archive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 02:12:58 +00:00
b620e2a2bd fix(javascript): omit self-referential static references with disclosure (#1157)
* fix(javascript): omit self-referential static references with disclosure

A bundled module that requires its own key (`r(50)` inside webpack
module 50) resolved, through the static-reference composer, to its own
module node. The resulting self-referential imports edge failed result
validation and lost the entire analysis ("Result schema rejected 1
issue ... (custom)"). This is the same invariant as #1146; #1152 fixed
the bundler async-chunk composer, and this is the remaining adjacent
composer.

Skip a reference whose resolved target is its own source, count it, and
disclose the omission the way #1152 discloses async-chunk references.
Sibling requires are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(javascript): share the self-reference omission disclosure

The self-import (#1130), async-chunk (#1152) and static-reference
disclosures repeated one sentence template in three composers, and the
new test repeated the analyze-and-check sequence of its neighbour. That
put jscpd at its 2.5% threshold. Share the template through
selfReferenceOmissions with identical wording, and share one test helper
for the graph-wide self-reference invariants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:01:59 +08:00
58a91f03d7 fix(artifacts): recheck failed DMG detaches before reporting cleanup failure (#1155)
* fix(artifacts): recheck failed DMG detaches before reporting cleanup failure

NativeDmgArtifactReader.close() detached each device once and, when a
detach failed, asked `hdiutil info` once whether the device was still
attached. On APFS images, detaching the synthesized container ejects the
image disk too. `hdiutil info` can still list that ejected device
briefly, and a just-read volume can report busy. Either way a real
cleanup succeeded but close() reported "DMG detach or mount-root cleanup
failed" and kept the mount root.

macOS CI hit this intermittently in verify:macos-bundle: `hdiutil
detach /dev/disk23` failed with "No such file or directory" (already
ejected) and the device was still counted as attached. The #1103
review also raised the missing recheck.

After the first pass, recheck the remaining devices up to three times
with 250/500/1000 ms settle delays. Drop any that hdiutil no longer
lists, retry the detach of any still listed, and fail only for devices
that stay attached. The host seam gains an optional delay so tests run
without real waits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(artifacts): share DMG detach fixtures to stay under the duplication gate

The new busy-device and recheck cases copied the fake hdiutil host and
the APFS close sequence from neighbouring tests, which pushed jscpd to
its 2.5% threshold in Static checks. Share one single-device host
between the busy and retained-attachment cases and one APFS close
helper between the ejected-device cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:01:52 +08:00
N0zoM1z0 5160d9b717 feat(website): explain REA through a first investigation 2026-10-09 10:00:22 +08:00
089bbf58f3 fix(cli): cancel routed JavaScript analysis in rea analyze (#1156)
`rea analyze PATH` routes a JavaScript directory or ASAR to the same
static analysis as `rea analyze-javascript-application`, but it passed
no signal and was not wrapped in withCommandCancellation. On SIGINT the
process was killed with no output (signal SIGINT, empty stdout).
The dedicated command returns a typed `cancelled` result and exits 130.
The #1098 review raised this route.

Decide the route first. A JavaScript target now runs exactly like the
dedicated command: cancellation wraps logging, so the cancelled exit
code is not replaced by the logged failure status. The binary route is
unchanged and keeps its own SIGINT handling in runDirectAnalysis.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:48:10 +08:00
YsopwnableandGitHub 72201668ed fix(javascript): preserve graphs with async chunk self-references (#1152) 2026-10-09 09:37:37 +08:00
Kaoru0822andGitHub 84094af54a Merge pull request #1151 from morluto/docs/readme-french
docs: add French README translation
2026-10-08 18:34:31 -07:00
YsopwnableandGitHub 16624f1324 fix(evidence): cancel exports before atomic publication 2026-10-09 09:29:22 +08:00
Kaoru0822andClaude Opus 5.5 9664b2b101 docs: add French README translation
Adds README_fr.md, translated from the current English README with the
same commands, links, heading structure and FAQ entries. Links Français
from every README language selector and registers it in the README
consistency matrix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 01:21:53 +00:00
morluto 2609352eef chore(release): preserve concurrent main updates 2026-10-09 09:17:44 +08:00
morluto 90f532feb1 chore(release): sync published 6.1.0 checkpoint 2026-10-09 09:16:40 +08:00
YsopwnableandGitHub e9ebfb94ef fix(android): validate engine archive prerequisites (#1145)
* fix(android): validate engine archive prerequisites

* fix(android): bound engine metadata reads before allocation
2026-10-09 09:15:33 +08:00
Andrew BarnesandGitHub 13935077ba fix(browser): report Windows HAR heap exhaustion (#1147)
Recognize exit code 134 without a signal when the HAR decoder reports
V8 heap exhaustion. Preserve the observed termination in the memory
failure and cover Windows, POSIX, and unrelated decoder failures.
2026-10-09 09:13:00 +08:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ae9aaee16b chore(release/6.1.0): release rea-agents 6.1.0 (#1144)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-10-09 08:51:58 +08:00
morluto cdddfddf49 fix(release): publish reviewed release PR merges 2026-10-09 08:47:00 +08:00
YsopwnableandGitHub 441813c070 perf(mcp): share repeated output schema definitions 2026-10-09 08:46:59 +08:00
morluto c12bad7f13 fix(release): publish reviewed release PR merges 2026-10-09 08:44:47 +08:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>morluto
70e5c24718 chore(main): release rea-agents 6.1.0 (#1106)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-09 08:15:17 +08:00
morluto 283c82f68c revert(test): remove duplicated application workflow acceptance tests
Commit 5ac3dc96 re-added application workflow scenarios under tests/acceptance/applications that are already covered under tests/boundary (mcp, filesystem, process) via the shared tests/fixtures/applicationMcpHarness.js fixture, per the boundary classification in 0e0b386d. The duplicates added no new coverage and pushed the jscpd duplication gate over its 2.5% threshold, failing CI static checks.
2026-10-09 08:03:13 +08:00
morluto c29d85a7db chore: clarify local Cloudflare ignore comment and wrap website deploy note 2026-10-09 08:00:11 +08:00
morluto b7d803dd10 fix(cli): drive mcp doctor help from a single option schema
Parses rea mcp doctor arguments against one zod option schema that is also the help renderer's source of truth, answering --help/-h in any position without starting a diagnostic session and keeping actionable errors for unknown options and invalid formats. Updates the landed help assertion to the schema-rendered format list and adds boundary coverage for help positioning and --format edge cases.
2026-10-09 08:00:07 +08:00
morluto 5ac3dc96f0 test(acceptance): add application workflow MCP parity harness and scenarios
Adds an owned in-memory MCP harness plus acceptance coverage for trace/compare/export-shape workflows over inline and recorded Evidence, including rejection of mismatched evidence references and spoofed providers. All 16 scenarios pass on the current base.
2026-10-09 08:00:00 +08:00
morluto 96af2fa744 fix(browser): preserve captures when response bodies are unavailable 2026-10-09 07:47:34 +08:00
morluto 67756dacc0 fix(ci): align diagnostic regressions and format merged changes 2026-10-09 07:45:49 +08:00
morluto 80c3d36dfa fix(ghidra): preserve POSIX runtime paths with spaces 2026-10-09 07:29:17 +08:00
morluto c715af8058 fix(android): report actual provider readiness 2026-10-09 07:29:17 +08:00
morluto 55c9ce26d1 test(cli): verify self-import parity and cleanup 2026-10-09 07:29:16 +08:00
morluto a69b8337ab fix(cli): support MCP doctor help 2026-10-09 07:29:16 +08:00
morluto 1d9b96a9c4 fix(cli): preserve web cancellation exit status 2026-10-09 07:29:15 +08:00
morluto 3e598b2d38 fix(ghidra): admit Linux arm64 with matching native tools 2026-10-09 07:29:15 +08:00
morluto 1f79002497 docs: clarify contribution scope and cosmetic suggestions 2026-10-09 07:28:47 +08:00
d84bc8f11a feat(ghidra): make the startup deadline configurable (#974)
* feat(ghidra): make the startup deadline configurable

Large binaries (an 8 MB PE with about 20,000 functions) need more than the
fixed 330 s for import and full auto-analysis, so every first request timed
out. REA_GHIDRA_STARTUP_TIMEOUT_MS overrides the deadline in milliseconds;
absent, empty or invalid values keep the 330 s default.

* docs(ghidra): document REA_GHIDRA_STARTUP_TIMEOUT_MS

* fix(ghidra): parse startup deadlines through runtime configuration

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-09 07:21:36 +08:00
65a809a0da fix(process): avoid cold ownership helper deadline exhaustion (#1139)
* fix(process): avoid cold ownership helper deadline exhaustion

Fixes #1137.

* test(process): fix cold ownership test deadlines and cleanup

* fix(process): isolate Swift cache overrides and retain diagnostics

* test(electron): assert blank path rejection without trim assumptions

---------

Co-authored-by: Chris <Chis@RICH-13918M.local>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-09 07:18:07 +08:00
morlutoandRudy Mizrahi Celekli 4034883e98 fix(artifacts): preserve keyed archive integer precision
Integrate the numeric precision correction from #1135 with complete supplemental
metadata handling. Keep decoded numbers with an explicit limitation for mixed XML
content or unclassified integer text, rather than assigning another literal's
exact decimal. Project nonnumeric UID and CF$UID dictionary values recursively
while preserving numeric references, graph identity and original byte digests.

Add filesystem, built CLI and MCP regressions for the review findings, adjacent
XML representations and numeric reference controls. Retain the original native
Foundation qualification fixtures and verifier.

Verification: 84 focused tests; npm run check; npm run docs:check;
npm run verify:test-discovery; npm run verify:keyed-archive (real Foundation,
CLI and stdio MCP). No real Hopper or Ghidra verification was needed or run.

Co-authored-by: Rudy Mizrahi Celekli <47457359+rudycelekli@users.noreply.github.com>
Signed-off-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-09 07:13:32 +08:00
1d7115b7b4 fix(javascript): omit self-referential import edges with disclosure (#1130)
* fix(javascript): omit self-referential import edges with disclosure

A specifier that resolves back to the importing module itself produced an
imports edge from a module to itself, which the application graph schema
forbids; one such specifier failed result validation and lost the whole
analysis. Skip those edges at composition time and disclose the omission
count in the graph limitations.

Fixes #1129

* test(javascript): verify self-import behavior through the compiled CLI

---------

Co-authored-by: BaaRai <baarai@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-09 07:10:38 +08:00
c1d0002470 test(browser): pin DOM and source-map URL policy classifications (#1120)
* test(browser): pin DOM and source-map URL policy classifications

Cover the unsupported DOM scope, credentialed destinations, and the source-map fetcher's pre-request filtering of credentials, other origins and non-http(s) URLs, so a later vocabulary unification can show exactly what changes.

Refs #829

* test(browser): assert exact sanitized credentialed DOM URL

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-09 06:57:40 +08:00
e8939ca220 fix(hopper): enforce caller-supplied request deadlines (#1119)
* fix(hopper): enforce caller-supplied request deadlines

* fix(hopper): correct idle deadline state and name timeout bound

Zero-ms deadlines never enter the FIFO, so report providerState
not_started instead of busy; the busy wording tells callers to wait
for an active request that does not exist. Name the Node timer
ceiling once and reuse it in validation and the error message.

* fix(hopper): enforce elapsed deadlines before dispatch and replies

* fix(hopper): distinguish unsent requests in timeout diagnostics

* test(hopper): exercise request deadlines against native Hopper

---------

Co-authored-by: Vilmer Frost <frost.vilmer@gmail.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-09 06:52:07 +08:00
329cf206d1 fix(hopper): preserve owned basic-block endpoint instructions (#1118)
* fix(hopper): preserve owned basic-block endpoint instructions

* test(hopper): share probe runner and restore fixture hashability

Reuse one probe helper with the facade test's execFile options
(encoding, timeout, maxBuffer) instead of repeating the invocation.
Give the fixture Block a hash consistent with its equality so the
equality-based ownership doubles stay hashable.

* fix(hopper): align owned instruction walks with normalized block ranges

---------

Co-authored-by: Vilmer Frost <frost.vilmer@gmail.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-09 06:51:53 +08:00
morluto 2b27515476 fix(process): stabilize native capture and ownership prerequisites 2026-10-09 06:49:02 +08:00
morluto 44fd18305f fix(capture): retain Electron observations and HAR resource failures 2026-10-09 06:49:02 +08:00
morluto 6c13fc8e0a fix(android): report startup and selector failure causes 2026-10-09 06:49:02 +08:00
morluto 70e8a8f860 fix(contracts): preserve local inputs and comparison uncertainty 2026-10-09 06:49:02 +08:00
morlutoandArad Ashkan 7a7460f04f docs: add Persian README translation with full language navigation
Adds README_fa.md (Persian/Farsi) contributed via PR #1140, keeps onboarding commands, links, and structure aligned with English, links فارسی from every translated selector, and registers fa in the readme consistency matrix.

Co-authored-by: Arad Ashkan <65897590+AradAshkan@users.noreply.github.com>
2026-10-09 06:44:02 +08:00
morluto b3f1b4e822 fix(mcp): retain oversized errors before transport delivery 2026-10-09 06:39:12 +08:00
morluto 480b16b9c1 fix(ghidra): parse long procedure selectors without recursion 2026-10-09 06:39:12 +08:00
morluto e762e32afb refactor(browser): charge source-map records before retention 2026-10-09 06:32:10 +08:00
morluto f7366a517f fix(verify): close both sides of the browser proxy 2026-10-09 06:32:10 +08:00
morluto faf209e50d fix(browser): bound script metadata and preserve omissions 2026-10-09 06:32:10 +08:00
morluto 0a0174b9ac fix(browser): validate and bound source-map evidence 2026-10-09 06:32:10 +08:00
morluto e1df5e8555 fix(apple): account for repeated archive projections 2026-10-09 06:32:10 +08:00
morluto d575a7340b fix(apple): decode bundle plist bytes before executable lookup 2026-10-09 06:32:09 +08:00
morluto 6495171812 fix(javascript): classify direct defaults and argv elements 2026-10-09 06:32:09 +08:00
morluto 14b19a0b8a fix(native): resample tool identity for each invocation 2026-10-09 06:32:09 +08:00
9fd65ce071 fix(setup): treat empty client config files as new documents (#1117)
* fix(setup): treat empty client config files as new documents

An empty MCP config file (for example Antigravity's mcp_config.json, which
the client can create with zero bytes) was reported as malformed, which
blocked rea setup from configuring every other detected agent. Parse a
whitespace- or BOM-only JSON/JSONC file as an empty document and write a
fresh document over it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(setup): use explicit unicode escape for BOM-only empty config case

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-09 06:30:46 +08:00
567a82f8d4 fix(update): accept npm 12 release metadata (#1138)
* fix(update): accept npm 12 release metadata

(cherry picked from commit aa400d18c95e36029c5c654c396ec66924e1358f)

* fix(update): classify npm release metadata without a spawn mock

Keep the npm 11 string and npm 12 one-element array accepted by the
cherry-picked lookup, and separate invalid metadata from a failed
registry command.

---------

Co-authored-by: Dapo Olaomo <penielgold@gmail.com>
2026-10-09 06:29:29 +08:00
morluto 82626c4499 fix(hopper): bind file mappings to verified source bytes
Read source digests, FAT tables and embedded headers from one opened source stream before translating native offsets. Preserve native mapping observations through changed, missing, nonregular and access-denied source failures; keep filesystem work outside the Hopper API thread.

Exercise source drift, relocated slices, missing files, permissions and recovery through real Hopper CLI/MCP verification for thin, FAT32 and FAT64 inputs.
2026-10-09 06:13:41 +08:00
morluto ae06dcf83f test: prune resource-recovery wording assertions 2026-10-09 06:13:41 +08:00
08561e0eb7 feat(clients): register Grok Build and Grok Bot during setup (#1134)
* feat(clients): register Grok Build and Grok Bot during setup

Detect both clients and install the local REA MCP registration the same
way setup already handles the other agents. Grok Build edits only the
REA tables in its TOML config and sets startup_timeout_sec to 30. Grok
Bot writes the mcpServers.rea entry into mcp.json. That file is the
registration to add to the signed-in account; setup does not call the
account API.

Filed by Grok.

* fix(clients): clear a disabled Grok Build registration during setup

Grok Build keeps a server suppressed when its name stays in
disabled_mcp_servers, and setup was treating that file as already aligned.
Setup now removes only that name, doctor reports the registration stale until
it is gone, and uninstall keeps comments in an inline table whose only server
was REA. Split the lifecycle suite and reflow the get-started page so static
checks pass.

* fix(verify): check packed Hopper search as literal-only

The package verifier still called a regex probe that main deleted. The
native bridge refuses regex and joins string hits with typed records, so
the restored probe stubs that inventory and the check asserts the literal
matches and the regex refusal. The new README translations also name
Grok Build and Grok Bot.

* fix(clients): stop treating a Grok Bot data directory as registered

Grok Bot stores connectors in the signed-in account and runs them on its
hosted computer. Setup no longer writes mcp.json or reports that file as
aligned. Doctor and setup tell the caller to add the server from the
Grok Bot chat.

* fix(clients): drop multiline Grok disable-list entries

Decode disabled_mcp_servers strings with the document parser so a
multiline basic or literal string that loads as rea is removed.

---------

Co-authored-by: Grok <eugene.ray@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-09 06:08:29 +08:00
morluto 0639a541a3 fix(mcp): preserve retained evidence on oversized delivery 2026-10-09 05:55:55 +08:00
morluto 826583dd43 fix(verify): remove stale mocked bridge package probe 2026-10-09 05:54:10 +08:00
morluto c3136064c7 fix(cli): accept exact native names through explicit selectors 2026-10-09 05:46:16 +08:00
morluto 2a4979b7cd docs: move Portuguese toward the end of language navigation 2026-10-09 05:35:05 +08:00
morluto a61491ad00 docs: add ten README translations and expand language navigation 2026-10-09 05:33:36 +08:00
morluto 2847311a57 fix(hopper): normalize native block endpoints and retain terminal evidence 2026-10-09 05:31:49 +08:00
morluto 4458128a84 fix(ghidra): make qualified function renames idempotent 2026-10-09 05:27:39 +08:00
morluto 34f7c4fd66 fix(docs): validate translated links to canonical product facts 2026-10-09 05:15:23 +08:00
morluto 67252b23b9 perf(javascript): index configuration and module lookups 2026-10-09 05:15:23 +08:00
morluto 02d0702761 fix(electron): bound hook retention and report dropped coverage 2026-10-09 05:15:23 +08:00
morluto e714fb027a fix(inspector): bound retained metadata and preserve cancellation 2026-10-09 05:15:23 +08:00
morluto f983c718c3 fix(browser): bound aggregate source-map decoding before expansion 2026-10-09 05:15:23 +08:00
morluto 3d450a29fc fix(application): budget bridge candidate projection by bytes 2026-10-09 05:15:23 +08:00
morluto 73b09f4c5c fix(managed): bind inspection evidence to artifact identity 2026-10-09 05:15:23 +08:00
morluto dbcf8c3391 fix(apple): budget Interface Builder decoding and projection 2026-10-09 05:15:23 +08:00
morluto f2e9c491ea fix(native): share command supervision and retain failure output 2026-10-09 05:15:23 +08:00
morluto 4a45c14742 fix(firmware): reject malformed UTF-8 producer reports 2026-10-09 05:15:23 +08:00
morluto 2b86ea9c7c docs: require partial native facts at provider boundaries 2026-10-09 05:12:36 +08:00
morluto f201b04fbb fix(hopper)!: isolate regex matching from the native API thread
BREAKING CHANGE: Hopper regex mode uses ECMAScript Unicode syntax instead of Python re. Matching runs in a cancellable worker with a five-second deadline; literal mode keeps Unicode casefold matching.
2026-10-09 05:12:36 +08:00
morluto 246170238e fix(hopper): preserve partial native call and string evidence 2026-10-09 05:12:36 +08:00
morluto 0a49d5bfb9 fix(hopper): load verified FAT64 slices with native Mach-O semantics 2026-10-09 05:12:36 +08:00
morluto 458771cb58 fix(hopper): preserve launcher evidence on readiness timeout 2026-10-09 05:12:36 +08:00
morluto 7d7b3f961a fix(hopper): preserve typed string values and verify real searches 2026-10-09 05:12:36 +08:00
morluto 35f1ddd6bc fix(hopper): resolve FAT loaders and original file coordinates 2026-10-09 05:12:36 +08:00
morluto b2d5bc1109 fix(hopper): validate annotation destinations and symbol extents 2026-10-09 05:12:36 +08:00
morluto 704fa7699c fix(ghidra): preserve source failures and snapshot ownership 2026-10-09 05:10:36 +08:00
morluto 2f59287f1a feat(website): add GitHub link with live star count 2026-10-09 04:55:51 +08:00
morluto d83f0983d5 docs: align translated READMEs and website links with English 2026-10-09 04:55:43 +08:00
morluto 1136ea8810 test(session): preserve live binding after cancelled switches 2026-10-09 04:43:19 +08:00
morluto e44cb45908 fix(session): serialize snapshot lifecycle with admitted requests 2026-10-09 04:37:43 +08:00
morluto 8d39eae6b9 chore(website): configure Cloudflare hosting for rea.tools 2026-10-09 04:29:57 +08:00
morluto 09d7b9bab5 test(process): cover argv rewrites in native ownership inspection 2026-10-09 04:12:49 +08:00
morluto 9a70fb482c test(process): distinguish native startup from producer verification 2026-10-09 04:12:49 +08:00
morluto f591e37a1e test(artifacts): retire simulated mounts after detach failures 2026-10-09 04:12:49 +08:00
morluto a0784c0d2c test(process): retire native caches at worker teardown 2026-10-09 04:12:49 +08:00
morluto 96d3cafb07 fix(browser): bind correlated replies to their selected session 2026-10-09 04:12:49 +08:00
morluto ee043e2cbf fix(verify): report final success only after resource cleanup 2026-10-09 04:12:49 +08:00
morluto 3eb7dbdc16 fix(verify): isolate package archives and published canary caches 2026-10-09 04:12:49 +08:00
morluto 90b06db6c0 test(process): expose unavailable capture and unexpected teardown failures 2026-10-09 04:12:49 +08:00
morluto 6b92002597 fix(browser): preserve command failures and reject malformed replies 2026-10-09 04:12:48 +08:00
morluto bc4aa46d7c fix(ghidra): recover regex stack exhaustion without losing annotations 2026-10-09 03:54:11 +08:00
morluto 549effa056 fix(ghidra): resolve exact external function entries 2026-10-09 03:54:02 +08:00
morluto dcae3c6c14 fix(ghidra): reject address truncation before reads and edits 2026-10-09 03:53:28 +08:00
morluto 84a17d5519 fix(ghidra): preserve function references and snapshot boundaries
Collect references from the complete owned function body and exact entry,
including instruction-interior destinations and embedded-data sources.
Bind the corrected semantics to the analysis profile so legacy snapshots
cannot replay incomplete results. Preserve snapshot mismatch reasons and
recovery advice through CLI and MCP without disturbing live sessions.

Return exact entry candidates for ambiguous function names. Expand the
real-provider regression workflow and replace redundant parser assertions
with malformed-output coverage through the pinned MCP client.

Verified with Ghidra 12.1.4 on macOS arm64: full host-native lane, real
CLI/MCP boundary probes, 30 focused tests, and typecheck/lint.
2026-10-09 03:19:47 +08:00
morluto 017297c40c test(browser): reject unmodeled CDP commands in protocol fixtures 2026-10-09 03:13:53 +08:00
morluto ffd9611308 test: consolidate failure guards and trim unused fixture APIs 2026-10-09 03:13:43 +08:00
morluto 81ae838567 refactor(config): prune unused format wrappers and launcher export 2026-10-09 03:13:27 +08:00
YsopwnableandGitHub d1c904bac7 fix(build): exclude the test-only generated catalog from packages (#1116) 2026-10-09 03:02:07 +08:00
morluto 8f9da25164 test(mcp): serialize real process capture and report unavailable authority 2026-10-09 02:58:03 +08:00
morluto fe48e779c2 test(setup): scope removal regression to its fixture client 2026-10-09 02:58:03 +08:00
morluto 9a5b077899 test(process): retain cancellation checks under unverified host cleanup 2026-10-09 02:58:03 +08:00
morluto 9588482bfe docs(testing): prioritize real workflows and distinct regressions 2026-10-09 02:58:03 +08:00
morluto 0e0b386dc3 test: classify application scenarios by their actual boundaries 2026-10-09 02:58:03 +08:00
morluto a1b7d40bb0 test: remove duplicate result assertions before throwing guards 2026-10-09 02:58:03 +08:00
morluto 5945915f91 test: prune redundant cases and promote configuration lifecycle coverage 2026-10-09 02:58:03 +08:00
morluto bef234d6ce fix(ghidra): preserve Unicode boundaries without breaking sessions
Validate native annotation text before mutation and escape surrogate code units in Java bridge JSON replies. Reject unsupported text with field and index diagnostics while preserving supported Unicode and line endings.

Probe real CLI/MCP annotation, transport, mapping, rollback and cleanup behavior; replace seven redundant unit tests with E2E and SDK boundary coverage.
2026-10-09 02:50:33 +08:00
morluto 64717ad7da test(hopper): cover real boundary failures and CLI parity 2026-10-09 02:44:28 +08:00
morluto d830cb79b3 fix(analysis): resolve procedure identities and preserve literal queries 2026-10-09 02:42:39 +08:00
morluto f2e22e13a6 fix(hopper): preserve native navigation and annotation semantics 2026-10-09 02:42:39 +08:00
YsopwnableandGitHub b743bd20a0 fix(hopper): verify lease cleanup and repair the macOS test lane (#1111)
* fix(hopper): distinguish shared lease roots from leaked sessions

* fix(ci): follow the moved process CLI regression tests
2026-10-09 02:41:34 +08:00
YsopwnableandGitHub ec6db0f063 perf(javascript): reuse validated immutable graphs (#1112)
* perf(javascript): reuse validated immutable graphs

* fix(ci): follow the moved process CLI boundary test
2026-10-09 02:40:21 +08:00
morluto 1f5be750bc fix(ghidra): harden location resolution and provider lifecycle
Preserve boundary diagnostics, resolve procedure names before bare hex addresses, and launch the inspected JVM directly on macOS for verifiable startup cancellation. Keep private socket paths within host limits and retain ownership when cleanup is incomplete.

Exercise the fixes through real CLI/MCP Ghidra workflows and provider-boundary integration tests, replacing redundant unit coverage.
2026-10-09 02:38:07 +08:00
morluto e2fc2493b6 fix(process): retain ownership after incomplete cleanup 2026-10-09 02:37:47 +08:00
morluto be783efaba fix(release): always bump minor for releases 2026-10-09 02:15:30 +08:00
morluto 7005166f58 test(hopper): verify real boundaries and prune redundant happy paths 2026-10-09 02:05:58 +08:00
morluto 2f71375efc fix(hopper)!: bind analysis to the active target and correct boundary contracts
Preserve native document identity, nullable bookmark labels, address diagnostics, segment-end byte prefixes, and verified file mappings. Validate batch rename addresses before mutation.

BREAKING CHANGE: remove set_current_document. Use open_binary to switch targets; document arguments are scoped to the active target.
2026-10-09 02:05:31 +08:00
YsopwnableandGitHub 34cc9259c1 fix(process): derive executable names from the capture host (#1108) 2026-10-09 02:02:24 +08:00
Rudy Mizrahi CelekliandGitHub 8b08ec0af9 fix(browser): compose nested source-map column offsets (#1109)
* test(browser): verify native nested source-map coordinates

Qualify valid compiler-produced nested index maps through actual owned Chrome and the built public analyze-web-bundle CLI. Preserve same-line and zero-column controls, physical UTF-16 mapping positions and the independently correct dedicated decoder control. No production or workflow changes.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* test(browser): retain native source-map failure receipts

Serialize the already-created phase and proof fields for failed native source-map owners. Preserve all producer inputs, scenarios, commands and assertions; first native two-failure/two-control evidence remains unchanged.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* test(browser): preserve nested source-map boundary contracts

Qualify ordinary maps, nested offsets, sibling clipping, parent gaps, empty sections and deeper composition against the actual browser fetcher. Compare the existing dedicated decoder only for its valid non-overlapping profile. Preserve the four frozen compiler/Chrome/public CLI scenarios.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* fix(browser): compose nested source-map column offsets

Normalize indexed sections to absolute leaf offsets before upstream flattening. Apply enclosing column offsets only for same-line child sections and retain parent clipping boundaries for gaps and empty sections. Preserve raw map evidence, source identities, transport budgets and the dedicated decoder profile.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

---------

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
2026-10-09 02:01:29 +08:00
morluto 82560ebf59 test(process): serialize real CLI capture boundaries 2026-10-09 01:55:12 +08:00
morluto 8a4c4b657f test(process): report the cause of terminal capture failures 2026-10-09 01:55:12 +08:00
morluto 1d0baa352f fix(native): preserve bridge failure types during cleanup 2026-10-09 01:55:12 +08:00
morluto 7c323870c2 fix(native): bound retained observations and preserve failure evidence 2026-10-09 01:55:12 +08:00
morluto d6cc9ca3a6 fix(javascript): preserve lexical open and exact template semantics 2026-10-09 01:55:12 +08:00
morlutoandGitHub fe297e56e8 fix(apple): classify empty dyld settings by consumer semantics (#1110) 2026-10-09 01:54:32 +08:00
YsopwnableandGitHub 1e63144390 ci: run signature target binding regressions on native macOS (#1107) 2026-10-09 01:53:40 +08:00
YsopwnableandGitHub cd756b07d5 fix(hopper): report launcher failures before bridge timeout (#1105)
* fix(hopper): report launcher failures before bridge timeout

* fix(hopper): separate helper ownership from GUI lifetime
2026-10-09 01:51:45 +08:00
morlutoandGitHub 7914e36ab9 fix(apple): validate dylib candidates and preserve search uncertainty (#1100)
* fix(apple): validate dylib candidates and preserve search uncertainty

* fix(apple): scope embedded dyld uncertainty by search phase

* fix(apple): scope dyld prefixes to eligible paths and platforms

* fix(apple): ignore embedded process settings on library roots
2026-10-09 01:43:52 +08:00
Rudy Mizrahi CelekliandGitHub 3b2ec69c54 fix(apple): report missing keyed archive hierarchy references (#1104)
* test(apple): qualify malformed native hierarchy references

Freeze actual Foundation/AppKit producer, single-slot positive UID mutation and real optional nil control at filesystem and compiled CLI boundaries. Preserve original raw-byte evidence and existing CI.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* fix(apple): report missing keyed archive hierarchy references

Preserve known hierarchy objects and links while reporting unresolved nonnil object-table references as partial coverage. Retain archived nil semantics and original byte evidence.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

---------

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
2026-10-09 01:31:53 +08:00
YsopwnableandGitHub 4bb056bbb5 fix(artifacts): retain failed DMG cleanup state and diagnostics (#1103) 2026-10-09 01:28:42 +08:00
morlutoandGitHub c789af04b7 fix(native): bind signature observations to registered target versions (#1099)
* fix(native): bind signature observations to registered target versions

* fix(native): preserve target drift and cancellation across signature captures

* fix(native): inspect signatures from a verified byte snapshot
2026-10-09 01:27:26 +08:00
morluto acb3e0f2e5 fix(release): restore automatic release proposals on main 2026-10-09 01:25:36 +08:00
Rudy Mizrahi CelekliandGitHub 3db46813f8 fix(javascript): parse plain TypeScript artifact dialects (#1102)
* test(javascript): reproduce TypeScript source dialect loss

Add native directory/ASAR and built public CLI owners with actual locked native TypeScript syntax/emit and independent Babel source-range oracles. Production and every workflow remain unchanged.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* test(javascript): admit isolated native compiler inputs

Use the actual TypeScript7 producer-requested --ignoreConfig flag when supplying isolated source filenames. Preserve every graph input, owning assertion, control and production file.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* fix(javascript): parse plain TypeScript artifacts without JSX

Select the plain .ts dialect using the artifact source path so valid angle-bracket assertions retain syntax-derived graph facts. Preserve existing JSX defaults and all other dialects. Document the inert artifact parser contract.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

---------

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
2026-10-09 01:24:47 +08:00
YsopwnableandGitHub cc0ee2edc6 fix(javascript): honor cancellation before evidence publication (#1098) 2026-10-09 01:15:50 +08:00
f8e143a3bc fix(apple): decode Interface Builder XML byte encodings (#1095)
* test(apple): qualify native Interface Builder XML encodings

Exercise actual AppKit/Foundation XML archive representations through filesystem and compiled CLI consumers. Preserve independent UID/hierarchy and original-byte provenance oracles.

Tests-only candidate qualification on unchanged native Apple CI; no production or workflow changes.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* fix(apple): share BOM-aware Interface Builder XML decoding

Reuse the existing fatal BOM-aware XML plist decoder for Interface Builder archives. Preserve original serialized-byte digests, binary decoding, and frozen native producer/consumer tests.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* fix(apple): reject unsupported XML encoding declarations

---------

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
Co-authored-by: N0zoM1z0 <1705004398@qq.com>
2026-10-09 01:11:49 +08:00
Kaoru0822andGitHub ae0630a619 Merge pull request #1097 from morluto/fix/web-runtime-script-initiators
fix(browser): treat module-import initiator positions as script positions
2026-10-08 10:00:21 -07:00
YsopwnableandGitHub 81b5104276 fix(analysis): bind overview document identity to the active target (#1094)
* fix(analysis): bind overview document identity to the active target

* fix(analysis): refresh legacy overview snapshot results
2026-10-09 00:57:28 +08:00
morluto 4a1837d9d5 fix(javascript): bound primitive string growth and repeated evaluation 2026-10-09 00:49:31 +08:00
morluto 770d2f60aa test(workflows): verify residual questions in CLI snapshots 2026-10-09 00:49:31 +08:00
morluto eb89a16d9b fix(process): identify unknown live processes in cleanup diagnostics 2026-10-09 00:49:31 +08:00
morluto bfb7776006 fix(release): publish prereleases under the next npm tag 2026-10-09 00:49:30 +08:00
morluto 4b1066501b refactor(setup): validate formats through canonical configuration 2026-10-09 00:49:30 +08:00
morluto 8b01e0cc89 fix(providers): validate ELF versions and preserve probe cancellation 2026-10-09 00:49:30 +08:00
morluto 915855e75c fix(native): supervise LLDB and preserve bounded capture uncertainty 2026-10-09 00:49:30 +08:00
morluto 488f448ae2 fix(apple): derive dispatch coverage from located decode facts 2026-10-09 00:49:30 +08:00
morluto 15f92f2988 fix(apple): validate physical slices and dylib platform compatibility 2026-10-09 00:49:30 +08:00
morluto 16f4609d68 fix(javascript): bound semantic expansion and resolve lexical receivers 2026-10-09 00:49:30 +08:00
morluto eaf5734083 fix(browser): bound source-map fetches and retain predecessor requests 2026-10-09 00:49:30 +08:00
morluto 135d102f9d refactor(artifacts): centralize stable inventory identities 2026-10-09 00:49:30 +08:00
morluto d4c2c3c18f fix(target): preserve app bundle filesystem failure reasons 2026-10-09 00:49:30 +08:00
morluto 17809caacd fix(managed): match exact method bytes without decoded signatures 2026-10-09 00:49:30 +08:00
morluto 0136ee8c22 fix(workflows): retain residual unknowns through CLI and MCP 2026-10-09 00:49:30 +08:00
morluto 65a6288257 refactor(input): share schema issue error construction 2026-10-09 00:49:30 +08:00
Kaoru0822andClaude Opus 5.5 99d31ee24a fix(browser): treat module-import initiator positions as script positions
An empty script_id also marks a script-type initiator, such as a module
import, whose url is the importing script file. Its position is a real
script position without a session-script association. The previous
wording called every empty-ID callsite parser markup with "no script
position" and excluded it from the external-script conversion. Describe
the empty ID as a missing session-script association, which only rules
out joining captured source or resource_start, and keep the
external-script conversion for script-file positions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 16:44:25 +00:00
3c157b4ec6 fix(browser): keep initiator callsites out of script coordinate guidance (#1096)
observe_web_execution also derives callsites from a request's reported
initiator, such as an image the parser found in HTML or a module import,
with an empty script_id. Those positions point into the initiating
resource, not a script, so the "script's enclosing resource" wording and
source-map conversion did not apply to them. Describe the coordinates as
relative to the resource named by url, explain empty script IDs, and
limit the documented conversion to positions with a script_id.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:43:03 +08:00
c1b370ce0d fix(javascript): retain NodeNext artifact source facts (#1087)
* test(javascript): reproduce omitted NodeNext artifact sources

Tests only: qualify current .mts/.cts artifact selection through real directory and ASAR reconstruction and the built public CLI.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* fix(javascript): retain NodeNext artifact source facts

Read .mts and .cts artifact files through the existing inert TypeScript parser, preserving selected file identities and analysis facts in directory and ASAR inputs. Document source selection and format the existing regression assertion.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

---------

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
Co-authored-by: N0zoM1z0 <1705004398@qq.com>
2026-10-09 00:39:39 +08:00
5a67c7fc54 fix(hopper): diagnose MCP provider failures with stage, state, and request context (#1044)
* fix(hopper): diagnose MCP provider failures with stage, state, and request context

* fix(hopper): retain health at the actual provider lifecycle boundary

* test(hopper): distinguish socket loss from later observed process exit

* test(hopper): cover observed exit separately from socket-first replies

---------

Co-authored-by: N0zoM1z0 <1705004398@qq.com>
2026-10-09 00:33:56 +08:00
b5c2954ad6 fix(domain): order canonical paths by code point, not UTF-16 unit (#1034)
compareCodePoints documented a Unicode code-point comparison but used
</> operators, which compare UTF-16 code units. For astral code points
(above U+FFFF) the lead surrogate 0xD800-0xDBFF is numerically below the
BMP tail U+E000-U+FFFF, so those characters sorted before it.

Delegate to the existing compareUnicodeCodePoints, which the domain
already uses elsewhere, so the two canonical comparators agree. All call
sites use only the sign, so sort and '< 0' checks are unaffected.

Adds the missing regression coverage for canonicalOrdering.

Co-authored-by: TahsinArafat <TahsinArafat@users.noreply.github.com>
Co-authored-by: N0zoM1z0 <1705004398@qq.com>
2026-10-09 00:26:25 +08:00
6218d289e2 fix(browser): advertise zero-based units for runtime source coordinates (#1085)
* fix(browser): advertise zero-based units for runtime source coordinates

inspect_web_event_listeners and observe_web_execution return CDP
callsite, listener and resource_start line_number/column_number values
unchanged: zero-based lines and UTF-16 columns. Neither the advertised
output schema nor docs/web-runtime.md said so. trace_web_source_location
takes one-based lines, so a caller chaining a listener location into it
lands one line early.

Describe the units on the shared schema fields, which reach both tools'
advertised output schemas, and document the conversion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(browser): describe runtime positions relative to the enclosing resource

For an inline <script>, CDP reports listener and callsite positions
relative to the HTML document, and resource_start is where the script
text starts in that document. A real headless Chromium 155 page showed
this: a listener on HTML line 6 came back as line_number 5, with
resource_start {3, 8}. Describe all positions as relative to the
enclosing resource. Document that inline-script positions must have
resource_start subtracted, plus one added to the line, before
trace_web_source_location.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(browser): clarify runtime-to-source coordinate conversion

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: N0zoM1z0 <1705004398@qq.com>
2026-10-09 00:22:18 +08:00
Rudy Mizrahi CelekliandGitHub 23862af1b5 fix(javascript): preserve HTML carriage return source ranges (#1092)
* test(javascript): reproduce HTML carriage return source coordinates

Compare actual resolved and unresolved artifact graph evidence with parse5 source locations across HTML line terminators and UTF-16 columns. Exercise both public built CLI analysis commands. Tests only; production and workflows unchanged.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* fix(javascript): preserve HTML carriage return source ranges

Treat LF, CRLF, and bare CR as HTML line endings when projecting actual script opening-tag offsets to retained source ranges. Preserve UTF-16 column semantics and document the source-coordinate contract.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

---------

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
2026-10-09 00:15:24 +08:00
e94be95c66 test(artifacts): verify native directory identity on supported Windows runtimes (#1069)
* fix(artifacts): guard Windows device 0 in directory open integrity check

On Windows, Node.js fs.lstat reports dev as 0 without opening a handle. When DirectoryArtifactReader.open inspects the opened handle using handle.stat(), stat.dev is populated with the volume serial number.

Consequently, the TOCTOU check observed.dev !== entry.sourceIdentity.device evaluated to true on Windows for every regular file, throwing an ArtifactReaderFailure integrity error.

Relax the device comparison when running on Windows or when the inventoried device is 0, while maintaining strict inode verification to ensure the target has not been replaced.

* test(artifacts): verify native directory identity without device bypasses

---------

Co-authored-by: N0zoM1z0 <1705004398@qq.com>
2026-10-09 00:11:28 +08:00
Rudy Mizrahi CelekliandGitHub 83defc054b fix(javascript): preserve original BOM source coordinates (#1093)
* test(javascript): reproduce BOM source range drift

Compare original UTF-8 source bytes and their retained digests with direct Babel and parse5 source-location oracles through actual directory and ASAR artifact reconstruction and both built public CLI analysis commands. Preserve no-BOM, structured-BOM and invalid UTF-8 controls. Tests only; production and workflows unchanged.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* fix(javascript): preserve original BOM source coordinates

Retain the initial UTF-8 BOM for JavaScript and HTML source text so graph ranges refer to the original digest-bound bytes. Preserve structured-text BOM handling and fatal UTF-8 decoding.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* style(javascript): format BOM decoder option

Apply the locked Oxfmt line wrapping to the approved decoder option; no behavior or test changes.

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

---------

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
2026-10-09 00:09:55 +08:00
eb69763d27 fix(process): name the reason two captures cannot be compared (#1080)
* fix(process): name the reason two captures cannot be compared

compare-process-captures rejected captures whose comparison contracts
differ, but the reason never reached the caller. The CLI printed
"Process command could not complete... run `rea doctor`". MCP wrapped
the error in an AnalysisInputError with no issues ("Analysis input is
invalid. Check the arguments"). A stale capture under
max_capture_age_ms got the same treatment.

Throw typed input issues that name the differing contract fields, or
the stale side with its completion time and age, and let
runDerivedOperation pass AnalysisInputError through unchanged. Document
what the comparison contract covers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(process): report CLI trace-spec issues under the MCP input path

The CLI reported an invalid trace specification under operation
"compare-process-captures" with root-relative issue paths such as
["events"]. MCP reports the same input as compare_process_captures with
paths under trace_spec. Use the canonical operation name and prefix
trace_spec so both surfaces describe the same input identically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(process): name the side and constraint for unusable capture Evidence

Adjacent compare_process_captures failures still lost their reason.
Over MCP, Evidence from another workflow, tampered Evidence and an
invalid capture result all became "Evidence is invalid or has changed".
The CLI said only "Capture evidence is malformed".

Parse each side separately. Evidence from another workflow is an input
issue on left or right that names the operation and predicate it has.
Tampered Evidence and invalid capture results stay integrity
mismatches, and their message now carries the validation detail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:04:35 +08:00
289aef6838 fix(workflows): keep caller-input details in workflow input errors (#1089)
* fix(managed): keep caller-input details in managed workflow errors

The managed workflows wrapped every rejected input as
`new AnalysisInputError(op, { cause })` with no issues. The projected
result said "Correct the listed arguments and retry" and listed nothing:

- `rea import-managed-reconstruction`,
  `rea project-managed-application-graph` and
  `rea verify-managed-native-boundaries` lost every schema issue for
  malformed JSON;
- domain checks such as a reconstruction text hash mismatch, or Evidence
  from the wrong operation, lost their message on CLI and MCP;
- `rea compare-managed-members` with a missing file did not say which
  path failed.

Add managedInputError, which projects Zod issues with their paths and
keeps a domain TypeError's message, as the JavaScript workflows already
do. Return the BinaryTargetError from an unopenable comparison path
unchanged, with details.path, as every other path-based command does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(managed): keep nested Evidence schema issues off request paths

A ZodError raised after the request parsed comes from a nested
Evidence result, so its issue paths are relative to that result.
Projecting it against the whole request reported, for example,
artifact.sha256 as a missing request field. Keep request-relative
projection for the raw request's schema failures. Report nested schema
failures as root issues whose message names the nested location.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(managed): type the malformed nested Evidence fixture

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(workflows): keep input details in the remaining workflow services

Inventory projection (project_android_application_graph and
project_apple_application_graph), build_reconstruction_obligation_ledger
and evaluate_reconstruction_coverage had the same issue-less
`AnalysisInputError({ cause })` as the managed services. For example,
evaluating coverage for an unknown boundary returned "Correct the listed
arguments" with no issues, dropping "Unknown reconstruction boundary: X"
on CLI and MCP.

Move the helpers to src/application/workflowInputError.ts and use them
for these services: requestInputError for raw request schema failures,
workflowInputError for nested schema and domain constraint failures.
Classification is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:56:19 +08:00
1286 changed files with 82016 additions and 18290 deletions
+3 -1
View File
@@ -61,7 +61,8 @@ limits, security/privacy/containment implications, and meaningful proof gaps. --
- Breaking changes or migration steps:
<!-- For ! / BREAKING CHANGE: show a previously valid call/configuration,
its new behavior and migration, and why compatibility/deprecation cannot
preserve it. Compatible fixes/additions should use patch/minor semantics.
preserve it. Every release increments minor, including breaking changes;
keep breaking markers and migration notes so callers can assess compatibility.
See docs/releasing.md. -->
- Real Hopper/Ghidra, browser, or OS coverage:
- Package or release metadata impact:
@@ -69,6 +70,7 @@ limits, security/privacy/containment implications, and meaningful proof gaps. --
## Review checklist
- [ ] This PR addresses a concrete problem or an agreed enhancement.
- [ ] The PR has one focused outcome and the title follows `type(scope): outcome`.
- [ ] Related issue is linked, or the reason for not linking one is stated above.
- [ ] Tests cover changed observable behavior and meaningful failure paths.
+20 -3
View File
@@ -102,7 +102,7 @@ jobs:
with:
name: rea-dist
path: dist
- name: Restore generated outputs from downloaded build
- name: Generate test artifacts from the downloaded runtime
run: |
node scripts/generate-mcp-tool-catalog.mjs
node scripts/generate-skill-metadata.mjs
@@ -329,10 +329,12 @@ jobs:
run: npm run test:focused -- tests/boundary/cli/nativePlistCli.test.ts tests/boundary/cli/nativePlistPrototypeKeyCli.test.ts tests/boundary/cli/nativePlistSelectionFailureCli.test.ts
- name: Native Swift demangling CLI and MCP regressions
run: npm run test:focused -- tests/boundary/cli/nativeDemangleCli.test.ts tests/boundary/mcp/nativeDemangleMcp.test.ts
- name: Native signature target binding and CLI/MCP recovery regressions
run: npm run test:focused -- tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts tests/boundary/cli/nativeSignatureCli.test.ts tests/boundary/mcp/nativeSignatureTargetBindingMcp.test.ts
- name: Process filesystem snapshot descriptor regressions
run: npm run test:focused -- src/process/capture/FilesystemSnapshot.test.ts
- name: Native Darwin ownership and process cleanup regressions
run: npm run test:focused -- tests/boundary/process/darwinProcessRunTokenReader.test.ts tests/boundary/process/processCaptureLifecycle.test.ts tests/boundary/cli/processCli.test.ts tests/boundary/mcp/sessionMcp.test.ts
run: npm run test:focused -- tests/boundary/process/darwinProcessRunTokenReader.test.ts tests/boundary/process/directAnalysisSignals.test.ts tests/boundary/process/processCaptureLifecycle.test.ts tests/boundary/process/processCli.test.ts tests/boundary/mcp/sessionMcp.test.ts
reconstruction-readiness:
name: Reconstruction readiness
@@ -448,8 +450,8 @@ jobs:
tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts
tests/boundary/filesystem/setupUnavailableProvider.test.ts
tests/boundary/filesystem/windowsNpxRegistration.test.ts
src/application/ClientRegistrationIdentity.test.ts
tests/boundary/filesystem/webScriptManifestPaths.test.ts
tests/boundary/process/ProcessEvidence.host.test.ts
tests/acceptance/webModuleCli.test.ts
tests/acceptance/webSourceLocationCli.test.ts
tests/boundary/cli/referenceSourceImportStatus.test.ts
@@ -473,6 +475,21 @@ jobs:
tests/conformance/providers/ghidra/realGhidraWindowsWorkflow.test.ts
- run: npm run verify:windows-native
- run: npm run verify:package:windows
- name: Verify native directory identity on Node 24
run: >-
npx vitest run --coverage.enabled=false
tests/boundary/filesystem/directoryArtifactReaderOpen.test.ts
tests/boundary/filesystem/javascriptPackagePrecedence.test.ts
tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts
- uses: actions/setup-node@v4
with:
node-version: 22.19.0
- name: Verify native directory identity on Node 22
run: >-
npx vitest run --coverage.enabled=false
tests/boundary/filesystem/directoryArtifactReaderOpen.test.ts
tests/boundary/filesystem/javascriptPackagePrecedence.test.ts
tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts
- uses: actions/upload-artifact@v4
if: always()
with:
+97 -8
View File
@@ -1,6 +1,11 @@
name: Release
on:
push:
branches: [main]
pull_request:
branches: [main]
types: [closed]
workflow_dispatch:
inputs:
release_branch:
@@ -18,12 +23,89 @@ env:
HUSKY: "0"
concurrency:
group: release-${{ inputs.release_branch }}
group: release-${{ inputs.release_branch || github.event.pull_request.merge_commit_sha || github.ref_name }}
cancel-in-progress: false
jobs:
release-proposal:
name: Update release proposal
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: googleapis/release-please-action@v4
with:
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
target-branch: main
skip-github-release: true
merged-release:
name: Release reviewed main merge
if: >-
github.event_name == 'pull_request' && github.event.pull_request.merged == true &&
github.event.pull_request.head.repo.full_name == github.repository &&
startsWith(github.event.pull_request.head.ref, 'release-please--branches--main--')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.release.outputs.release_created }}
sha: ${{ steps.release.outputs.sha }}
steps:
- name: Check out reviewed release merge
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
package-manager-cache: false
- run: npm ci
- name: Validate reviewed merge before release creation
env:
SOURCE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
run: |
if [[ "${SOURCE_SHA}" != "${GITHUB_SHA}" ]]; then
echo "The reviewed merge differs from the workflow source; refusing mismatched publication provenance." >&2
exit 1
fi
node scripts/verify-release-checkpoint.mjs \
--phase publish --release-branch main --source-sha "$SOURCE_SHA"
- uses: googleapis/release-please-action@v4
id: release
with:
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
target-branch: main
skip-github-release: false
skip-github-pull-request: true
- name: Bind publication to the reviewed merge
env:
RELEASE_CREATED: ${{ steps.release.outputs.release_created }}
RELEASE_SHA: ${{ steps.release.outputs.sha }}
SOURCE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
run: |
if [[ "${RELEASE_CREATED}" != true ]]; then
echo "Release Please did not create a release for the reviewed merge. Check its pending label and release metadata; for a partial publication, re-run failed jobs in the original run." >&2
exit 1
fi
if [[ "${RELEASE_SHA}" != "${SOURCE_SHA}" ]]; then
echo "The tagged release differs from the reviewed merge; refusing mismatched publication provenance. The GitHub release already exists; do not create another release as a retry." >&2
exit 1
fi
release-please:
name: Release Please
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: write
@@ -176,8 +258,11 @@ jobs:
publish:
name: Publish to npm
needs: release-please
if: inputs.phase == 'publish' && needs.release-please.outputs.release_created == 'true'
needs: [merged-release, release-please]
if: >-
always() && !cancelled() &&
((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
permissions:
contents: read
@@ -185,7 +270,7 @@ jobs:
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.release-please.outputs.sha }}
ref: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
persist-credentials: false
- uses: actions/setup-node@v6
@@ -213,7 +298,8 @@ jobs:
if [ "${published_version}" = "\"${version}\"" ]; then
echo "rea-agents@${version} is already published; verifying the existing package."
else
npm publish --access public
tag="$(node scripts/release-npm-tag.mjs "${version}")"
npm publish --access public --tag "${tag}"
fi
- name: Verify published CLI from npm
@@ -247,8 +333,11 @@ jobs:
publish-mcp:
name: Publish to MCP Registry
needs: [release-please, publish]
if: inputs.phase == 'publish' && needs.release-please.outputs.release_created == 'true' && needs.publish.result == 'success'
needs: [merged-release, release-please, publish]
if: >-
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
permissions:
contents: read
@@ -256,7 +345,7 @@ jobs:
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.release-please.outputs.sha }}
ref: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
persist-credentials: false
- name: Verify package and registry metadata versions
+122 -1
View File
@@ -2,6 +2,15 @@ name: Publish REA website
on:
workflow_dispatch:
inputs:
target:
description: Publish both hosts, or Pages while configuring Cloudflare
type: choice
required: true
default: both
options:
- both
- pages
permissions:
contents: read
@@ -19,12 +28,42 @@ jobs:
contents: read
pages: read
steps:
- name: Validate the publication target
env:
PUBLISH_TARGET: ${{ inputs.target }}
run: |
case "$PUBLISH_TARGET" in
both|pages) ;;
*) echo "::error::Unknown publication target: $PUBLISH_TARGET"; exit 1 ;;
esac
- name: Require Cloudflare publishing credentials
if: inputs.target == 'both'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
if [[ -z "$CLOUDFLARE_API_TOKEN" || -z "$CLOUDFLARE_ACCOUNT_ID" ]]; then
echo "::error::Set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID in repository Actions secrets before publishing."
exit 1
fi
- name: Check out the repository
uses: actions/checkout@v4
- name: Prepare the example download
run: python3 scripts/prepare-website.py
- name: Check website links and publishing configuration
run: python3 scripts/verify-website.py
- name: Record the published commit
run: |
python3 - <<'PY'
import json
import os
from pathlib import Path
Path("website/public/deployment.json").write_text(
json.dumps({"commit": os.environ["GITHUB_SHA"]}) + "\n",
encoding="utf-8",
)
PY
- name: Configure Pages
uses: actions/configure-pages@v5
- name: Upload the website
@@ -35,14 +74,96 @@ jobs:
deploy:
needs: prepare
runs-on: ubuntu-latest
timeout-minutes: 5
timeout-minutes: 15
permissions:
contents: read
actions: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Check out publishing configuration
if: inputs.target == 'both'
uses: actions/checkout@v4
- name: Set up Node.js for Wrangler
if: inputs.target == 'both'
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Download the verified website
if: inputs.target == 'both'
uses: actions/download-artifact@v4
with:
name: github-pages
path: ${{ runner.temp }}/website-archive
- name: Extract the shared publishing artifact
if: inputs.target == 'both'
run: |
mkdir -p "$RUNNER_TEMP/rea-website-public"
tar -xf "$RUNNER_TEMP/website-archive/artifact.tar" -C "$RUNNER_TEMP/rea-website-public"
- name: Publish the website to Cloudflare
if: inputs.target == 'both'
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
wranglerVersion: "4.149.0"
packageManager: npm
command: deploy --cwd website --env production --assets "${{ runner.temp }}/rea-website-public"
- name: Publish to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
- name: Verify the selected published revisions
env:
PAGES_URL: ${{ steps.deployment.outputs.page_url }}
PUBLISH_TARGET: ${{ inputs.target }}
run: |
python3 -u - <<'PY'
import json
import os
import sys
import time
from urllib.error import URLError
from urllib.parse import urljoin
from urllib.request import Request, urlopen
expected = os.environ["GITHUB_SHA"]
pages_url = os.environ["PAGES_URL"]
if not pages_url:
sys.exit("GitHub Pages returned no published URL.")
target = os.environ["PUBLISH_TARGET"]
if target not in ("both", "pages"):
sys.exit("Unknown publication target: " + target)
hosts = {"GitHub Pages": pages_url}
if target == "both":
hosts["Cloudflare"] = "https://rea.tools/"
pending = dict(hosts)
for attempt in range(24):
for name, base in list(pending.items()):
url = urljoin(base, "deployment.json") + "?commit=" + expected
try:
request = Request(url, headers={
"Cache-Control": "no-cache",
"User-Agent": "REA-Website-Verifier/1.0 (+https://github.com/morluto/rea)",
})
with urlopen(request, timeout=10) as response:
actual = json.load(response).get("commit")
if actual == expected:
print(f"{name} serves {expected}: {base}")
del pending[name]
else:
print(f"Waiting for {name}: observed revision {actual}")
except (URLError, TimeoutError, ValueError) as error:
print(f"Waiting for {name}: {error}")
if not pending:
break
if attempt < 23:
time.sleep(5)
if pending:
sys.exit("Could not verify published revision on: " + ", ".join(pending))
with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as summary:
links = " and ".join(f"[{name}]({url})" for name, url in hosts.items())
summary.write(f"Published commit `{expected}` to {links}.\n")
PY
+8 -3
View File
@@ -49,12 +49,17 @@ Thumbs.db
# Generated website download
/website/public/examples/notes-example.zip
/website/public/deployment.json
# Build-generated MCP tool catalog (regenerated by `npm run build`; never commit)
src/generatedMcpToolCatalog.ts
# Local Cloudflare development state
/website/.wrangler/
/website/.dev.vars*
/website/.env*
!/website/.dev.vars.example
!/website/.env*.example
# Derived catalogs, portable conformance projections, and packaged skill bundles
# (regenerated from this checkout by `npm run build:cached`; never commit)
# (regenerated by `npm run docs:generate`; never commit)
/skills/
/docs/public/product-catalog.json
/docs/verification/managed-conformance-manifest.json
+1 -1
View File
@@ -1,3 +1,3 @@
{
".": "6.0.0"
".": "6.1.0"
}
+14 -6
View File
@@ -2,7 +2,7 @@
## Product Direction
REA exposes reverse-engineering tools through a CLI and MCP server. Hopper, the bring-your-own Ghidra adapter, and the bring-your-own IDA MCP adapter are operation-capable deep binary-analysis providers. Ghidra is supported on Linux x64 and macOS x64/arm64 with matching native decompiler tools, and has an experimental Windows x64 P0 boundary for approved native x86 and x86-64 PE applications; on Linux and macOS it supplies inventory, function analysis, and atomic function annotation edits in an ephemeral database, without modifying executable bytes or controlling a GUI. Windows P0 has no mutation authority. IDA adapts upstream legacy attached GUI and modern headless database-supervisor profiles for live read-only analysis; initial real verification covers Windows, and an attached GUI database is never saved or closed. Keep provider-specific code out of the domain and application layers.
REA exposes reverse-engineering tools through a CLI and MCP server. Hopper, the bring-your-own Ghidra adapter, and the bring-your-own IDA MCP adapter are operation-capable deep binary-analysis providers. Ghidra is supported on Linux x64/arm64 and macOS x64/arm64 with matching native decompiler tools, and has an experimental Windows x64 P0 boundary for approved native x86 and x86-64 PE applications; on Linux and macOS it supplies inventory, function analysis, and atomic function annotation edits in an ephemeral database, without modifying executable bytes or controlling a GUI. Windows P0 has no mutation authority. IDA adapts upstream legacy attached GUI and modern headless database-supervisor profiles for live read-only analysis; initial real verification covers Windows, and an attached GUI database is never saved or closed. Keep provider-specific code out of the domain and application layers.
Prioritize:
@@ -24,15 +24,15 @@ REA is a layered ESM TypeScript application. Dependencies flow inward from pure
- `src/application/` composes shared CLI/MCP workflows, including Evidence provenance, unknowns, and eligible snapshot bindings; `src/server/` translates MCP requests; `src/cli.ts` and `src/main.ts` are the CLI and MCP entry points.
- `src/process/` owns shared process lifecycle primitives, not provider wire protocols. Reuse its supervision and identity primitives before adding provider-local lifecycle code; a PID and executable pathname alone do not establish ownership after exit or reuse. `bridge/` contains provider-side adapters.
- `tests/` contains unit, composition, boundary, acceptance, and conformance tests. `scripts/verify-*` and capability directories under `scripts/verify/` contain real-toolchain checks.
- `docs/public/product-catalog.json`, `docs/verification/managed-conformance-*.json`, and `skills/` are ignored build outputs. Update source contracts and authored instructions in `skill-src/`, then run `npm run build:cached`; never commit derived catalog digests or portable conformance projections.
- `src/generatedMcpToolCatalog.ts` is build-generated and gitignored. Never commit it; resolve any trace of it in merges by deleting it and running `npm run build:cached`.
- `docs/public/product-catalog.json`, `docs/verification/managed-conformance-*.json`, and `skills/` are ignored build outputs. Update source contracts and authored instructions in `skill-src/`, then run `npm run docs:generate`; never commit derived catalog digests or portable conformance projections.
- `.cache/mcp-tool-catalog.json` is generated test metadata. Generate it with `npm run mcp-catalog:generate`; source checking and runtime builds do not consume it.
## Build, Test, and Development Commands
- `npm ci`: install the locked dependencies.
- `npm run build:cached`: build the CLI and MCP server.
- `npm run build:cached`: compile the CLI/MCP runtime and bundle its authored skill. Test catalogs and documentation/conformance artifacts have separate tasks.
- `npm run test:local`: run changed source tests without building; pass exact source test paths to run them regardless of Git status.
- `npm run test:focused -- PATH...`: run exact test files; build first for boundary, acceptance, or process-global tests.
- `npm run test:focused -- PATH...`: run exact test files, preparing runtime and generated artifacts required by the selected tests.
- `npm run check:changed`: run cached static checks and source tests affected since the branch merge base (default `origin/main`).
- `npm run check:fast`: run cached typecheck and lint checks.
- `npm run check:pr`: opt into the complete local deterministic gate and generated-document checks for broad changes; CI owns full coverage. Routine iterations need focused tests and relevant checks, not the whole gate each time.
@@ -50,6 +50,8 @@ Configuration is parsed and validated by `src/config.ts` and `src/config/`. Keep
Use ESM TypeScript, two-space indentation, and the committed Oxfmt configuration. Keep compiler strictness intact (`strict`, `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`, `verbatimModuleSyntax`). Use `camelCase` for values/functions, `PascalCase` for classes/types, and `UPPER_SNAKE_CASE` for constants. Parse unknown values at every MCP, environment, and subprocess boundary. Avoid `any`, unchecked casts, non-null assertions, import-time I/O, and floating promises. Exported APIs require concise JSDoc. Model expected failures with the tagged error algebra and `Result`, not broad exception wrappers.
Assess brittleness during implementation, debugging, review, and maintenance. Simplify duplicated rules, scattered defaults, caller-specific exceptions, and unclear invariants in touched code. Prefer standard format-aware parsers and existing shared helpers; normalize once at the owning boundary while preserving source evidence. Keep refactors focused on the task, and introduce abstractions only when they remove observed complexity. Verify preserved behavior and corrected edge cases through affected callers.
## Boundary Contracts
Treat a boundary as a contract between the producer's actual representation and the consumer's required meaning. When implementing or auditing a boundary, trace the value through parsing, normalization, authorization, serialization, and the CLI/MCP result. Establish affected callers from their code paths; similar tool names or workflows do not prove that they share a schema or failure mode.
@@ -60,6 +62,8 @@ Keep portable evidence and scenario validation distinct from host-native executi
Preserve meaningful failure reasons through application and adapter layers. Malformed input is distinct from an unsupported target, unavailable provider, or host operating-system permission denial. Diagnostics should identify the failed constraint and the target or lifecycle request it applies to. Recovery advice must address that reason and point to an available workflow; generic catches must not erase actionable validation details. Preserve collected observations on execution failure independently of whether cleanup succeeds.
Preserve partial native facts. A shared schema must not require one provider's full metadata before another provider can report the facts it observes. Keep observed classifications and endpoints alongside explicit unknowns for unsupported flags; never invent missing flags or discard known facts to satisfy an all-or-nothing availability shape. Before claiming a native API lacks a capability, inspect the installed API and probe representative producer objects. Exercise the resulting partial representation through CLI, MCP, and composed results such as function dossiers.
Leave meaningful target, action, capture, and output choices to the agent. A selected operation already expresses intent; do not require approval booleans or repeated permission declarations. Trace each setting to its consumer: remove ignored options and single-value confirmations, derive built-in lifecycle behavior, and supply defaults for omitted optional metadata. Report actual effects and limitations where they help interpret results rather than asking callers to restate them.
Verify the representation consumed at the next boundary. Internal validator success does not establish JSON Schema validity or client compatibility: validate advertised input and output schemas against their declared dialect after SDK conversion, and keep generated contracts aligned. Use representative producer data and regressions that exercise the failed behavior. Platform and provider support claims require the corresponding real workflow; package startup, capability probes, and mock transport tests establish only their narrower claims. Report unverified coverage explicitly.
@@ -90,6 +94,10 @@ See [docs/tool-design.md](docs/tool-design.md) for the design checklist. When us
## Testing Guidelines
Prefer full end-to-end workflows through the public CLI or MCP with real providers and no mocked dependencies, then integration across production boundaries, then goldens captured from real producers. Keep a focused module test only for a distinct failure or semantic case that stronger workflows cannot reliably reproduce. Delete redundant getter, serialization, enum, count, and snapshot assertions when a stronger consumer workflow already proves the claim. Test paths, compiled imports, and suites named E2E do not establish end-to-end coverage. See [docs/testing.md](docs/testing.md) for pruning and classification rules.
Treat advertised examples as executable contracts: derive them from representative producer data, exercise them through the advertised CLI or MCP workflow, and assert the behavior they claim to demonstrate. Share production projections where practical and declare omitted coverage explicitly.
Name tests `*.test.ts`. Use Vitest and production seams (`tests/fixtures/`) rather than module mocks. Domain tests assert pure behavior; adapter tests use fake launcher/socket seams; MCP tests connect with the client SDK version pinned in `package.json`. Preserve the canonical tool inventory defined by `TOOL_CONTRACTS` and verified through `CATALOG_IDENTITY` and generated product metadata. Cover malformed input, cancellation, lifecycle cleanup, and actual format, protocol, host-permission, and target-identity boundaries. Do not add tests that merely freeze arbitrary caps or prescribed call sequences. Real Hopper, Ghidra, browser, managed conformance, and any real managed-tool claims cannot be replaced by mocks; use the corresponding `verify:*` command.
Keep tool catalogs complete and self-describing; prefer capability- and session-scoped availability over schema truncation. Serialized bytes alone do not measure agent usability or model context cost.
@@ -102,7 +110,7 @@ Development requires Node.js 24.18.0 and npm 11.16.0 (`.nvmrc` and `packageManag
## Commit & Pull Request Guidelines
Use Conventional Commit subjects because Release Please derives versions and changelogs from them. Examples: `feat: add historical source import`, `fix(process): stop timers after exit`, and `docs: update architecture`. Use `!` or a `BREAKING CHANGE:` footer for breaking changes. Pull request titles must follow the same format because squash merges use the title as the release commit. Pull requests should describe contract or behavior changes, list verification commands, link issues, and include sanitized MCP examples when schemas change. State whether real Hopper/Ghidra verification was performed. Never commit binaries, Hopper or Ghidra project documents, credentials, `dist/`, `node_modules/`, or local planning artifacts (e.g. `.codex/`).
Use Conventional Commit subjects because Release Please derives changelogs from them. Release Please uses `always-bump-minor`: every release increments minor, including breaking changes. Examples: `feat: add historical source import`, `fix(process): stop timers after exit`, and `docs: update architecture`. Use `!` or a `BREAKING CHANGE:` footer for breaking changes. Pull request titles must follow the same format because squash merges use the title as the release commit. Pull requests should describe contract or behavior changes, list verification commands, link issues, and include sanitized MCP examples when schemas change. State whether real Hopper/Ghidra verification was performed. Never commit binaries, Hopper or Ghidra project documents, credentials, `dist/`, `node_modules/`, or local planning artifacts (e.g. `.codex/`).
<!-- BEGIN:turborepo-agent-rules -->
+223
View File
@@ -1,5 +1,228 @@
# Changelog
## [6.1.0](https://github.com/morluto/rea/compare/rea-agents-6.0.0...rea-agents-6.1.0) (2026-10-09)
### ⚠ BREAKING CHANGES
* **hopper:** Hopper regex mode uses ECMAScript Unicode syntax instead of Python re. Matching runs in a cancellable worker with a five-second deadline; literal mode keeps Unicode casefold matching.
* **hopper:** remove set_current_document. Use open_binary to switch targets; document arguments are scoped to the active target.
### Features
* **clients:** register Grok Build and Grok Bot during setup ([#1134](https://github.com/morluto/rea/issues/1134)) ([08561e0](https://github.com/morluto/rea/commit/08561e0eb7a4a01a256f3693a97061fbea7fb8fa))
* **ghidra:** make the startup deadline configurable ([#974](https://github.com/morluto/rea/issues/974)) ([d84bc8f](https://github.com/morluto/rea/commit/d84bc8f11abd6cbf5612daf5c8f7e1805d23c16e))
* **website:** add GitHub link with live star count ([2f59287](https://github.com/morluto/rea/commit/2f59287f1a1794a68102a45c61cc657659fa5de6))
### Bug Fixes
* **analysis:** bind overview document identity to the active target ([#1094](https://github.com/morluto/rea/issues/1094)) ([81b5104](https://github.com/morluto/rea/commit/81b51042767ad75310bad1a7a9faf954339841c0))
* **analysis:** resolve procedure identities and preserve literal queries ([d830cb7](https://github.com/morluto/rea/commit/d830cb79b305e6d4d255f8080de69ca668cbe0af))
* **android:** report actual provider readiness ([c715af8](https://github.com/morluto/rea/commit/c715af80587f808de816d3add6a6f989437ab66a))
* **android:** report startup and selector failure causes ([6c13fc8](https://github.com/morluto/rea/commit/6c13fc8e0a318ebe7be6b038089b350d99f8e286))
* **apple:** account for repeated archive projections ([e1df5e8](https://github.com/morluto/rea/commit/e1df5e85551125b27ef0debb7573de5507b149e1))
* **apple:** budget Interface Builder decoding and projection ([dbcf8c3](https://github.com/morluto/rea/commit/dbcf8c33915d924fdbc881bf2cf9676587f3b96f))
* **apple:** classify empty dyld settings by consumer semantics ([#1110](https://github.com/morluto/rea/issues/1110)) ([fe297e5](https://github.com/morluto/rea/commit/fe297e56e804130dd49cc0a95fcf2d115c183ae3))
* **apple:** decode bundle plist bytes before executable lookup ([d575a73](https://github.com/morluto/rea/commit/d575a7340bb19b84733c79b14de9f4df7181c6b3))
* **apple:** decode Interface Builder XML byte encodings ([#1095](https://github.com/morluto/rea/issues/1095)) ([f8e143a](https://github.com/morluto/rea/commit/f8e143a3bcd20ddcc0d76d90e73e238d456fe5a3))
* **apple:** derive dispatch coverage from located decode facts ([488f448](https://github.com/morluto/rea/commit/488f448ae27fd62904967ec1e0ab072e834a9822))
* **apple:** keep skipped catalog entries from reading as absence ([a079af2](https://github.com/morluto/rea/commit/a079af297bb454417dfd3d89b0037397554ad83e))
* **apple:** report apps without asset catalogs as empty inventories ([e2f0378](https://github.com/morluto/rea/commit/e2f03786f496ed742572acd87f47270c958171ec))
* **apple:** report apps without asset catalogs as empty inventories ([79e5a39](https://github.com/morluto/rea/commit/79e5a3923db896323c0491e31004214856529b74))
* **apple:** report missing keyed archive hierarchy references ([#1104](https://github.com/morluto/rea/issues/1104)) ([3b2ec69](https://github.com/morluto/rea/commit/3b2ec69c54fc9cf65bf8441edbb96e9619c3a37e))
* **apple:** validate dylib candidates and preserve search uncertainty ([#1100](https://github.com/morluto/rea/issues/1100)) ([7914e36](https://github.com/morluto/rea/commit/7914e36ab9adbbf591a6e1f3fafcb2e17f55d667))
* **apple:** validate physical slices and dylib platform compatibility ([15f92f2](https://github.com/morluto/rea/commit/15f92f298891c8b16d7d13c78cb6ca66cb90cd85))
* **application:** budget bridge candidate projection by bytes ([3d450a2](https://github.com/morluto/rea/commit/3d450a29fc91cef3e06ffce9a925ac70dcb24272))
* **artifacts:** preserve keyed archive integer precision ([4034883](https://github.com/morluto/rea/commit/4034883e9860a97edee03260fab59f9229debfa6))
* **artifacts:** report keyed-archive selection mistakes as invalid input ([5a55d5b](https://github.com/morluto/rea/commit/5a55d5b1c6aefb4a4e5df63ec43b7e2fc0a7d385))
* **artifacts:** report keyed-archive selection mistakes as invalid input ([25fb6d3](https://github.com/morluto/rea/commit/25fb6d32a325d88bb0f4e05e16f5f82e4f91c5ca))
* **artifacts:** retain failed DMG cleanup state and diagnostics ([#1103](https://github.com/morluto/rea/issues/1103)) ([4bb056b](https://github.com/morluto/rea/commit/4bb056bbb5b735e214ed8d725aa3de1c06fb0dc9))
* **browser:** advertise zero-based units for runtime source coordinates ([#1085](https://github.com/morluto/rea/issues/1085)) ([6218d28](https://github.com/morluto/rea/commit/6218d289e241715d0868004caafe15b5c4758450))
* **browser:** bind correlated replies to their selected session ([96d3caf](https://github.com/morluto/rea/commit/96d3cafb0725881b6cdab0e2185104957dc69f71))
* **browser:** bound aggregate source-map decoding before expansion ([f983c71](https://github.com/morluto/rea/commit/f983c718c33789ee40aec43f84315f6b53e95bde))
* **browser:** bound script metadata and preserve omissions ([faf209e](https://github.com/morluto/rea/commit/faf209e50d9faab03a185c1f044de3582552d11e))
* **browser:** bound source-map fetches and retain predecessor requests ([eaf5734](https://github.com/morluto/rea/commit/eaf57340831a753faec58246290f9502d159b6b1))
* **browser:** compose nested source-map column offsets ([#1109](https://github.com/morluto/rea/issues/1109)) ([8b08ec0](https://github.com/morluto/rea/commit/8b08ec0af936405fd20db25f941e2ae6318c5145))
* **browser:** keep initiator callsites out of script coordinate guidance ([#1096](https://github.com/morluto/rea/issues/1096)) ([3c157b4](https://github.com/morluto/rea/commit/3c157b4ec6297e6737bd3c2ec3dc520c7403402f))
* **browser:** normalize selected script manifest roots on Windows ([#1078](https://github.com/morluto/rea/issues/1078)) ([b49beb4](https://github.com/morluto/rea/commit/b49beb42d7a97f73d052a27fb99673b1bd0d3d39))
* **browser:** preserve captures when response bodies are unavailable ([96af2fa](https://github.com/morluto/rea/commit/96af2fa7444a3777d51ff46202ef563146929dbd))
* **browser:** preserve command failures and reject malformed replies ([6b92002](https://github.com/morluto/rea/commit/6b92002597d34555c32ebf24b9db6e4c17c0a08d))
* **browser:** treat module-import initiator positions as script positions ([ae0630a](https://github.com/morluto/rea/commit/ae0630a619ee2c98a68e65ca44bf0b93424e2f8e))
* **browser:** treat module-import initiator positions as script positions ([99d31ee](https://github.com/morluto/rea/commit/99d31ee24ad060babace9eca3325858418e0f55a))
* **browser:** validate and bound source-map evidence ([0a0174b](https://github.com/morluto/rea/commit/0a0174b9ac8a73b61094e857a2950f4e5a06f544))
* **build:** exclude the test-only generated catalog from packages ([#1116](https://github.com/morluto/rea/issues/1116)) ([d1c904b](https://github.com/morluto/rea/commit/d1c904bac7977d44b06bf2f823587baedcc88b9e))
* **build:** make catalogs and packaged skills generated outputs ([#1086](https://github.com/morluto/rea/issues/1086)) ([e613405](https://github.com/morluto/rea/commit/e6134056cf519410160a196363f8a04f0729324e))
* **capture:** retain Electron observations and HAR resource failures ([44fd183](https://github.com/morluto/rea/commit/44fd18305fa5a8893ebb5b952099fdf45dbd6e55))
* **ci:** align diagnostic regressions and format merged changes ([67756da](https://github.com/morluto/rea/commit/67756dacc0d435a61d2d0c403412151769da028f))
* **ci:** restore generated shard inputs and cleanup assertions ([5f93b4b](https://github.com/morluto/rea/commit/5f93b4b125e78f088a2349a178e60f8c0af3e1a2))
* **cli:** accept exact native names through explicit selectors ([c313606](https://github.com/morluto/rea/commit/c3136064c7f8f72b2f6c6799ec41ea6424111bb7))
* **cli:** drive mcp doctor help from a single option schema ([b7d803d](https://github.com/morluto/rea/commit/b7d803dd10c7ef8bb73f81f5e893168af8ea86e6))
* **cli:** preserve blank JSON paths for input validation ([#1068](https://github.com/morluto/rea/issues/1068)) ([3dcb732](https://github.com/morluto/rea/commit/3dcb732da33f6ceef597506b14a4536f1c9aff96))
* **cli:** preserve web cancellation exit status ([1d9b96a](https://github.com/morluto/rea/commit/1d9b96a9c497694592137cf95bea2af0a190abe7))
* **cli:** support MCP doctor help ([a69b833](https://github.com/morluto/rea/commit/a69b8337ab38707aaad2ac2fedb652f05afeb71c))
* **contracts:** preserve local inputs and comparison uncertainty ([70e8a8f](https://github.com/morluto/rea/commit/70e8a8f86060496452ccbc1a49b39822efef4113))
* **docs:** validate translated links to canonical product facts ([34f7c4f](https://github.com/morluto/rea/commit/34f7c4fd661e07db66c560a4d54272b0163a52b6))
* **domain:** order canonical paths by code point, not UTF-16 unit ([#1034](https://github.com/morluto/rea/issues/1034)) ([b5c2954](https://github.com/morluto/rea/commit/b5c2954ad67f0280732df043d5d9702f3d202819))
* **domain:** preserve prototype-named JSON members through Evidence boundaries ([#1049](https://github.com/morluto/rea/issues/1049)) ([40e18b2](https://github.com/morluto/rea/commit/40e18b2e37f2be11f452606c2d875981b03ad03e))
* **domain:** reject excessive JSON depth with actionable diagnostics ([#1029](https://github.com/morluto/rea/issues/1029)) ([bfbfc73](https://github.com/morluto/rea/commit/bfbfc7330aa7175018de04f1d099b8d4aeec3bd1))
* **electron:** bound hook retention and report dropped coverage ([02d0702](https://github.com/morluto/rea/commit/02d07027617a5df014f92a351aa9418aade6f06f))
* **evidence:** name the failed constraint when a bundle is rejected ([82a1a52](https://github.com/morluto/rea/commit/82a1a525d7aa725d939926ba953434fbf5db0ab0))
* **evidence:** name the failed constraint when a bundle is rejected ([cf96ceb](https://github.com/morluto/rea/commit/cf96ceb2119f52f61a621d2a790b7c8804c65595))
* **evidence:** report missing evidence files instead of permission advice ([e8b92a4](https://github.com/morluto/rea/commit/e8b92a40c003a5e6ce3cefe4ad18392d39759653))
* **evidence:** report missing evidence files instead of permission advice ([89252f1](https://github.com/morluto/rea/commit/89252f1f35de91494bd49460b1645d8c09cc6d33))
* **evidence:** require a complete record before the single-record hint ([38bbb65](https://github.com/morluto/rea/commit/38bbb656da2659088e30a1ca3e4434b2f65bab32))
* **firmware:** reject malformed UTF-8 producer reports ([4a45c14](https://github.com/morluto/rea/commit/4a45c14742e6868f687cead2ddf736c3f7c49acd))
* **ghidra:** admit Linux arm64 with matching native tools ([3e598b2](https://github.com/morluto/rea/commit/3e598b2d38a728cd9fd4a359741500a021ba9d3a))
* **ghidra:** harden location resolution and provider lifecycle ([1f5be75](https://github.com/morluto/rea/commit/1f5be750bced759339183945488cea352b18f28b))
* **ghidra:** make qualified function renames idempotent ([4458128](https://github.com/morluto/rea/commit/4458128a845c1448140b44d6e62d6ccfe611d37c))
* **ghidra:** parse long procedure selectors without recursion ([480b16b](https://github.com/morluto/rea/commit/480b16b9c151e1d4bfbca8b49a0668d74ceaa400))
* **ghidra:** preserve function references and snapshot boundaries ([84a17d5](https://github.com/morluto/rea/commit/84a17d55199b1b46493aea6bf4e41029a3b200fc))
* **ghidra:** preserve POSIX runtime paths with spaces ([80c3d36](https://github.com/morluto/rea/commit/80c3d36dfa2597f34fdeb69e0ba75277ee50c85c))
* **ghidra:** preserve source failures and snapshot ownership ([704fa76](https://github.com/morluto/rea/commit/704fa7699ca183d5d5e0518a6fcdbb953ec35a10))
* **ghidra:** preserve Unicode boundaries without breaking sessions ([bef234d](https://github.com/morluto/rea/commit/bef234d6ce63cd296a654400443fcdbbcd44d7de))
* **ghidra:** recover regex stack exhaustion without losing annotations ([bc4aa46](https://github.com/morluto/rea/commit/bc4aa46d7c3960a1f325523e48aed2089b176e84))
* **ghidra:** reject address truncation before reads and edits ([dcae3c6](https://github.com/morluto/rea/commit/dcae3c6c148582d235f5650aace6f9744eeb153c))
* **ghidra:** resolve exact external function entries ([549effa](https://github.com/morluto/rea/commit/549effa056729e5f9239d9ed7864537a9c49e421))
* **hopper:** bind analysis to the active target and correct boundary contracts ([2f71375](https://github.com/morluto/rea/commit/2f71375efc517bb9e6f9800f8e8a6849766bc893))
* **hopper:** bind file mappings to verified source bytes ([82626c4](https://github.com/morluto/rea/commit/82626c4499a53c227187575d40a845fadd5318e2))
* **hopper:** diagnose MCP provider failures with stage, state, and request context ([#1044](https://github.com/morluto/rea/issues/1044)) ([5a67c7f](https://github.com/morluto/rea/commit/5a67c7fc540c494371de76dcdf353923656993d3))
* **hopper:** enforce caller-supplied request deadlines ([#1119](https://github.com/morluto/rea/issues/1119)) ([e8939ca](https://github.com/morluto/rea/commit/e8939ca2208bc41454b73b90a32418714e6a2969))
* **hopper:** isolate regex matching from the native API thread ([f201b04](https://github.com/morluto/rea/commit/f201b04fbbb29591b0d2bb3f3b24636526e4ec4e))
* **hopper:** load verified FAT64 slices with native Mach-O semantics ([0a49d5b](https://github.com/morluto/rea/commit/0a49d5bfb931124d3d398dfbd64e9f6ae8a01405))
* **hopper:** normalize native block endpoints and retain terminal evidence ([2847311](https://github.com/morluto/rea/commit/2847311a579ce2da4c8ded4d89fcdd01545e6da5))
* **hopper:** preserve launcher evidence on readiness timeout ([458771c](https://github.com/morluto/rea/commit/458771cb583e38b848b3d8c58595d163143ff50a))
* **hopper:** preserve native navigation and annotation semantics ([f2e22e1](https://github.com/morluto/rea/commit/f2e22e13a6956985bd65a2d75e4db7e3128cce81))
* **hopper:** preserve owned basic-block endpoint instructions ([#1118](https://github.com/morluto/rea/issues/1118)) ([329cf20](https://github.com/morluto/rea/commit/329cf206d1111c051733c7115cabff4b46722deb))
* **hopper:** preserve partial native call and string evidence ([2461702](https://github.com/morluto/rea/commit/246170238ebb8669a20b57b559b03cfc26ee1b8c))
* **hopper:** preserve typed string values and verify real searches ([7d7b3f9](https://github.com/morluto/rea/commit/7d7b3f961af2dc9b7572e91aaec29e6c4eccf9d5))
* **hopper:** report launcher failures before bridge timeout ([#1105](https://github.com/morluto/rea/issues/1105)) ([cd756b0](https://github.com/morluto/rea/commit/cd756b07d5b9c06e085d778e9f1c4fc9bd403219))
* **hopper:** resolve FAT loaders and original file coordinates ([35f1ddd](https://github.com/morluto/rea/commit/35f1ddd6bc77588b9971961cd70968a815aae266))
* **hopper:** validate annotation destinations and symbol extents ([b2d5bc1](https://github.com/morluto/rea/commit/b2d5bc110914f2ad42d17bc739d5f37c1b0f0a4d))
* **hopper:** verify lease cleanup and repair the macOS test lane ([#1111](https://github.com/morluto/rea/issues/1111)) ([b743bd2](https://github.com/morluto/rea/commit/b743bd20a0cbc39728e2e822fa75c013d87e1730))
* **inspector:** bound retained metadata and preserve cancellation ([e714fb0](https://github.com/morluto/rea/commit/e714fb027afdf11bfbb0838722523d3fa9ebf244))
* **install:** guard empty prefix_args expansion on macOS Bash 3.2 under set -u ([#1061](https://github.com/morluto/rea/issues/1061)) ([68b9fa4](https://github.com/morluto/rea/commit/68b9fa489b0c07f580633785ec61c17fa20b5083))
* **javascript:** bound primitive string growth and repeated evaluation ([4a1837d](https://github.com/morluto/rea/commit/4a1837d9d52b1a6454469e2dc63eeb8d716fb296))
* **javascript:** bound semantic expansion and resolve lexical receivers ([16f4609](https://github.com/morluto/rea/commit/16f4609d689445c05a0befc744fce7e72f7047ec))
* **javascript:** classify direct defaults and argv elements ([6495171](https://github.com/morluto/rea/commit/649517181268a0b729970b7cbbda15e04b199e32))
* **javascript:** honor cancellation before evidence publication ([#1098](https://github.com/morluto/rea/issues/1098)) ([cc0ee2e](https://github.com/morluto/rea/commit/cc0ee2edc60dda42d7b7cfb2c08e39b0b736b5e9))
* **javascript:** omit self-referential import edges with disclosure ([#1130](https://github.com/morluto/rea/issues/1130)) ([1d7115b](https://github.com/morluto/rea/commit/1d7115b7b470719a32e477ca3cbb681f0ad408b8))
* **javascript:** parse plain TypeScript artifact dialects ([#1102](https://github.com/morluto/rea/issues/1102)) ([3db4681](https://github.com/morluto/rea/commit/3db46813f80f84504c5b87c73a4bece82a8fdc5e))
* **javascript:** preserve HTML carriage return source ranges ([#1092](https://github.com/morluto/rea/issues/1092)) ([23862af](https://github.com/morluto/rea/commit/23862af1b57413e09abf394f6e4f8369f6355d5a))
* **javascript:** preserve lexical open and exact template semantics ([d6cc9ca](https://github.com/morluto/rea/commit/d6cc9ca3a6e92604c837f61c193baf5abedaabc8))
* **javascript:** preserve original BOM source coordinates ([#1093](https://github.com/morluto/rea/issues/1093)) ([83defc0](https://github.com/morluto/rea/commit/83defc054b69e94da888d70bb3b05d835e1bf74d))
* **javascript:** retain NodeNext artifact source facts ([#1087](https://github.com/morluto/rea/issues/1087)) ([c1b370c](https://github.com/morluto/rea/commit/c1b370ce0dfcbd5725cf6aebe23b022dfc75ac84))
* **javascript:** use Node legacy package entry fields ([1706005](https://github.com/morluto/rea/commit/170600541bf44f2a63731e8b3d42aae3cbb8d6b3))
* **managed:** bind inspection evidence to artifact identity ([73b09f4](https://github.com/morluto/rea/commit/73b09f4c5c511ce80baa0c89717181bbe0ecba8f))
* **managed:** match exact method bytes without decoded signatures ([17809ca](https://github.com/morluto/rea/commit/17809caacd0f44fcc7c1af42932ea655489d64ae))
* **mcp:** flatten root union input schemas ([#1062](https://github.com/morluto/rea/issues/1062)) ([6a7650e](https://github.com/morluto/rea/commit/6a7650e93cba9169baad95d2c52b11351ae39d0e))
* **mcp:** preserve retained evidence on oversized delivery ([0639a54](https://github.com/morluto/rea/commit/0639a541a3b712940119f59e8189bc962b267726))
* **mcp:** retain complete application results across response limits ([a1ed423](https://github.com/morluto/rea/commit/a1ed4232de635d4acc0dd9eb293e309a29d03e80))
* **mcp:** retain oversized errors before transport delivery ([b3f1b4e](https://github.com/morluto/rea/commit/b3f1b4e822aec5626d1952a88f50c7520dc265d3))
* **metadata:** stop schema changes churning generated skill evidence ([#1075](https://github.com/morluto/rea/issues/1075)) ([d52f66d](https://github.com/morluto/rea/commit/d52f66d62a93ec09cccdf53e64e3ccd24d16e520))
* **native:** bind signature observations to registered target versions ([#1099](https://github.com/morluto/rea/issues/1099)) ([c789af0](https://github.com/morluto/rea/commit/c789af04b76c0e76a522a5dece34f461288f5ecd))
* **native:** bound retained observations and preserve failure evidence ([7c32387](https://github.com/morluto/rea/commit/7c323870c2dcab78654ee7a80128b06affdf2bbe))
* **native:** preserve bridge failure types during cleanup ([1d0baa3](https://github.com/morluto/rea/commit/1d0baa352fc76d1760abbe29a3cc80fa17296916))
* **native:** resample tool identity for each invocation ([14b19a0](https://github.com/morluto/rea/commit/14b19a0b8a5af401212c552173e761eac53bfe46))
* **native:** share command supervision and retain failure output ([f2e9c49](https://github.com/morluto/rea/commit/f2e9c491ea00bf79d5665102d95a3339b63cfe4f))
* **native:** supervise LLDB and preserve bounded capture uncertainty ([915855e](https://github.com/morluto/rea/commit/915855e75ca3fe3b1b54978ace6bbe10d1973f64))
* **process:** avoid cold ownership helper deadline exhaustion ([#1139](https://github.com/morluto/rea/issues/1139)) ([65a809a](https://github.com/morluto/rea/commit/65a809a0da0555aa8eeafbc2ec2559517471be02))
* **process:** derive executable names from the capture host ([#1108](https://github.com/morluto/rea/issues/1108)) ([34cc925](https://github.com/morluto/rea/commit/34cc9259c1076a0d9575a87679befca692ec86c2))
* **process:** identify unknown live processes in cleanup diagnostics ([eb89a16](https://github.com/morluto/rea/commit/eb89a16d9bc51b65dcc75e05f8492de894320753))
* **process:** name the reason two captures cannot be compared ([#1080](https://github.com/morluto/rea/issues/1080)) ([eb69763](https://github.com/morluto/rea/commit/eb69763d27e29fe3247c008925016e4c73cb6abe))
* **process:** preserve observations after successful cleanup ([2af12a1](https://github.com/morluto/rea/commit/2af12a18a36bb80fb7d91ef4c8009212750daa0a))
* **process:** retain ownership after incomplete cleanup ([e2fc249](https://github.com/morluto/rea/commit/e2fc2493b6a59f0663495d277c0cfec1aeb25a7a))
* **process:** stabilize native capture and ownership prerequisites ([2b27515](https://github.com/morluto/rea/commit/2b2751547662f8cb142f96d91eff50388a1ecb31))
* **process:** stop failing captures on tokens macOS cannot expose ([#1057](https://github.com/morluto/rea/issues/1057)) ([5547c1e](https://github.com/morluto/rea/commit/5547c1e697af8c2e1c771abe5344d10b7c1cd340))
* **providers:** validate ELF versions and preserve probe cancellation ([8b01e0c](https://github.com/morluto/rea/commit/8b01e0cc8904b512b517b4471ef2150f40c98f0d))
* **release:** always bump minor for releases ([be783ef](https://github.com/morluto/rea/commit/be783efaba62dc76c5d8d42229869a32afdb302a))
* **release:** publish prereleases under the next npm tag ([bfb7776](https://github.com/morluto/rea/commit/bfb7776006303c6164dba2f22b8e81fdb7c20beb))
* **release:** publish reviewed release PR merges ([cdddfdd](https://github.com/morluto/rea/commit/cdddfddf495ffd172f62a6a801f7697fe502847e))
* **release:** recognize conventional titles in GitHub merge commits ([f5009ed](https://github.com/morluto/rea/commit/f5009ed55f081939409808870458162991f836ca))
* **release:** restore automatic release proposals on main ([acb3e0f](https://github.com/morluto/rea/commit/acb3e0f2e5b38a9c97df7ed78c8e6e5f38bfa08f))
* **release:** validate frozen checkpoint versions and breaking notes ([8a31508](https://github.com/morluto/rea/commit/8a31508c8958800364e255540ded1586298d7008))
* **release:** validate frozen checkpoint versions and breaking notes ([60825fb](https://github.com/morluto/rea/commit/60825fb44751c9482bdf4cafdac3599da7c82d45)), closes [#1063](https://github.com/morluto/rea/issues/1063)
* select Node legacy package entry fields for import and require ([#1035](https://github.com/morluto/rea/issues/1035)) ([1706005](https://github.com/morluto/rea/commit/170600541bf44f2a63731e8b3d42aae3cbb8d6b3))
* **session:** serialize snapshot lifecycle with admitted requests ([e44cb45](https://github.com/morluto/rea/commit/e44cb45908af8d3902e0db044e0cf7b86cdd7ed0))
* **setup:** launch Windows npx registrations through cmd.exe ([#1072](https://github.com/morluto/rea/issues/1072)) ([ef30343](https://github.com/morluto/rea/commit/ef30343154d64d78b620d41c37457eee5d99fc71))
* **setup:** treat empty client config files as new documents ([#1117](https://github.com/morluto/rea/issues/1117)) ([9fd65ce](https://github.com/morluto/rea/commit/9fd65ce0711ce9257aee15e62a5ecf9832960035))
* **snapshot:** retain MCP workflow replay bindings ([5e1afe4](https://github.com/morluto/rea/commit/5e1afe454f2f283eeeea4532d07a9bdbea8bd51e))
* **target:** preserve app bundle filesystem failure reasons ([d4c2c3c](https://github.com/morluto/rea/commit/d4c2c3c18f691d04526fe38867347faa2530861d))
* **update:** accept npm 12 release metadata ([#1138](https://github.com/morluto/rea/issues/1138)) ([567a82f](https://github.com/morluto/rea/commit/567a82f8d4feb629a7c10d2cc9180377eb1f6f66))
* **verify:** close both sides of the browser proxy ([f7366a5](https://github.com/morluto/rea/commit/f7366a517ff0545f01df6202b1ed3231d9dd3b4a))
* **verify:** isolate package archives and published canary caches ([3eb7dbd](https://github.com/morluto/rea/commit/3eb7dbdc1689737b44cc4ff044bcb1a311511969))
* **verify:** remove stale mocked bridge package probe ([826583d](https://github.com/morluto/rea/commit/826583dd43fc918c70a87dfe148c2fdbc8612f3b))
* **verify:** report final success only after resource cleanup ([ee043e2](https://github.com/morluto/rea/commit/ee043e2cbf30152c60b403fff050a0fc848a3286))
* **workflows:** keep caller-input details in workflow input errors ([#1089](https://github.com/morluto/rea/issues/1089)) ([289aef6](https://github.com/morluto/rea/commit/289aef6838e424c14f331a9905020079e6b7539b))
* **workflows:** retain residual unknowns through CLI and MCP ([0136ee8](https://github.com/morluto/rea/commit/0136ee8c22300ed351af5eab6329ab4d271b5bb6))
### Performance Improvements
* **mcp:** advertise each tool JSON Schema once per target ([#1059](https://github.com/morluto/rea/issues/1059)) ([5ccb924](https://github.com/morluto/rea/commit/5ccb9241be15bb6a19b99fbbfb27ff3356ba157b))
* **build:** avoid duplicate documentation generation ([c701376](https://github.com/morluto/rea/commit/c701376777212ec9acfd10c97f45b1eaf0c885c6))
* **javascript:** index configuration and module lookups ([67252b2](https://github.com/morluto/rea/commit/67252b23b9e35e9526fed7842dab47a2c7c9fe5f))
* **javascript:** reuse validated immutable graphs ([#1112](https://github.com/morluto/rea/issues/1112)) ([ec6db0f](https://github.com/morluto/rea/commit/ec6db0f0630c4b0b35f0db84c0030784c1aa58a6))
### Code Refactoring
* **artifacts:** centralize stable inventory identities ([135d102](https://github.com/morluto/rea/commit/135d102f9df844d70d352870afce80f67c0ae96f))
* **browser:** charge source-map records before retention ([e762e32](https://github.com/morluto/rea/commit/e762e32afbba3fd32532fd42a3f4fdcd05252ad3))
* **config:** prune unused format wrappers and launcher export ([81ae838](https://github.com/morluto/rea/commit/81ae8385679ef10b95d34d0f0b5ba11732ac86b5))
* **input:** share schema issue error construction ([65a6288](https://github.com/morluto/rea/commit/65a6288257e4c3cab9adb53b0f9123ef567b61bd))
* **setup:** validate formats through canonical configuration ([4b10665](https://github.com/morluto/rea/commit/4b1066501b379107b2375e270f386fc9855a85c1))
### Documentation
* add Persian README translation with full language navigation ([7a7460f](https://github.com/morluto/rea/commit/7a7460f04f3cfd7e365d70ff891bdd0c474901b6))
* add ten README translations and expand language navigation ([a61491a](https://github.com/morluto/rea/commit/a61491ad00b21e912e16f68bff829927aed6e16f))
* align translated READMEs and website links with English ([d83f098](https://github.com/morluto/rea/commit/d83f0983d5aa18d29d95d24c4ccb3928be6f29f5))
* celebrate 20,000 GitHub stars 🎉 ([#1079](https://github.com/morluto/rea/issues/1079)) ([9455e7c](https://github.com/morluto/rea/commit/9455e7c8bc9d5c4a9a1fc9077aa15007ad80997e))
* check surrounding cases when changing boundaries ([46d195e](https://github.com/morluto/rea/commit/46d195e8a48b710fc355e84cb6b79461b9c39005))
* clarify contribution scope and cosmetic suggestions ([1f79002](https://github.com/morluto/rea/commit/1f7900249783956070af49c10f86002a0dfdc42b))
* clarify evidence ownership and verification guidance ([a9e00b8](https://github.com/morluto/rea/commit/a9e00b83f37651a8dd241375615b248062e04227))
* move Portuguese toward the end of language navigation ([2a4979b](https://github.com/morluto/rea/commit/2a4979b7cdc1a517865573d6c36654c022347d0a))
* move star milestone into history sections across readmes ([6e6ff7b](https://github.com/morluto/rea/commit/6e6ff7bd2eabb668602258ad5e31eb758b31f823))
* require partial native facts at provider boundaries ([2b86ea9](https://github.com/morluto/rea/commit/2b86ea9c7cd3cfc2141a021f8b531dbc84395c81))
* require surrounding boundary checks for review fixes ([9833c94](https://github.com/morluto/rea/commit/9833c94656e1d54e7711dc483bf8091ced14234f))
* **testing:** prioritize real workflows and distinct regressions ([9588482](https://github.com/morluto/rea/commit/9588482bfeeea869d7274d27fb96bac70278dd42))
* **website:** add a CTF showcase and FAQ ([#1077](https://github.com/morluto/rea/issues/1077)) ([a4d77f0](https://github.com/morluto/rea/commit/a4d77f0ff0242b7ab891030d2ffa09a4b4bc1c3c))
### Tests
* **acceptance:** add application workflow MCP parity harness and scenarios ([5ac3dc9](https://github.com/morluto/rea/commit/5ac3dc96f064e691b357aeddc11fa76b7b4d66d1))
* **artifacts:** retire simulated mounts after detach failures ([f591e37](https://github.com/morluto/rea/commit/f591e37a1e45d9b7179ed788c4e7ee77a708c6b0))
* **artifacts:** verify native directory identity on supported Windows runtimes ([#1069](https://github.com/morluto/rea/issues/1069)) ([e94be95](https://github.com/morluto/rea/commit/e94be95c66515fa6fba2a46e4b29850d34141fef))
* **browser:** pin DOM and source-map URL policy classifications ([#1120](https://github.com/morluto/rea/issues/1120)) ([c1d0002](https://github.com/morluto/rea/commit/c1d0002470dc2b6bde73e4ffe438e734dbb24d8d))
* **browser:** reject unmodeled CDP commands in protocol fixtures ([017297c](https://github.com/morluto/rea/commit/017297c40c0c38b9d9c17f5194f0f4c51748f0c3))
* classify application scenarios by their actual boundaries ([0e0b386](https://github.com/morluto/rea/commit/0e0b386dc35bdee1174df9a2d718e0f3e5793666))
* **cli:** verify self-import parity and cleanup ([55c9ce2](https://github.com/morluto/rea/commit/55c9ce26d1b52f359a21512103088048f9aa7492))
* consolidate failure guards and trim unused fixture APIs ([ffd9611](https://github.com/morluto/rea/commit/ffd9611308c91b894a7b0d27f331238f5117dacf))
* **hopper:** cover real boundary failures and CLI parity ([64717ad](https://github.com/morluto/rea/commit/64717ad7daa3152eeb718b4eb5fdcc77f1d76542))
* **hopper:** verify real boundaries and prune redundant happy paths ([7005166](https://github.com/morluto/rea/commit/7005166f5878737fb7b3c7ec4e3d2ee5f4dcce30))
* **mcp:** serialize real process capture and report unavailable authority ([8f9da25](https://github.com/morluto/rea/commit/8f9da251647beb530f6851c9dad5040b19a50a35))
* **process:** cover argv rewrites in native ownership inspection ([09d7b9b](https://github.com/morluto/rea/commit/09d7b9bab53ae968502fd6e19df694cca8cab817))
* **process:** distinguish native startup from producer verification ([9a70fb4](https://github.com/morluto/rea/commit/9a70fb482c974658088167c9d279bd70159e5dc8))
* **process:** expose unavailable capture and unexpected teardown failures ([90b06db](https://github.com/morluto/rea/commit/90b06db6c0b2c5863f292f63a226a5c5678a86ac))
* **process:** report the cause of terminal capture failures ([8a4c4b6](https://github.com/morluto/rea/commit/8a4c4b657fe3b0c249dd2e2147f6960361684c59))
* **process:** retain cancellation checks under unverified host cleanup ([9a5b077](https://github.com/morluto/rea/commit/9a5b0778993102460e333de6cad3ba85383c10be))
* **process:** retire native caches at worker teardown ([a0784c0](https://github.com/morluto/rea/commit/a0784c0d2cb70c48ecfeb0be23c6d68de5ee5be1))
* **process:** serialize real CLI capture boundaries ([82560eb](https://github.com/morluto/rea/commit/82560ebf593a7dfc704698ee312e4d8f1629be8f))
* prune redundant cases and promote configuration lifecycle coverage ([5945915](https://github.com/morluto/rea/commit/5945915f91adf85de91e16b69534bc3ed1d1b69a))
* prune resource-recovery wording assertions ([ae06dcf](https://github.com/morluto/rea/commit/ae06dcf83f65b06abb390d2e0b0ea33543278d6e))
* remove duplicate result assertions before throwing guards ([a1b7d40](https://github.com/morluto/rea/commit/a1b7d40bb0212cdfdddd770a1bccbed097601e0a))
* **session:** preserve live binding after cancelled switches ([1136ea8](https://github.com/morluto/rea/commit/1136ea881029eab59ffb0834149e86a6aff69e3b))
* **setup:** scope removal regression to its fixture client ([fe48e77](https://github.com/morluto/rea/commit/fe48e779c264745d28b5a819e51c06e6b5bd2776))
* **workflows:** verify residual questions in CLI snapshots ([770d2f6](https://github.com/morluto/rea/commit/770d2f60aa948cfdb61c71304be2f3e35f673840))
### Continuous Integration
* run signature target binding regressions on native macOS ([#1107](https://github.com/morluto/rea/issues/1107)) ([1e63144](https://github.com/morluto/rea/commit/1e63144390070f569998271fafd1459788ef3265))
## [6.0.0](https://github.com/morluto/rea/compare/rea-agents-5.0.0...rea-agents-6.0.0) (2026-10-08)
+63 -18
View File
@@ -2,6 +2,29 @@
REA welcomes focused bug fixes, documentation improvements, tests, and reverse-engineering workflow enhancements. Open an issue before a large contract or architecture change so its scope can be agreed before implementation.
## Choosing a contribution
Small contributions are welcome when they solve a concrete problem, including
broken commands, misleading documentation, and missing regression coverage.
Explain the problem, keep the diff focused, and share how you checked the change.
Please avoid standalone cosmetic changes without a clear benefit, such as fixing
a typo in an internal comment, changing capitalization or punctuation in
already-clear prose, or reformatting unchanged code. Include related polish in a
useful fix when practical. For cosmetic-only suggestions, please open an issue
instead of a standalone PR so they can be collected and addressed together.
Corrections to confusing explanations or incorrect instructions are welcome.
Avoid speculative cleanup without a concrete problem to solve. Keep related
corrections in one PR.
Before requesting review, read the final diff and run the relevant checks. Be clear
about anything you couldn't test. You should be able to explain the changes you
submit.
We may decline changes when the benefit doesn't justify the review or maintenance
work.
For capability organization and provider composition, follow the incremental
[migration guide](docs/capability-migration.md). Run `npm run verify:test-discovery`
after adding or moving tests.
@@ -19,9 +42,10 @@ npm run check:fast
`npm ci` installs the exact dependencies and prepares the Husky hooks without
building the project. Run `npm run build:cached` when you need the standalone
CLI or MCP server; `npm run build` remains the uncached compiler leaf used by
Turbo. Turbo caches deterministic builds and static checks across Git
worktrees. After a package, lockfile, or managed-skill version change, run
CLI or MCP server; it compiles the runtime and bundles the authored skill.
`npm run build` forces those tasks to rerun. Neither command generates test
catalogs, documentation projections, or managed verification evidence. Turbo
caches deterministic builds and static checks across Git worktrees. After a package, lockfile, or managed-skill version change, run
`npm run metadata:generate` before building.
Keep dependencies flowing inward through the existing domain, contracts, provider, application, server, and adapter layers. Parse unknown values at process and protocol boundaries, model expected failures with `Result`, and preserve the canonical tool inventory defined by `TOOL_CONTRACTS` unless a deliberate contract change updates every verifier, generated catalog artifact, and snapshot. Keep tool discovery complete and report capability- and session-scoped availability through `binary_session`.
@@ -39,10 +63,14 @@ use `npm run docs:generate`; `docs:build` generates them before building the web
Pull requests run `npm run docs:check`. `.github/workflows/pages.yml` is a
manual VitePress build and does not publish. The public site at
<https://morluto.github.io/rea/> is published only by the manual website
workflow on `main`. The repository's **Settings → Pages → Build and
deployment → Source** must be set to **GitHub Actions** before the first
deployment.
<https://rea.tools/> serves `website/public/` through Cloudflare Workers. See
the [website README](website/README.md#cloudflare-workers) for previews and
manual deployments.
The separate GitHub Pages host uses only the manual
`.github/workflows/website-pages.yml` workflow on `main`. The repository's
**Settings → Pages → Build and deployment → Source** must be set to
**GitHub Actions** before deploying there.
## Development feedback and PR verification
@@ -69,7 +97,22 @@ Packaging, setup, installation, or distribution changes also require
`npm run verify:package` and `npm pack --dry-run`. Provider behavior changes
require the matching real-provider `verify:*` lane.
`check:fast` runs cached typecheck and lint, reporting diagnostics on failure.
`check:fast` runs cached source/test typecheck and lint without compiling or
running generators. `npm run test:prepare` prepares all artifacts consumed by
the complete suite; focused tests prepare only their runtime and declared
artifact dependencies. The test-only MCP catalog lives at
`.cache/mcp-tool-catalog.json` and can be regenerated independently with
`npm run mcp-catalog:generate`.
Build and generation commands hold one SQLite exclusive transaction around
their whole Turbo graph, including cache restoration. The persistent database
in `.cache/rea-command-locks/` is a lock identity, not a stale-file marker;
the operating system releases ownership when the command supervisor exits.
On POSIX, that supervisor retains the transaction and process-group identity
through cancellation, including loss of its parent runner. Windows cancellation
waits for the command to complete. Do not remove the database while commands
are running.
`check` adds formatting, dead-code, and package-metadata freshness checks.
Formatting uses Oxfmt and the committed `.oxfmtrc.json`; generated sources use
the same configuration. Pre-commit formats and lints staged files; pre-push runs
@@ -77,8 +120,10 @@ the same configuration. Pre-commit formats and lints staged files; pre-push runs
`docs:check` builds and validates generated metadata for the current checkout.
The product catalog (`docs/public/product-catalog.json`), portable managed
conformance projections (`docs/verification/managed-conformance-*.json`), and
packaged skill (`skills/`) are ignored build outputs. Edit skill instructions
and references in `skill-src/`; the build adds catalog-dependent metadata to
packaged skill (`skills/`) are ignored generated outputs. `docs:generate`
prepares documentation and managed evidence; `evidence:generate` prepares only
the managed commitment and its runtime/skill dependencies. Edit skill
instructions and references in `skill-src/`; the skill task adds metadata to
the packaged copy without rewriting authored files. The generated manifest
commits to that exact packaged skill bundle. It is a portable projection of
the deterministic managed verifier, not a record of optional real-provider runs.
@@ -138,14 +183,14 @@ Describe the behavior change and verification performed in the pull request. Nev
## Maintainer release checklist
Use the [checkpoint release guide](docs/releasing.md). Releases start from an
explicit `release/VERSION` branch cut at a recorded commit. Manually prepare
the bot PR, wait for its exact-head CI and review, merge into that frozen
branch, and manually publish through the official Release workflow. Main
pushes do not update release PRs or publish packages. Both publishers build
the exact SHA tagged by Release Please.
Use the [release guide](docs/releasing.md). Main pushes automatically refresh a
Release Please PR with the next version and changelog. Review its migration
notes, wait for the current CI checks to pass, and merge it into main to publish.
The Release workflow validates and tags that merge; both publishers build its
exact SHA. Ordinary main pushes cannot publish packages. An optional manual
`release/VERSION` checkpoint retains explicit preparation and publication.
Keep new implementation commits on main for the next release. The workflow
owns packaged-artifact verification, npm publication, the published CLI/MCP
canary, and MCP Registry publication. Sync release metadata back to main after
publication; see the guide for partial-publication recovery and verification.
canary, and MCP Registry publication. Sync manual checkpoint metadata back to
main after publication; see the guide for recovery and verification.
+20 -16
View File
@@ -1,6 +1,6 @@
<div align="center">
**English** · [简体中文](README_zh.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [العربية](README_ar.md)
**English** · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA: Reverse Engineer Anything
@@ -18,7 +18,7 @@
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[Website](https://morluto.github.io/rea/) · [Guides](https://morluto.github.io/rea/guides/) · [Showcases](https://morluto.github.io/rea/showcase/)**
**[Website](https://rea.tools/) · [Guides](https://rea.tools/guides/) · [Showcases](https://rea.tools/showcase/)**
[Quick start](#quick-start) · [How REA works](#how-rea-works) · [What you can analyze](#what-you-can-analyze) · [Showcases](#showcases) · [FAQ](#faq) · [Documentation](#documentation)
@@ -54,7 +54,7 @@ REA connects your agent to tools for inspecting native binaries, JavaScript and
Setup registers REA with your agent and installs matching workflow instructions. Native analysis can use an existing Hopper or Ghidra installation; setup can optionally install Hopper with approval. Static JavaScript analysis needs neither engine.
> **[Visit the REA website](https://morluto.github.io/rea/)** for setup instructions, illustrated guides, and real case studies.
> **[Visit the REA website](https://rea.tools/)** for setup instructions, illustrated guides, and real case studies.
## Quick start
@@ -70,7 +70,7 @@ 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 and
Setup supports Claude Code, Codex, Cursor, Gemini CLI, Grok Build and
[other agents](docs/installation.md#supported-agents). See
[installation and setup](docs/installation.md) for provider configuration and
manual MCP registration.
@@ -149,15 +149,15 @@ 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](https://morluto.github.io/rea/guides/native/) |
| Native binaries | Pseudocode, assembly, strings, symbols, calls and references | Hopper, Ghidra or IDA; [native analysis](https://rea.tools/guides/native/) |
| Offline ELF layout | Sections, segments, original symbols/relocations and static mitigation candidates | Caller-supplied pwntools on Linux x64; [binary diagnostics](docs/binary-diagnostics.md) |
| EVM bytecode | Dispatch selectors, byte offsets, inferred arguments and mutability | Local raw/hex carrier; [offline EVM guide](docs/evm-bytecode.md) |
| Recorded Linux crashes | Raw notes, every recorded thread's registers/signals and optional mapping candidates | Caller-supplied pwntools; 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; [application analysis](https://morluto.github.io/rea/guides/javascript/) |
| Websites | Page structure, scripts, network observations and requested screenshots | A Chrome-family browser; [browser analysis](https://morluto.github.io/rea/guides/browser/) |
| JavaScript / Electron | Modules, imports, source maps, routes, IPC and native add-on relationships | Node.js and npm; [application analysis](https://rea.tools/guides/javascript/) |
| Websites | Page structure, scripts, network observations and requested screenshots | A Chrome-family browser; [browser analysis](https://rea.tools/guides/browser/) |
| Saved network captures | Requests, responses, exposed payloads and source locations | HAR; mitmdump on Linux for native mitmproxy captures; [capture guide](docs/web-network-captures.md) |
| .NET assemblies | Metadata, CIL instructions, declared native dependencies and build comparisons | Static inspection; [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; [Android guide](docs/android-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) |
| Firmware | Regions, extraction results and native-analysis handoffs | Binwalk / Unblob on Linux; [firmware guide](docs/firmware-analysis.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) |
@@ -173,6 +173,8 @@ Native formats and host support vary by provider. See
[IDA guide](docs/ida-provider.md), and
[experimental Windows Ghidra support](docs/windows-ghidra-p0.md).
Ghidra also supports [16-bit DOS analysis](docs/ghidra-dos.md).
For large binaries, raise its startup deadline with
`REA_GHIDRA_STARTUP_TIMEOUT_MS`.
For provider selection, see the [CLI guide](docs/cli.md#choose-a-provider).
Check [release availability](docs/installation.md#released-package-and-main)
for features added since the latest npm release.
@@ -185,7 +187,7 @@ 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](https://morluto.github.io/rea/showcase/dx-ball/) ·
[Read the case study](https://rea.tools/showcase/dx-ball/) ·
[Reconstruction repository](https://github.com/N0zoM1z0/dx-ball)
### Notion: trace the Electron clipboard bridge
@@ -193,7 +195,7 @@ original-x86 cases and reproduces all 63 compiled function bytes.
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](https://morluto.github.io/rea/showcase/notion/)
[Read the case study](https://rea.tools/showcase/notion/)
### TH04: recover a DOS bullet-ring calculation
@@ -201,7 +203,7 @@ 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](https://morluto.github.io/rea/showcase/th04/) ·
[Read the case study](https://rea.tools/showcase/th04/) ·
[Reconstruction repository](https://github.com/N0zoM1z0/th04)
If you've used REA on something interesting, we'd love to see it. Share your
@@ -289,14 +291,14 @@ with your REA version, target type, steps to reproduce and error output.
## Documentation
Start with the website's [worked guides](https://morluto.github.io/rea/guides/).
Start with the website's [worked guides](https://rea.tools/guides/).
For exact options, prerequisites and result contracts:
- [Installation and setup](docs/installation.md): agent registration, provider configuration, updates and uninstall.
- [Readiness and troubleshooting](docs/installation.md#check-readiness-for-your-task): diagnose one agent or analysis engine.
- [CLI and Evidence](docs/cli.md): commands, provider selection, snapshots, import/export and exit statuses.
- [MCP contracts](docs/mcp-contracts.md) and [agent prompts](docs/mcp-prompts.md): tool results, sessions and guided investigations.
- [Tool catalog](docs/mcp-contracts.md#generated-catalog): build-generated inventory of tools, providers and CLI commands.
- [Tool catalog](docs/mcp-contracts.md#generated-catalog): generated inventory of tools, providers and CLI commands.
- [Roadmap](docs/roadmap.md): planned work and capability trackers.
Report vulnerabilities through [SECURITY.md](SECURITY.md).
@@ -313,13 +315,13 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and checks,
## Project links
[Website](https://morluto.github.io/rea/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [Security](SECURITY.md)
[Website](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [Security](SECURITY.md)
## Star history
🎉 **20,000 GitHub stars — thank you!**
🎉 **30,000 GitHub stars — thank you!**
Thanks to everyone using REA, reporting bugs, testing builds, and contributing fixes.
Thanks to everyone using REA, reporting bugs, requesting features, testing builds, and contributing fixes.
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
@@ -333,6 +335,8 @@ Thanks to everyone using REA, reporting bugs, testing builds, and contributing f
REA provides tools for lawful reverse-engineering research, analysis, and reconstruction. You are responsible for obtaining any required authorization and complying with applicable laws. The project does not endorse illegal or unauthorized use.
REA is an open-source software project. We have not issued or endorsed any cryptocurrency or token. Tokens using the REA name are not affiliated with the project.
## License
[MIT](LICENSE)
+173 -241
View File
@@ -1,355 +1,287 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · **العربية**
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · **العربية** · [فارسی](README_fa.md)
# REA: اعكس هندسة أي شيء
# REA: هندسة عكسية لأي شيء
### وسيط MCP واحد للهندسة العكسية عبر الملفات التنفيذية والتطبيقات وسلوك وقت التشغيل.
### خادم MCP واحد للهندسة العكسية للملفات الثنائية والتطبيقات والسلوك أثناء التشغيل.
**اعثر على ميزة تعجبك. افهم آلية عملها. ابنها بالطريقة التي تريدها.**
**وجدت ميزة تعجبك؟ افهم طريقة عملها حتى مستوى الشيفرة الثنائية.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](#منصة-أدوات-التحقيق)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[الموقع (بالإنجليزية)](https://morluto.github.io/rea/) · [أدلة الاستخدام](https://morluto.github.io/rea/guides/) · [دراسة حالة DX-Ball](https://morluto.github.io/rea/showcase/dx-ball/)**
**[الموقع](https://rea.tools/) · [الأدلة](https://rea.tools/guides/) · [دراسات الحالة](https://rea.tools/showcase/)**
[البدء السريع](#البدء-السريع) · [حالة الدعم](#حالة-الدعم) · [من الملف التنفيذي إلى السلوك](#من-الملف-التنفيذي-إلى-السلوك) · [منصة أدوات التحقيق](#منصة-أدوات-التحقيق) · [خطة العمل](#خطة-العمل) · [كيف يعمل؟](#كيف-يعمل)
[البدء السريع](#البدء-السريع) · [كيف يعمل REA](#كيف-يعمل-rea) · [ما الذي يمكنك تحليله](#ما-الذي-يمكنك-تحليله) · [دراسات الحالة](#دراسات-الحالة) · [الأسئلة الشائعة](#الأسئلة-الشائعة) · [التوثيق](#التوثيق)
<table aria-label="REA community">
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA يشغّل جسر التحليل داخل Hopper لفحص ملف ثنائي للشيفرة الأصلية" width="1200" />
<br />
<table aria-label="مجتمع REA">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
<img src="docs/assets/discord.svg" height="42" alt="Discord" /><br />
<strong>انضم إلى مجتمع الهندسة العكسية</strong>
</a><br />
<sub>Discord · أسئلة وأجوبة · شارك أعمالك</sub>
<sub>Discord · أسئلة وأجوبة · مشاركة النتائج</sub>
</td>
</tr>
</table>
<br />
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA يشغّل جسر التحليل داخل Hopper لفحص ملف تنفيذي أصلي" width="1200" />
</div>
---
هل وجدت في تطبيق ميزة تريدها في منتجك؟ اطلب من وكيلك استقصاءها باستخدام REA. يستطيع فحص التطبيق من دون شيفرته المصدرية، وشرح طريقة عمل الميزة وعرض الأدلة، ثم بناء نسخة تناسب مشروعك.
هل وجدت في تطبيق ميزة تريد إضافتها إلى منتجك؟ اطلب من وكيلك استقصاءها باستخدام REA. يستطيع فحص التطبيق من دون شيفرته المصدرية، وشرح طريقة عمل الميزة وعرض الأدلة، ثم بناء نسخة تناسب مشروعك.
يوفر REA أدوات لتحليل الملفات التنفيذية الأصلية وتطبيقات JavaScript وElectron وتجميعات .NET والمواقع. يمكنك استخدامها من وكيلك أو من الطرفية. يعمل التحليل محليًا، وتتضمن النتائج الأدلة والقيود.
يربط REA وكيلك بأدوات لفحص الملفات الثنائية للشيفرة الأصلية، وتطبيقات JavaScript وElectron، وتجميعات .NET، والمواقع. يمكنك أيضًا استخدام الأدوات نفسها من الطرفية. يجري التحليل محليًا، وتتضمن النتائج الأدلة والقيود التي تستند إليها كل خلاصة.
يضبط Setup إعدادات الوكيل ويربطه بتثبيت موجود من Hopper أو Ghidra، ويثبّت سير عمل REA الموجّه. ويمكنه تثبيت Hopper بعد موافقتك. ولا يحتاج التحليل الثابت لتطبيقات JavaScript إلى أي محرك.
يسجّل الإعداد REA لدى وكيلك ويثبّت تعليمات سير العمل المطابقة. يمكن لتحليل الشيفرة الأصلية استخدام تثبيت موجود من Hopper أو Ghidra؛ ويمكن للإعداد أيضًا تثبيت Hopper اختياريًا بعد موافقتك. لا يحتاج التحليل الثابت لـ JavaScript إلى أي من المحركين.
## اطلب من وكيلك مباشرة
بعد [الإعداد](#البدء-السريع)، أعد تشغيل الوكيل ثم اطلب:
```text
استقصِ طريقة عمل البحث في تطبيق Notes، واعرض الأدلة، ثم ابنِ ميزة مشابهة لمشروعي.
```
استبدل Notes بالتطبيق الذي تريد فهمه، أو اطلب نظرة عامة أولًا.
## من الملف التنفيذي إلى السلوك
| فك الترجمة | الفهم | إعادة الإنشاء |
| ---------------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------ |
| استخرج الشيفرة شبه المصدرية، والتعليمات، والسلاسل النصية، والرموز | تتبع تدفق التحكم والمراجع المتبادلة والاستدعاءات والأنواع | حوّل ما تعلمه الوكيل إلى ميزة تناسب تقنياتك وواجهتك ومتطلباتك |
يوضح REA كيف وصل إلى نتائجه. وهو لا يدّعي استعادة الشيفرة المصدرية الأصلية أو استنساخ التطبيق تلقائيًا.
## لماذا REA؟
| | |
| ------------------------ | ---------------------------------------------------------------------------- |
| **مصمم للوكلاء** | اسأل عما يفعله التطبيق ودع الوكيل يفحصه بدلًا من التخمين. |
| **CLI وMCP** | استخدم قدرات الهندسة العكسية نفسها من الطرفية أو الوكيل. |
| **إعداد موجّه** | اضبط وكيلك، واربط أداة تحليل موجودة، أو ثبّت Hopper بموافقتك. |
| **من الفهم إلى الشيفرة** | افهم الميزة ثم ابنِ نسختك الخاصة في جلسة البرمجة نفسها. |
| **محلي حسب التصميم** | يجري التحليل على مضيفك المحلي المدعوم، ولا يرفع REA التطبيق إلى خدمة مستضافة. |
| **يحافظ على السياق** | استقصِ عدة تطبيقات دون بدء التحليل من جديد عند كل سؤال. |
> **[زر موقع REA](https://rea.tools/)** للاطلاع على تعليمات الإعداد والأدلة المصوّرة ودراسات الحالة الفعلية.
## البدء السريع
### تشغيل الإعداد (موصى به)
### إعداد وكيلك
اربط REA بوكيلك:
بعد تثبيت Node.js وnpm، نفّذ:
```bash
npx rea-agents setup
```
يعرض Setup أولاً قائمة متعددة الاختيار للوكلاء الذين تريد ربطهم. تكون تسجيلات REA الموجودة محددة افتراضياً، ولا يؤدي اكتشاف عميل جديد وحده إلى تحديده؛ ويمكنك اختيار عميل غير مهيأ. راجع المسارات والتغييرات المحددة قبل الموافقة. يثبت Setup سير عمل REA للوكلاء المحددين افتراضياً. Hopper خيار منفصل يتطلب موافقة مستقلة، ويمكن أيضاً حفظ مسارات تثبيت Ghidra الموجودة.
اختر الوكلاء، وراجع التغييرات المقترحة، ثم وافق عليها. يضيف الإعداد خادم MCP الخاص بـ REA وتعليمات سير العمل المطابقة، مع الاحتفاظ بنسخ احتياطية من الإعدادات الموجودة. أعد تشغيل وكيلك بعد ذلك.
يعرض التغييرات قبل تطبيقها وينشئ نسخة احتياطية من الإعدادات الموجودة. راجع [دليل التثبيت والإعداد](docs/installation.md) للمتطلبات والخيارات الإضافية.
يدعم الإعداد Claude Code وCodex وCursor وGemini CLI وGrok Build و[وكلاء آخرين](docs/installation.md#supported-agents). راجع [التثبيت والإعداد](docs/installation.md) لتهيئة موفّري التحليل وتسجيل MCP يدويًا.
### الاستخدام مع وكيل
### اطلب من وكيلك
بعد الإعداد، أعد تشغيل الوكيل واشرح التطبيق أو الميزة التي تريد استقصاءها. يدعم REA Claude Code وClaude Desktop وCodex وCursor وGemini CLI وWindsurf وDevin وOpenCode وAntigravity وGitHub Copilot CLI وCommand Code وVS Code. تكون تسجيلات REA الموجودة محددة افتراضياً، ويجب اختيار العملاء المكتشفين الآخرين يدوياً. يمكن للوكلاء الآخرين استخدام إعداد MCP أدناه.
يمكن تشغيل Hopper في الوضع التجريبي. إذا ظهرت رسالة عند التشغيل الأول، فاختر التجربة أو أدخل ترخيصًا موجودًا.
### مهارة مساعد البرمجة (اختياري)
أضف المهارة إلى مساعد البرمجة للحصول على سياق أغنى:
```bash
npx skills add morluto/rea --skill reverse-engineer-anything
```text
استقصِ طريقة عمل البحث في تطبيق Notes، واعرض الأدلة، ثم ابنِ ميزة مشابهة لمشروعي.
```
توفر المهارة سير عمل التحقيق في REA. نفّذ الإعداد أعلاه لربط REA بالوكيل وضبط أدوات التحليل. يثبّت الإعداد افتراضيًا مهارة مطابقة للإصدار؛ وهذا الأمر يثبّت نسخة المستودع.
استبدل Notes بالتطبيق المستهدف وحدّد الميزة التي تريد فهمها.
لشجرة JavaScript/Electron المستخرجة أو ملف ASAR، لا يلزم إعداد MCP ولا محرك أصلي:
### استخدام الطرفية
افحص مجلد تطبيق JavaScript/Electron بعد فك حزمه، أو ملف ASAR:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
استبدل المسار بهدفك (مثل `"D:/apps/example"` على Windows). يدعم سير العمل الثابت هذه المسارات، بينما يتطلب التحليل الأصلي محركه الخاص.
تتضمن النتيجة الوحدات وعمليات الاستيراد وحدود Electron والأدلة المتعلقة بها. استبدل المسار بمسار هدفك، مثل `"D:/apps/example"` على Windows.
### تثبيت أمر rea
ثبت واجهة سطر الأوامر:
```bash
curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh | bash
```
يجب تثبيت Node.js وnpm مسبقًا. عند تشغيله من طرفية، يثبت البرنامج `rea` ويبدأ الإعداد.
يمكنك بدلًا من ذلك التثبيت باستخدام npm ثم تشغيل الإعداد:
لتثبيت أمر `rea` للاستخدام المعتاد:
```bash
npm install --global rea-agents
rea setup
rea --help
```
### المتطلبات
لتحليل الشيفرة الأصلية، هيّئ موفّرًا أولًا. راجع [دليل CLI وEvidence](docs/cli.md) لأوامر تحليل الشيفرة الأصلية، واختيار الموفّر، واللقطات، والاستخدام في السكربتات.
- macOS 12 أو أحدث
- Ubuntu 24.04+ أو Fedora 41+ أو Arch Linux بنواة 64 بت أو CachyOS
- Node.js 22.x (>=22.19) أو 24.x (>=24.11) أو 26+ وnpm
### تحديث REA
يتطلب تحليل الملفات التنفيذية الأصلية Hopper أو Ghidra. Hopper برنامج منفصل، وللوضع التجريبي قيود يحددها المورّد، لكن الترخيص المدفوع ليس إلزاميًا.
يتطور REA بسرعة، وتتضمن الإصدارات الجديدة إصلاحات متكررة للأخطاء. حافظ على تحديث نسختك.
يدعم Ghidra نظامي Linux x64 وmacOS x64/arm64. ثبت Ghidra 12.1.x وJDK الكامل بنواة 64 بت الذي يعلنه ذلك التثبيت (`application.java.min` حتى `application.java.max`) بصورة منفصلة، ثم اضبط REA لاستخدامهما. تتطلب إصدارات 12.1 الحالية JDK 21 أو أحدث ولا تحدد حدًا أعلى. جرى التحقق من الجسر مع Ghidra 12.1.4 وJDK 21. يحتاج macOS أيضًا إلى أداة فك الترجمة الأصلية المطابقة لمعمارية الجهاز.
يستطيع Setup التحقق من التثبيت وحفظ المسارات؛ ولا يثبت أو يحدّث Ghidra أو Java أو Node.js أو npm أو Homebrew.
يتضمن فرع main والحزمة npm 4.1.0 دعمًا تجريبيًا لـ Windows x64 Ghidra P0 لتطبيقات PE الأصلية x86-64 (غير المُدارة وغير DLL) على NTFS محلي، مع ضوابط Job Object وقوائم DACL الخاصة وقبول المسارات المجمّعة. تحقق من [حد الإصدار](docs/installation.md#released-package-and-main) قبل توقع ذلك من حزمة npm أقدم. راجع [دليل Windows Ghidra P0](docs/windows-ghidra-p0.md) للمتطلبات والنطاق الموثق.
### تشخيص المشكلات
يشخّص `npx -y rea-agents@latest doctor` الجهاز والاعتماديات وأدوات التحليل وإعدادات الوكيل من دون تغييرها. أضف `--json` للحصول على تشخيص منظم.
على Linux، يفضّل REA الملف التنفيذي `/opt/hopper/bin/Hopper`، وإذا لم يكن متاحًا يتحقق تلقائيًا من `~/.local/share/rea/hopper/bin/Hopper`. استخدم `HOPPER_LAUNCHER_PATH` للمسارات الأخرى. إذا كان الملف موجودًا لكن محرك التحليل لا يظهر، فشغّل `ldd /absolute/path/to/Hopper | grep 'not found'` على مسار Hopper الفعلي للتحقق من المكتبات الناقصة. راجع [دليل Hopper](docs/installation.md#hopper).
### التحديث والإزالة
- يحدّث `rea update` تثبيت REA الحالي.
- يزيل `rea uninstall` تسجيلات الوكلاء وملفات سير العمل التي يديرها REA، ويترك Hopper مثبتًا.
- يزيل `rea uninstall --purge-data` أيضًا ذاكرة REA المؤقتة وحالته. استخدمه فقط إذا أردت حذف هذه البيانات.
## حالة الدعم
تُوصف قدرات المستودع الحالية ومتطلبات الأنظمة في [دليل الدعم الإنجليزي](README.md#current-status). قد يسبق فرع main [إصدار npm](docs/installation.md#released-package-and-main).
- يوفر Ghidra 25 عملية للقراءة فقط على Linux x64 وmacOS x64/arm64 وحدود Windows x64 P0 التجريبية. يدعم Linux/macOS أيضًا تعليقات الدوال الذرية على مستوى الجلسة. نظام Windows P0 للقراءة فقط، ولا يوفر Ghidra أي تحكم في الواجهة.
- يتطلب الفحص الثابت لنظام Android تجهيز JADX/Java بشكل منفصل؛ ويغطي التحقق بالموفر الحقيقي نظام Linux. راجع [تحليل Android](docs/android-analysis.md).
- تحدد طلبات المتصفح وElectron والعمليات هدفها وإجراءاتها ودورة حياتها مباشرة، وتعتمد على وصول المضيف الفعلي دون منح أذونات منفصلة من REA. ما زالت كتابة إعدادات Setup وتثبيت Hopper تتطلب الموافقة على خطتها الدقيقة.
- يصف `rea capabilities` عمليات جلسة الملف التنفيذي لا كل أدوات التطبيقات ووقت التشغيل. راجع قائمة أدوات MCP الكاملة والدليل ذا الصلة.
## استقصاء كامل بطلب واحد
بعد الإعداد، يمكنك أن تطلب من وكيلك:
> افتح التطبيق، واكتشف آلية عمل البحث من دون اتصال، واشرح تدفق التحكم، ثم ابنِ نسخة لمشروعي باستخدام TypeScript وSQLite.
يمكن للوكيل تحويل ذلك إلى سير عمل قابل للتحقق:
```text
فتح الهدف
→ انتظار اكتمال تحليل Hopper
→ جرد المعماريات والرموز والسلاسل النصية
→ تحديد نقاط الدخول والدوال المهمة
→ تتبع الاستدعاءات والمراجع المتبادلة وتدفق التحكم
→ فك ترجمة التنفيذ ذي الصلة
→ تلخيص السلوك والقيود والحالات الطرفية
→ بناء الميزة بما يلائم تقنيات المشروع ومتطلباته
```
## ما الذي يمكنك فعله؟
### فهم تطبيق لا تملك مصدره
اكشف نقاط الدخول، وأسماء الفئات، والسلاسل النصية، والأطر المستخدمة، ومسارات الميزات من الملف التنفيذي وحده.
### بناء ميزة تعجبك بطريقتك
استقصِ الميزة، وافهم سلوكها، ثم ابنِ نسخة ملائمة لمنتجك وتقنياتك ومتطلباتك.
### توثيق بروتوكول أو تنسيق غير موثق
تتبع الموزعات، والثوابت، والمقارنات، والقراءة والكتابة لفهم الرسائل وتنسيقات الملفات وآلات الحالات.
### إعادة إنشاء سلوك متوافق
حوّل منطقًا مفكوك الترجمة إلى مواصفات واضحة، ثم نفذ بديلًا نظيفًا تحكمه اختبارات السلوك الملحوظ.
### تدقيق منطق أمني حساس
افحص التحقق من الإدخال، والتشفير، والصلاحيات، والتخزين، ومسارات الأخطاء من دون رفع الملف التنفيذي إلى خدمة بعيدة.
## منصة أدوات التحقيق
| عائلة الأدوات | العدد | الاستخدام |
| ---------------------- | ----: | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| الفحص الأصلي | 41 | الدوال والشيفرة شبه المصدرية والتعليمات والسلاسل والرموز والاستدعاءات والمراجع والتعليقات وقراءة البايتات ومواضع الملفات |
| سير عمل التحقيق | 14 | نظرات عامة على التطبيقات وملفات الدوال وواجهات API الأصلية والتوزيع وفك الترجمة الدفعي وتتبع الميزات ومسارات الاستدعاء ومخططاته واكتشاف Swift وObjective-C |
| أدوات الملفات الثنائية | 10 | بيانات Mach-O والتوقيعات وplist والمعماريات وأسماء Swift ومراقبة الاستدعاءات عبر LLDB على macOS؛ تخطيط ELF وأدلة الحماية الثابتة وسجلات الأعطال على Linux |
| مخطط المخرجات | 7 | جرد الدلائل والحزم وملفات Interface Builder المترجمة وكتالوجات موارد Apple وتحليل تحميل Mach-O dylib والاستخراج واستدلال واجهات EVM دون اتصال |
| PE/CLI المُدار | 7 | هوية تجميعات .NET وبياناتها الوصفية وتعليمات CIL والاعتماديات الأصلية واستيراد إعادة البناء ومقارنة الإصدارات |
| البرامج الثابتة | 2 | فحص مناطق البرامج الثابتة على Linux واستخراجها الصريح |
| Android APK | 5 | بيانات الحزمة وmanifest والبحث عن الأصناف وقوائم الأعضاء وفك ترجمة الأساليب والمراجع الثابتة |
| مراقبة المتصفح | 12 | بنية الصفحات وبيانات الشبكة والبرامج النصية وخرائط المصدر واكتشاف WebMCP والصور ومقارنة الالتقاط |
| تحليل Electron | 5 | مراقبة العارضين وخريطة التطبيق الثابتة والمواءمة بين نتائج الثبات والتشغيل |
| وقت تشغيل JavaScript | 2 | اكتشاف أهداف Node/Electron Inspector ومواضع البرامج وأحداث سياقات التنفيذ |
| سير عمل التطبيقات | 13 | تصدير النصوص البرمجية الملتقطة، تتبع الميزات عبر الطبقات ومقارنة الإصدارات ومواءمة المصدر التاريخي ومقارنة أشكال القيم المعادة والتحقق من إعادة الإنشاء |
| مساحة العمل والملاحظة | 21 | الجلسات وحزم الأدلة وسياق التنقل ومقارنة العمليات والمخرجات والدوال وتسجيل الأسئلة غير المحسومة |
## خطة العمل
يشمل العمل التالي توسيع التحقق من الأهداف الأصلية، ومقارنة إصدارات JavaScript و.NET، ومراقبة وقت التشغيل والتحقق من إعادة الإنشاء. يتيح Setup بالفعل اختيار تكامل الوكيل وتثبيت Hopper. تثبيت أدوات تحليل أخرى ما زال عملًا لاحقًا. راجع [خطة التثبيت](docs/roadmap.md) و[تقييم أدوات التحليل](docs/provider-evaluation.md).
## استخدام REA مع وكلاء برمجة آخرين
يدعم Setup Claude Code وClaude Desktop وCodex وCursor وGemini CLI وWindsurf وDevin وOpenCode وAntigravity وGitHub Copilot CLI وCommand Code وVS Code. تكون تسجيلات REA الموجودة محددة افتراضياً، ويجب اختيار العملاء المكتشفين الآخرين يدوياً. يمكن لأي وكيل يدعم خوادم MCP المحلية الاتصال باستخدام الإعداد التالي:
<!-- x-release-please-start-version -->
```json
{
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents@6.0.0", "mcp"]
}
}
}
```
<!-- x-release-please-end -->
## كيف يعمل؟
```mermaid
flowchart LR
Human[المستخدم] --> Agent[وكيل البرمجة]
Agent --> REA[REA]
Human -->|CLI| REA
REA --> Hopper[عمليات تحليل Hopper]
Hopper --> App[تطبيقك]
REA --> Ghidra[تحليل Ghidra للقراءة فقط<br/>جرد + تحليل الدوال]
Ghidra --> App
```
يستخدم CLI وخادم MCP سير عمل التحليل نفسه. تحرر أوامر الطرفية جلسة الجسر الخاصة بها عند انتهائها، بينما يمكن لجلسة الوكيل الحفاظ على الاتصال أثناء الاستقصاء. إغلاق جلسة REA لا يغلق تطبيق Hopper الذي يستخدمه المستخدم.
## أوامر CLI
سير عمل الوكيل أعلاه هو أسهل طريقة لاستخدام REA. للحصول على نظرة سريعة على تطبيق من الطرفية:
لواجهة CLI المثبّتة عبر npm:
```bash
npx -y rea-agents@latest analyze /Applications/Notes.app
npx -y rea-agents@latest compare /absolute/path/to/left-evidence.json /absolute/path/to/right-evidence.json
rea update
```
شغّل `npx -y rea-agents@latest --help` لمعرفة خيارات فك الترجمة المباشر والخيارات الأخرى.
لتحديث تسجيلات الوكلاء والمهارة، نفّذ أمر الإعداد الذي يعرضه التحديث.
يمكن لـ REA فتح مجلد `.app` على Mac مباشرة. إذا لم يعثر الوكيل على التطبيق، فأخبره بمكان تثبيته.
إذا كنت تستخدم `npx`، فحدّث إعداد وكيلك بالأمر التالي:
## سلوك Hopper
```bash
npx rea-agents@latest setup
```
يشغّل REA برنامج Hopper عند الحاجة. على macOS قد تظهر نافذته في المقدمة أو يحتاج أحد الحوارات إلى استجابة منك. لا يمكن ضمان بقاء النافذة في الخلفية.
راجع تغييرات الإعداد وأعد تشغيل وكيلك. لأوامر CLI التي تنفّذها مرة واحدة، استخدم `npx rea-agents@latest` متبوعًا بالأمر المطلوب.
يحدد REA تنسيق الهدف ومعماريته لتجنب حوارات الاختيار المعتادة. عند إغلاق الجلسة، يوقف الجسر وينظف موارده الخاصة، لكنه لا يغلق تطبيق Hopper الذي تستخدمه.
## كيف يعمل REA
## الأمان والخصوصية
يستدعي وكيلك REA عبر MCP لفحص الهدف وتتبع الشيفرة ذات الصلة. يعيد REA النتائج مع أدلتها. يستخدمها الوكيل لطرح أسئلة متابعة، أو شرح السلوك، أو كتابة تنفيذ واختباره. تستخدم أوامر CLI سير العمل نفسه.
- يبقى التحليل محليًا بين REA وموفر التحليل المختار عبر مقابس محلية خاصة، ومع IDA عبر تسجيل MCP المحلي المضبوط.
- لا يرفع REA الأهداف إلى خدمة مستضافة.
- تعمل أدوات التحليل والأهداف المطلقة بصلاحيات المستخدم نفسه؛ وما زال التقاط واجهة النظام على macOS يعتمد على الوصول إلى تسهيلات الاستخدام وتسجيل الشاشة. ولا ينفّذ التحليل الثابت للـ JavaScript الوحدات المستخرجة.
- تستخدم جلسات Ghidra مشروعًا مؤقتًا معزولًا ولا تفتح مشاريع Ghidra التي يملكها المستخدم أو تعدّلها.
- راجع الملفات التنفيذية غير الموثوقة واعزلها كما تفعل مع أي مدخل أصلي قد يكون ضارًا.
- أبلغ عن الثغرات وفق [سياسة الأمان](SECURITY.md)، وليس عبر قضية عامة.
![مسار الاستقصاء في REA: يسأل وكيلك عن هدف محلي، ويفحصه REA ويتتبعه بأدوات التحليل، ثم يستخدم الوكيل الشيفرة والمراجع والجوانب غير المحسومة التي أعادتها الأدوات لشرح السلوك وتنفيذه واختباره.](website/public/assets/figures/rea-investigation-flow.svg)
[افتح الشكل بحجمه الكامل](website/public/assets/figures/rea-investigation-flow.svg).
<a id="current-status"></a>
## ما الذي يمكنك تحليله
يتطلب REA إصدار Node.js 22.x (>=22.19) أو 24.x (>=24.11) أو 26+، إضافة إلى npm. تعتمد الأدوات الإضافية والأنظمة المضيفة المدعومة على الهدف:
| الهدف | ما يعيده REA | المتطلبات والدليل |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| الملفات الثنائية للشيفرة الأصلية | شيفرة شبه برمجية، وتعليمات تجميع، وسلاسل نصية، ورموز، واستدعاءات، ومراجع | Hopper أو Ghidra أو IDA؛ [تحليل الشيفرة الأصلية](https://rea.tools/guides/native/) |
| بنية ELF دون تشغيل | الأقسام والمقاطع والرموز ومعلومات إعادة التموضع الأصلية، وآليات الحماية المحتملة المستدلّ عليها بالفحص الثابت | pwntools يوفّره المستدعي على Linux x64؛ [تشخيص الملفات الثنائية](docs/binary-diagnostics.md) |
| شيفرة EVM البايتية | محددات التوجيه، وإزاحات البايتات، والمعاملات المستنتجة، وقابلية تغيير الحالة | مدخل محلي يحمل بايتات خامًا أو تمثيلًا سداسيًا عشريًا؛ [دليل EVM دون تشغيل](docs/evm-bytecode.md) |
| أعطال Linux المسجّلة | سجلات note الخام، ومسجّلات المعالج/الإشارات لكل خيط مسجّل، وترشيحات اختيارية لخرائط الذاكرة | pwntools يوفّره المستدعي؛ GDB/pwndbg اختياريان؛ [الأعطال المسجّلة](docs/recorded-crashes.md) |
| JavaScript / Electron | الوحدات، وعمليات الاستيراد، وخرائط المصدر، والمسارات، وIPC، وعلاقات إضافات الشيفرة الأصلية | Node.js وnpm؛ [تحليل التطبيقات](https://rea.tools/guides/javascript/) |
| المواقع | بنية الصفحة، والسكربتات، وملاحظات الشبكة، ولقطات الشاشة المطلوبة | متصفح من عائلة Chrome؛ [تحليل المتصفح](https://rea.tools/guides/browser/) |
| تسجيلات الشبكة المحفوظة | الطلبات، والاستجابات، ومحتويات الحمولة المتاحة، ومواضعها في المصدر | HAR؛ وmitmdump على Linux لتسجيلات mitmproxy بصيغتها الأصلية؛ [دليل تسجيلات الشبكة](docs/web-network-captures.md) |
| تجميعات .NET | البيانات الوصفية، وتعليمات CIL، واعتماديات الشيفرة الأصلية المعلنة، ومقارنات البناء | فحص ثابت؛ [دليل الشيفرة المُدارة](docs/managed-code-analysis.md) |
| حزم Android APK | تصريحات ملف manifest، والأصناف، والدوال الناتجة عن فك الترجمة، والمراجع | JADX دون واجهة رسومية وJDK كامل على Linux/macOS؛ [دليل Android](docs/android-analysis.md) |
| البرامج الثابتة | المناطق، ونتائج الاستخراج، وما يُمرّر إلى تحليل الشيفرة الأصلية | Binwalk / Unblob على Linux؛ [دليل البرامج الثابتة](docs/firmware-analysis.md) |
| الحزم والموارد | قوائم الملفات، والبصمات، وملفات plist، وبنية حزم Apple، والموارد المستخرجة | [دليل تحليل الملفات وJavaScript](docs/javascript-artifact-reconstruction.md)، [تطبيقات Apple](docs/apple-application-analysis.md) |
| سلوك العمليات | مخرجات الطرفية، والتفاعلات، وملاحظات الخروج ونظام الملفات، ومقارنات التشغيل | Linux/macOS مع PTY أصلي؛ [تسجيل سلوك العمليات](docs/process-capture.md) |
يقرأ الفحص الثابت لـ JavaScript و.NET الملفات المقدّمة دون تشغيل التطبيق. يشغّل التسجيل أثناء التنفيذ الهدف المحدد أو يتفاعل معه بصلاحيات مستخدمك؛ ويشرح كل دليل للتسجيل أثناء التنفيذ آثاره.
<a id="choosing-a-deep-analysis-provider"></a>
تختلف صيغ ملفات الشيفرة الأصلية والأنظمة المضيفة المدعومة باختلاف الموفّر. راجع [إعداد Hopper وGhidra](docs/installation.md#hopper)، و[دليل IDA](docs/ida-provider.md)، و[دعم Ghidra التجريبي على Windows](docs/windows-ghidra-p0.md). يدعم Ghidra أيضًا [تحليل DOS ذي 16 بت](docs/ghidra-dos.md). لاختيار الموفّر، راجع [دليل CLI](docs/cli.md#choose-a-provider). تحقّق من [توفر الميزات في الإصدارات](docs/installation.md#released-package-and-main) للميزات المضافة بعد أحدث إصدار على npm.
## دراسات الحالة
### DX-Ball: إعادة بناء حساب توزيع الصوت بين القناتين
تتبّع استدعاء صوت إلى الدالة المساعدة التي تحوّل الموضع إلى توزيع بين القناتين، وافحص التعليمات، وحوّل الشيفرة شبه البرمجية غير المكتملة إلى C. تجتاز إعادة البناء 3,205 حالات اختبار على x86 الأصلي، وتعيد إنتاج جميع بايتات الدالة المترجمة البالغ عددها 63 بايتًا.
[اقرأ دراسة الحالة](https://rea.tools/showcase/dx-ball/) ·
[مستودع إعادة البناء](https://github.com/N0zoM1z0/dx-ball)
### Notion: تتبّع جسر الحافظة في Electron
اعثر على API الحافظة في عملية العرض، وتتبّعه عبر preload وIPC إلى العملية الرئيسية، وافحص صيغة الحافظة ذات التنسيق الغني.
[اقرأ دراسة الحالة](https://rea.tools/showcase/notion/)
### TH04: استعادة حساب حلقة المقذوفات في DOS
افحص تعليمات اللعبة الأصلية على PC-98 ذات 16 بت، واستعد حساب الزوايا الثابتة والموجّهة نحو الهدف، وقارن شيفرة C++ المعاد بناؤها بمخرجات المترجم المستخدم آنذاك.
[اقرأ دراسة الحالة](https://rea.tools/showcase/th04/) ·
[مستودع إعادة البناء](https://github.com/N0zoM1z0/th04)
إذا استخدمت REA لفحص هدف مثير للاهتمام، فنود الاطلاع عليه. شارك حالتك في [issue](https://github.com/morluto/rea/issues) أو [pull request](https://github.com/morluto/rea/pulls)، مع ذكر الهدف، وسؤالك، وكيف ساعدك REA، وما توصلت إليه.
## الأسئلة الشائعة
<details>
<summary><strong>هل يتضمن REA محرك فك ترجمة خاصًا به؟</strong></summary>
<summary><strong>أي وكلاء يمكنهم استخدام REA؟</strong></summary>
لا. يستطيع Setup تثبيت Hopper، لكنه يظل برنامجًا منفصلًا بترخيص خاص به. يوفر REA واجهة CLI وخادم MCP وسير العمل المخصص للوكلاء.
أي وكيل يدعم خوادم MCP المحلية. يهيّئ الإعداد [الوكلاء المدعومين](docs/installation.md#supported-agents)؛ ويمكن للعملاء الآخرين استخدام [تسجيل MCP يدويًا](docs/installation.md#mcp-registry).
</details>
<details>
<summary><strong>هل يجب أن يكون Hopper مفتوحًا مسبقًا؟</strong></summary>
<summary><strong>هل أحتاج إلى Hopper أو Ghidra أو IDA؟</strong></summary>
لا. يستطيع REA تشغيله. لكن Hopper قد يصبح مرئيًا أو يطلب إدخالًا عند الحاجة إلى قرار تفاعلي.
يستخدم تحليل الشيفرة الأصلية المتعمق أحدها. يعمل الفحص الثابت لـ JavaScript و.NET دون محرك لتحليل الشيفرة الأصلية. يستطيع الإعداد تثبيت Hopper بعد الموافقة؛ أما Ghidra وIDA فيستخدمان تثبيتاتك الموجودة. راجع [إعداد الموفّرين](docs/installation.md#hopper).
</details>
<details>
<summary><strong>هل يعمل REA على Linux أو Windows؟</strong></summary>
<summary><strong>هل أحتاج إلى تشغيل Hopper أولًا؟</strong></summary>
يدعم REA نظام macOS 12+ وUbuntu 24.04+ وFedora 41+ وArch Linux بنواة 64 بت وCachyOS. يعمل تحليل Ghidra للقراءة فقط على Linux x64 وmacOS x64/arm64 مع Ghidra 12.1.x وJDK الكامل الذي يعلنه التثبيت (JDK 21 أو أحدث دون حد أعلى في إصدارات 12.1 الحالية)، إضافة إلى حد Windows x64 P0 التجريبي لتطبيقات PE الأصلية x86-64 على NTFS محلي؛ راجع [دليل Windows Ghidra P0](docs/windows-ghidra-p0.md) للنطاق الموثق.
يشغّل REA برنامج Hopper عندما تحتاج العملية إليه. على macOS، قد تظهر عند التشغيل الأول نافذة تطلب اختيار وضع العرض التجريبي أو تفعيل الترخيص. راجع [تشغيل Hopper واستكشاف الأخطاء](docs/installation.md#launcher-paths-and-troubleshooting).
</details>
<details>
<summary><strong>هل يمكنني تحليل قواعد Hopper المحفوظة؟</strong></summary>
<summary><strong>ماذا يفعل تثبيت المهارة من skills.sh؟</strong></summary>
نعم. يدعم وقت التشغيل الملفات التنفيذية وقواعد `.hop`.
توفّر المهارة تعليمات الاستقصاء لوكيلك. استخدم `rea setup` لتسجيل خادم MCP الخاص بـ REA وتثبيت التعليمات المطابقة، ثم أعد تشغيل وكيلك. راجع [تثبيت المهارة وحدها](docs/installation.md#skill-only-installation).
</details>
## التطوير
<details>
<summary><strong>ما الشيفرة التي يعيدها REA؟</strong></summary>
راجع [CONTRIBUTING.md](CONTRIBUTING.md) لإعداد بيئة التطوير وبنية المشروع والاختبارات وإرشادات الإصدار.
يعيد تحليل الشيفرة الأصلية شيفرة شبه برمجية وتعليمات تجميع. يستعيد تحليل JavaScript/Electron الوحدات والعلاقات بينها. يستخدم وكيلك هذه النتائج لكتابة تنفيذ واختباره؛ وتقدّم [دراسات الحالة](#دراسات-الحالة) أمثلة عملية.
## المشروع
</details>
- [حزمة npm](https://www.npmjs.com/package/rea-agents)
- [GitHub](https://github.com/morluto/rea)
- [القضايا والطلبات](https://github.com/morluto/rea/issues)
- [Hopper Disassembler](https://www.hopperapp.com/)
- [سياسة الأمان](SECURITY.md)
<details>
<summary><strong>هل يرفع REA تطبيقي؟</strong></summary>
## تاريخ النجوم
يحلّل REA الأهداف محليًا. يتلقى وكيلك نتائج الأدوات، ولموفّر النموذج الذي يستخدمه سياسة بيانات خاصة به.
🎉 **وصلنا إلى 20,000 نجمة على GitHub، شكرًا لكم!**
</details>
شكرًا لكل من يستخدم REA ويبلّغ عن الأخطاء ويختبر الإصدارات ويساهم في إصلاحها.
<details>
<summary><strong>ماذا أفعل إذا واجهت خطأ؟</strong></summary>
حدّث أولًا؛ فقد يكون إصدار حديث قد أصلح المشكلة بالفعل.
لواجهة CLI المثبّتة عبر npm:
```bash
rea update
```
لإعداد الوكيل عبر `npx`:
```bash
npx rea-agents@latest setup
```
إذا كنت تستخدم وكيلًا، فأكمل [تحديث الإعداد](#تحديث-rea) وأعد تشغيله. أعد محاولة المهمة نفسها. إذا استمرت المشكلة، [افتح issue](https://github.com/morluto/rea/issues) يتضمن إصدار REA، ونوع الهدف، وخطوات إعادة الإنتاج، ومخرجات الخطأ.
</details>
## التوثيق
ابدأ بـ [الأدلة العملية](https://rea.tools/guides/) على الموقع. للخيارات الدقيقة والمتطلبات وعقود النتائج:
- [التثبيت والإعداد](docs/installation.md): تسجيل الوكلاء، وتهيئة الموفّرين، والتحديثات، وإلغاء التثبيت.
- [الجاهزية واستكشاف الأخطاء](docs/installation.md#check-readiness-for-your-task): تشخيص وكيل أو محرك تحليل محدد.
- [CLI وEvidence](docs/cli.md): الأوامر، واختيار الموفّر، واللقطات، والاستيراد/التصدير، وحالات الخروج.
- [عقود MCP](docs/mcp-contracts.md) و[تعليمات الوكلاء](docs/mcp-prompts.md): نتائج الأدوات، والجلسات، والاستقصاءات الموجّهة.
- [كتالوج الأدوات](docs/mcp-contracts.md#generated-catalog): قائمة الأدوات والموفّرين وأوامر CLI المولّدة أثناء البناء.
- [خطة العمل](docs/roadmap.md): الأعمال المخطط لها ومتابعة القدرات.
أبلغ عن الثغرات وفق [SECURITY.md](SECURITY.md).
## المساهمة
نرحّب بمساعدتك في تطوير REA! [افتح issue](https://github.com/morluto/rea/issues) للإبلاغ عن خطأ أو اقتراح ميزة، أو [أرسل pull request](https://github.com/morluto/rea/pulls) لتحسين الشيفرة أو التوثيق.
راجع [CONTRIBUTING.md](CONTRIBUTING.md) لإعداد بيئة التطوير والفحوص، و[دليل الاختبار](docs/testing.md) لمسارات التحقق، و[خريطة البنية](docs/architecture.mermaid) لهيكل المشروع.
## روابط المشروع
[الموقع](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [الأمان](SECURITY.md)
## سجل النجوم
🎉 **30,000 نجمة على GitHub — شكرًا لكم!**
شكرًا لكل من يستخدم REA، ويبلّغ عن الأخطاء، ويقترح ميزات، ويختبر إصدارات البناء، ويساهم بالإصلاحات.
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="تاريخ نجوم REA على GitHub" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="سجل نجوم REA على GitHub" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
</picture>
</a>
## إخلاء المسؤولية
يوفّر REA أدوات لأبحاث الهندسة العكسية والتحليل وإعادة البناء المشروعة. أنت مسؤول عن الحصول على أي تفويض مطلوب والالتزام بالقوانين المعمول بها. لا يؤيد المشروع الاستخدام غير القانوني أو غير المصرّح به.
## الترخيص
[MIT](LICENSE)
+285
View File
@@ -0,0 +1,285 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · **Deutsch** · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA: Alles reverse-engineeren
### Ein MCP für das Reverse Engineering von Binärdateien, Anwendungen und Laufzeitverhalten.
**Eine Funktion gefällt dir? Verstehe, wie sie funktioniert – bis hinunter auf die Binärebene.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[Website](https://rea.tools/) · [Anleitungen](https://rea.tools/guides/) · [Fallstudien](https://rea.tools/showcase/)**
[Schnellstart](#schnellstart) · [So funktioniert REA](#so-funktioniert-rea) · [Was du analysieren kannst](#was-du-analysieren-kannst) · [Fallstudien](#fallstudien) · [Häufige Fragen](#häufige-fragen) · [Dokumentation](#dokumentation)
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA startet seine Analysebrücke in Hopper und untersucht eine native Binärdatei" width="1200" />
<br />
<table aria-label="REA-Community">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
<img src="docs/assets/discord.svg" height="42" alt="Discord" /><br />
<strong>Der Reverse-Engineering-Community beitreten</strong>
</a><br />
<sub>Discord · Fragen und Antworten · Ergebnisse teilen</sub>
</td>
</tr>
</table>
<br />
</div>
---
Du hast in einer App eine Funktion entdeckt, die du in dein eigenes Produkt übernehmen möchtest? Lass deinen Agenten sie mit REA untersuchen. Er kann die App ohne ihren Quellcode analysieren, die Funktionsweise erklären, Belege zeigen und eine passende Umsetzung für dein Projekt entwickeln.
REA verbindet deinen Agenten mit Werkzeugen zum Untersuchen nativer Binärdateien, von JavaScript- und Electron-Apps, .NET-Assemblies und Websites. Dieselben Werkzeuge kannst du auch im Terminal nutzen. Die Analyse läuft lokal; die Ergebnisse enthalten die Belege und Einschränkungen hinter jeder Schlussfolgerung.
Die Einrichtung registriert REA bei deinem Agenten und installiert passende Workflow-Anweisungen. Für native Analysen kannst du eine vorhandene Hopper- oder Ghidra-Installation verwenden. Mit deiner Zustimmung kann die Einrichtung auch Hopper installieren. Statische JavaScript-Analysen benötigen keines der beiden Programme.
> **[Besuche die REA-Website](https://rea.tools/)** für Einrichtungshinweise, bebilderte Anleitungen und echte Fallstudien.
## Schnellstart
### Deinen Agenten einrichten
Wenn Node.js und npm installiert sind, führe diesen Befehl aus:
```bash
npx rea-agents setup
```
Wähle deine Agenten aus, prüfe die geplanten Änderungen und bestätige sie. Die Einrichtung fügt den MCP-Server von REA und passende Workflow-Anweisungen hinzu und sichert vorhandene Konfigurationen. Starte deinen Agenten anschließend neu.
Die Einrichtung unterstützt Claude Code, Codex, Cursor, Gemini CLI, Grok Build und [weitere Agenten](docs/installation.md#supported-agents). Informationen zur Provider-Konfiguration und manuellen MCP-Registrierung findest du unter [Installation und Einrichtung](docs/installation.md).
### Deinen Agenten fragen
```text
Untersuche, wie die Suche in der Notes-App funktioniert, zeige mir die Belege und entwickle eine ähnliche Funktion für mein Projekt.
```
Ersetze Notes durch deine Ziel-App und benenne die Funktion, die du verstehen möchtest.
### Das Terminal verwenden
Untersuche ein entpacktes JavaScript-/Electron-App-Verzeichnis oder eine ASAR-Datei:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
Das Ergebnis enthält Module, Imports, Electron-Grenzen und die zugehörigen Belege. Ersetze den Pfad durch dein Ziel, beispielsweise `"D:/apps/example"` unter Windows.
Für die regelmäßige Nutzung kannst du den Befehl `rea` installieren:
```bash
npm install --global rea-agents
rea --help
```
Konfiguriere für native Analysen zuerst einen Provider. Native Befehle, Provider-Auswahl, Snapshots und Skriptnutzung beschreibt der [CLI- und Evidence-Leitfaden](docs/cli.md).
### REA aktualisieren
REA entwickelt sich schnell weiter; neue Versionen enthalten häufig Fehlerbehebungen. Halte deine Installation aktuell.
Für eine über npm installierte CLI:
```bash
rea update
```
Um die Agentenregistrierungen und den Skill zu aktualisieren, führe den Einrichtungsbefehl aus, den das Update ausgibt.
Wenn du `npx` verwendest, aktualisiere die Agenteneinrichtung mit:
```bash
npx rea-agents@latest setup
```
Prüfe die Änderungen und starte deinen Agenten neu. Für einzelne CLI-Aufrufe verwende `npx rea-agents@latest`, gefolgt vom gewünschten Befehl.
## So funktioniert REA
Dein Agent ruft REA über MCP auf, um das Ziel zu untersuchen und relevanten Code zu verfolgen. REA liefert Ergebnisse mit ihren Belegen zurück. Der Agent nutzt sie für weitere Fragen, Erklärungen oder zum Schreiben und Testen einer Implementierung. CLI-Befehle verwenden dieselben Workflows.
![REA-Untersuchungsablauf: Dein Agent fragt nach einem lokalen Ziel, REA untersucht und verfolgt es mit Analysewerkzeugen, und der Agent nutzt den zurückgegebenen Code, Verweise und offene Fragen zum Erklären, Implementieren und Testen.](website/public/assets/figures/rea-investigation-flow.svg)
[Abbildung in voller Größe öffnen](website/public/assets/figures/rea-investigation-flow.svg).
<a id="current-status"></a>
## Was du analysieren kannst
REA benötigt Node.js 22.x (>=22.19), 24.x (>=24.11) oder 26+ sowie npm. Zusätzliche Werkzeuge und unterstützte Hostsysteme hängen vom Ziel ab:
| Ziel | Was REA zurückgibt | Voraussetzungen und Anleitung |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Native Binärdateien | Pseudocode, Assemblercode, Zeichenketten, Symbole, Aufrufe und Verweise | Hopper, Ghidra oder IDA; [native Analyse](https://rea.tools/guides/native/) |
| Offline-ELF-Struktur | Sektionen, Segmente, ursprüngliche Symbole/Relokationen und mögliche Schutzmaßnahmen aus statischer Analyse | Vom Aufrufer bereitgestelltes pwntools unter Linux x64; [Binärdiagnostik](docs/binary-diagnostics.md) |
| EVM-Bytecode | Dispatch-Selektoren, Byte-Offsets, abgeleitete Argumente und Veränderbarkeit des Zustands | Lokale Eingabe mit Rohbytes oder Hexdarstellung; [Offline-EVM-Leitfaden](docs/evm-bytecode.md) |
| Aufgezeichnete Linux-Abstürze | Unverarbeitete Note-Einträge, Register/Signale aller aufgezeichneten Threads und optionale Zuordnungskandidaten | Vom Aufrufer bereitgestelltes pwntools; optional GDB/pwndbg; [aufgezeichnete Abstürze](docs/recorded-crashes.md) |
| JavaScript / Electron | Module, Imports, Source Maps, Routen, IPC und Beziehungen zu nativen Add-ons | Node.js und npm; [Anwendungsanalyse](https://rea.tools/guides/javascript/) |
| Websites | Seitenstruktur, Skripte, Netzwerkbeobachtungen und angeforderte Screenshots | Ein Browser aus der Chrome-Familie; [Browseranalyse](https://rea.tools/guides/browser/) |
| Gespeicherte Netzwerkaufzeichnungen | Anfragen, Antworten, zugängliche Nutzdaten und Quellpositionen | HAR; mitmdump unter Linux für Aufzeichnungen im nativen mitmproxy-Format; [Leitfaden zu Netzwerkaufzeichnungen](docs/web-network-captures.md) |
| .NET-Assemblies | Metadaten, CIL-Anweisungen, deklarierte native Abhängigkeiten und Build-Vergleiche | Statische Untersuchung; [Leitfaden für verwalteten Code](docs/managed-code-analysis.md) |
| Android-APKs | Manifest-Deklarationen, Klassen, dekompilierte Methoden und Verweise | JADX ohne grafische Oberfläche und ein vollständiges JDK unter Linux/macOS; [Android-Leitfaden](docs/android-analysis.md) |
| Firmware | Bereiche, Extraktionsergebnisse und Übergaben an die native Analyse | Binwalk / Unblob unter Linux; [Firmware-Leitfaden](docs/firmware-analysis.md) |
| Pakete und Ressourcen | Dateiinventare, Hashwerte, plists, Aufbau von Apple-Bundles und extrahierte Ressourcen | [Artefakt- und JavaScript-Leitfaden](docs/javascript-artifact-reconstruction.md), [Apple-Anwendungen](docs/apple-application-analysis.md) |
| Prozessverhalten | Terminalausgabe, Interaktionen, Beobachtungen zu Prozessende und Dateisystem sowie Laufvergleiche | Linux/macOS mit nativer PTY; [Prozessaufzeichnung](docs/process-capture.md) |
Statische JavaScript- und .NET-Untersuchungen lesen die bereitgestellten Dateien, ohne die Anwendung auszuführen. Laufzeitaufzeichnungen führen das gewählte Ziel mit deinen Benutzerrechten aus oder interagieren damit; die jeweiligen Anleitungen beschreiben die Auswirkungen.
<a id="choosing-a-deep-analysis-provider"></a>
Native Formate und unterstützte Hostsysteme unterscheiden sich je nach Provider. Siehe [Hopper- und Ghidra-Einrichtung](docs/installation.md#hopper), den [IDA-Leitfaden](docs/ida-provider.md) und die [experimentelle Ghidra-Unterstützung unter Windows](docs/windows-ghidra-p0.md). Ghidra unterstützt auch [16-Bit-DOS-Analyse](docs/ghidra-dos.md). Die Provider-Auswahl beschreibt der [CLI-Leitfaden](docs/cli.md#choose-a-provider). Prüfe die [Verfügbarkeit in Releases](docs/installation.md#released-package-and-main) für Funktionen, die seit der neuesten npm-Version hinzugekommen sind.
## Fallstudien
### DX-Ball: Eine Stereo-Panning-Berechnung rekonstruieren
Verfolge einen Sound-Aufruf zur Hilfsfunktion, die Positionen in Panning-Werte umrechnet, untersuche die Anweisungen und überführe unvollständigen Pseudocode in C. Die Rekonstruktion besteht 3.205 Tests mit dem ursprünglichen x86-Code und reproduziert alle 63 Bytes der kompilierten Funktion.
[Fallstudie lesen](https://rea.tools/showcase/dx-ball/) · [Rekonstruktionsrepository](https://github.com/N0zoM1z0/dx-ball)
### Notion: Die Electron-Zwischenablagebrücke verfolgen
Finde die Zwischenablage-API des Renderers, verfolge sie über preload und IPC bis zum Hauptprozess und untersuche das Zwischenablageformat samt Formatierungsdaten.
[Fallstudie lesen](https://rea.tools/showcase/notion/)
### TH04: Eine DOS-Geschossring-Berechnung wiederherstellen
Untersuche die 16-Bit-Anweisungen des ursprünglichen PC-98-Spiels, rekonstruiere die festen und zielgerichteten Winkelberechnungen und vergleiche das rekonstruierte C++ mit der Ausgabe des damaligen Compilers.
[Fallstudie lesen](https://rea.tools/showcase/th04/) · [Rekonstruktionsrepository](https://github.com/N0zoM1z0/th04)
Wenn du mit REA etwas Interessantes untersucht hast, würden wir es gern sehen. Teile deine Fallstudie in einem [Issue](https://github.com/morluto/rea/issues) oder [Pull Request](https://github.com/morluto/rea/pulls) und beschreibe das Ziel, deine Frage, wie REA geholfen hat und was du herausgefunden hast.
## Häufige Fragen
<details>
<summary><strong>Welche Agenten können REA verwenden?</strong></summary>
Jeder Agent, der lokale MCP-Server unterstützt. Die Einrichtung konfiguriert die [unterstützten Agenten](docs/installation.md#supported-agents); andere Clients können die [manuelle MCP-Registrierung](docs/installation.md#mcp-registry) nutzen.
</details>
<details>
<summary><strong>Brauche ich Hopper, Ghidra oder IDA?</strong></summary>
Für tiefgehende native Analysen wird eines dieser Programme verwendet. Statische JavaScript- und .NET-Untersuchungen funktionieren ohne native Analyse-Engine. Die Einrichtung kann Hopper nach Zustimmung installieren; Ghidra und IDA nutzen deine vorhandenen Installationen. Siehe [Provider-Einrichtung](docs/installation.md#hopper).
</details>
<details>
<summary><strong>Muss ich Hopper vorher starten?</strong></summary>
REA startet Hopper, wenn eine Operation es benötigt. Unter macOS kann beim ersten Start ein Dialog erscheinen, in dem du den Demomodus auswählst oder deine Lizenz aktivierst. Siehe [Hopper-Start und Fehlerbehebung](docs/installation.md#launcher-paths-and-troubleshooting).
</details>
<details>
<summary><strong>Was bewirkt die Installation des Skills über skills.sh?</strong></summary>
Der Skill stellt Untersuchungsanweisungen für deinen Agenten bereit. Verwende `rea setup`, um den MCP-Server von REA zu registrieren und passende Anweisungen zu installieren. Starte deinen Agenten anschließend neu. Siehe [Installation nur des Skills](docs/installation.md#skill-only-installation).
</details>
<details>
<summary><strong>Welchen Code liefert REA zurück?</strong></summary>
Native Analysen liefern Pseudocode und Assemblercode. JavaScript-/Electron-Analysen rekonstruieren Module und ihre Beziehungen. Dein Agent nutzt diese Ergebnisse zum Schreiben und Testen einer Implementierung; die [Fallstudien](#fallstudien) zeigen konkrete Beispiele.
</details>
<details>
<summary><strong>Lädt REA meine App hoch?</strong></summary>
REA analysiert Ziele lokal. Dein Agent erhält die Werkzeugergebnisse; sein Modellanbieter hat eine eigene Datenrichtlinie.
</details>
<details>
<summary><strong>Was soll ich tun, wenn ich einen Fehler finde?</strong></summary>
Aktualisiere zuerst; eine neuere Version könnte den Fehler bereits behoben haben.
Für eine über npm installierte CLI:
```bash
rea update
```
Für die Agenteneinrichtung über `npx`:
```bash
npx rea-agents@latest setup
```
Wenn du einen Agenten verwendest, schließe die [Aktualisierung der Einrichtung](#rea-aktualisieren) ab und starte ihn neu. Wiederhole dieselbe Aufgabe. Besteht das Problem weiterhin, [öffne ein Issue](https://github.com/morluto/rea/issues) mit deiner REA-Version, dem Zieltyp, den Reproduktionsschritten und der Fehlerausgabe.
</details>
## Dokumentation
Beginne mit den [praxisnahen Anleitungen](https://rea.tools/guides/) auf der Website. Genaue Optionen, Voraussetzungen und Ergebniskontrakte findest du hier:
- [Installation und Einrichtung](docs/installation.md): Agentenregistrierung, Provider-Konfiguration, Updates und Deinstallation.
- [Bereitschaft und Fehlerbehebung](docs/installation.md#check-readiness-for-your-task): Diagnose eines bestimmten Agenten oder einer Analyse-Engine.
- [CLI und Evidence](docs/cli.md): Befehle, Provider-Auswahl, Snapshots, Import/Export und Exit-Status.
- [MCP-Kontrakte](docs/mcp-contracts.md) und [Agenten-Prompts](docs/mcp-prompts.md): Werkzeugergebnisse, Sitzungen und geführte Untersuchungen.
- [Werkzeugkatalog](docs/mcp-contracts.md#generated-catalog): Beim Build erzeugtes Inventar von Werkzeugen, Providern und CLI-Befehlen.
- [Roadmap](docs/roadmap.md): Geplante Arbeiten und Fortschrittsübersichten zu Fähigkeiten.
Melde Sicherheitslücken gemäß [SECURITY.md](SECURITY.md).
## Mitwirken
Wir freuen uns über deine Hilfe bei REA! [Öffne ein Issue](https://github.com/morluto/rea/issues), um einen Fehler zu melden oder eine Funktion vorzuschlagen, oder [sende einen Pull Request](https://github.com/morluto/rea/pulls), um Code oder Dokumentation zu verbessern.
Die Entwicklungsumgebung und Prüfungen beschreibt [CONTRIBUTING.md](CONTRIBUTING.md), die Verifikationspfade der [Testleitfaden](docs/testing.md) und die Projektstruktur die [Architekturübersicht](docs/architecture.mermaid).
## Projektlinks
[Website](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [Sicherheit](SECURITY.md)
## Sternverlauf
🎉 **30.000 GitHub-Sterne – vielen Dank!**
Danke an alle, die REA nutzen, Fehler melden, Funktionswünsche äußern, Builds testen und Fehlerbehebungen beitragen.
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="GitHub-Sternverlauf von REA" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
</picture>
</a>
## Haftungsausschluss
REA stellt Werkzeuge für rechtmäßige Reverse-Engineering-Forschung, Analyse und Rekonstruktion bereit. Du bist dafür verantwortlich, erforderliche Genehmigungen einzuholen und geltendes Recht einzuhalten. Das Projekt unterstützt keine rechtswidrige oder unbefugte Nutzung.
## Lizenz
[MIT](LICENSE)
+285
View File
@@ -0,0 +1,285 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · **Español** · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA: Ingeniería inversa de cualquier cosa
### Un solo MCP para la ingeniería inversa de binarios, aplicaciones y comportamiento en ejecución.
**¿Ves una función que te gusta? Entiende cómo funciona, hasta el nivel binario.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[Sitio web](https://rea.tools/) · [Guías](https://rea.tools/guides/) · [Casos prácticos](https://rea.tools/showcase/)**
[Inicio rápido](#inicio-rápido) · [Cómo funciona REA](#cómo-funciona-rea) · [Qué puedes analizar](#qué-puedes-analizar) · [Casos prácticos](#casos-prácticos) · [Preguntas frecuentes](#preguntas-frecuentes) · [Documentación](#documentación)
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA inicia su puente de análisis dentro de Hopper mientras inspecciona un binario nativo" width="1200" />
<br />
<table aria-label="Comunidad REA">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
<img src="docs/assets/discord.svg" height="42" alt="Discord" /><br />
<strong>Únete a la comunidad de ingeniería inversa</strong>
</a><br />
<sub>Discord · Preguntas y respuestas · Comparte tus resultados</sub>
</td>
</tr>
</table>
<br />
</div>
---
¿Has visto una función en una aplicación que quieres incorporar a tu producto? Pide a tu agente que la investigue con REA. Puede inspeccionar la aplicación sin su código fuente, explicar cómo funciona la función, mostrar las pruebas y crear una versión para tu proyecto.
REA conecta tu agente con herramientas para inspeccionar binarios nativos, aplicaciones JavaScript y Electron, ensamblados .NET y sitios web. También puedes usar las mismas herramientas desde la terminal. El análisis se ejecuta localmente y los resultados incluyen las pruebas y limitaciones que sustentan cada conclusión.
La configuración registra REA en tu agente e instala las instrucciones de trabajo correspondientes. El análisis nativo puede usar una instalación existente de Hopper o Ghidra; la configuración también puede instalar Hopper, si lo autorizas. El análisis estático de JavaScript no necesita ninguno de los dos motores.
> **[Visita el sitio web de REA](https://rea.tools/)** para encontrar instrucciones de configuración, guías ilustradas y casos reales.
## Inicio rápido
### Configura tu agente
Con Node.js y npm instalados, ejecuta:
```bash
npx rea-agents setup
```
Elige tus agentes, revisa los cambios propuestos y apruébalos. La configuración añade el servidor MCP de REA y las instrucciones de trabajo correspondientes, con copias de seguridad de la configuración existente. Reinicia tu agente al terminar.
La configuración admite Claude Code, Codex, Cursor, Gemini CLI, Grok Build y [otros agentes](docs/installation.md#supported-agents). Consulta [instalación y configuración](docs/installation.md) para configurar proveedores y registrar MCP manualmente.
### Pregunta a tu agente
```text
Investiga cómo funciona la búsqueda en la aplicación Notes, muéstrame las pruebas y crea una función similar para mi proyecto.
```
Sustituye Notes por la aplicación objetivo y especifica la función que quieres entender.
### Usa la terminal
Inspecciona un directorio extraído de una aplicación JavaScript/Electron o un archivo ASAR:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
El resultado incluye módulos, importaciones, límites de Electron y sus pruebas. Sustituye la ruta por la de tu objetivo, por ejemplo `"D:/apps/example"` en Windows.
Para instalar el comando `rea` y usarlo habitualmente:
```bash
npm install --global rea-agents
rea --help
```
Para el análisis nativo, configura primero un proveedor. Consulta la [guía de CLI y Evidence](docs/cli.md) para los comandos nativos, la selección de proveedores, las instantáneas y el uso en scripts.
### Actualiza REA
REA cambia rápidamente y las nuevas versiones incluyen correcciones frecuentes. Mantén tu instalación actualizada.
Para una CLI instalada mediante npm:
```bash
rea update
```
Para actualizar los registros de tus agentes y la skill, ejecuta el comando de configuración que muestra la actualización.
Si usas `npx`, actualiza la configuración de tu agente con:
```bash
npx rea-agents@latest setup
```
Revisa los cambios de configuración y reinicia tu agente. Para ejecutar comandos de CLI puntuales, usa `npx rea-agents@latest` seguido del comando.
## Cómo funciona REA
Tu agente llama a REA mediante MCP para inspeccionar el objetivo y seguir el código relevante. REA devuelve los hallazgos junto con sus pruebas. El agente los utiliza para hacer preguntas de seguimiento, explicar el comportamiento o escribir y probar una implementación. Los comandos de CLI usan los mismos flujos de trabajo.
![Flujo de investigación de REA: tu agente pregunta sobre un objetivo local, REA lo inspecciona y rastrea con herramientas de análisis, y el agente utiliza el código, las referencias y los aspectos desconocidos devueltos para explicar, implementar y probar.](website/public/assets/figures/rea-investigation-flow.svg)
[Abre la figura a tamaño completo](website/public/assets/figures/rea-investigation-flow.svg).
<a id="current-status"></a>
## Qué puedes analizar
REA requiere Node.js 22.x (>=22.19), 24.x (>=24.11) o 26+, además de npm. Las herramientas adicionales y los sistemas anfitriones compatibles dependen del objetivo:
| Objetivo | Qué devuelve REA | Requisitos y guía |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Binarios nativos | Pseudocódigo, ensamblador, cadenas, símbolos, llamadas y referencias | Hopper, Ghidra o IDA; [análisis nativo](https://rea.tools/guides/native/) |
| Estructura ELF sin ejecutar | Secciones, segmentos, símbolos/reubicaciones originales y posibles medidas de mitigación detectadas mediante análisis estático | pwntools proporcionado por quien llama, en Linux x64; [diagnóstico de binarios](docs/binary-diagnostics.md) |
| Bytecode EVM | Selectores de despacho, desplazamientos en bytes, argumentos inferidos y mutabilidad | Entrada local con bytes sin procesar o representación hexadecimal; [guía de EVM sin ejecutar](docs/evm-bytecode.md) |
| Fallos de Linux registrados | Registros note sin procesar, registros/señales de cada hilo registrado y posibles correspondencias de memoria opcionales | pwntools proporcionado por quien llama; GDB/pwndbg opcionales; [fallos registrados](docs/recorded-crashes.md) |
| JavaScript / Electron | Módulos, importaciones, mapas de código fuente, rutas, IPC y relaciones con complementos nativos | Node.js y npm; [análisis de aplicaciones](https://rea.tools/guides/javascript/) |
| Sitios web | Estructura de la página, scripts, observaciones de red y capturas de pantalla solicitadas | Un navegador de la familia Chrome; [análisis del navegador](https://rea.tools/guides/browser/) |
| Capturas de red guardadas | Solicitudes, respuestas, contenido accesible de las cargas útiles y ubicaciones de origen | HAR; mitmdump en Linux para capturas en formato nativo de mitmproxy; [guía de capturas](docs/web-network-captures.md) |
| Ensamblados .NET | Metadatos, instrucciones CIL, dependencias nativas declaradas y comparaciones de compilaciones | Inspección estática; [guía de código administrado](docs/managed-code-analysis.md) |
| APK de Android | Declaraciones del manifiesto, clases, métodos descompilados y referencias | JADX sin interfaz gráfica y un JDK completo en Linux/macOS; [guía de Android](docs/android-analysis.md) |
| Firmware | Regiones, resultados de extracción y derivaciones al análisis nativo | Binwalk / Unblob en Linux; [guía de firmware](docs/firmware-analysis.md) |
| Paquetes y recursos | Inventarios de archivos, resúmenes criptográficos, plists, estructura de paquetes de Apple y recursos extraídos | [Guía de artefactos y JavaScript](docs/javascript-artifact-reconstruction.md), [aplicaciones de Apple](docs/apple-application-analysis.md) |
| Comportamiento de procesos | Salida de la terminal, interacciones, observaciones de salida y del sistema de archivos, y comparaciones entre ejecuciones | Linux/macOS con una PTY nativa; [captura de procesos](docs/process-capture.md) |
La inspección estática de JavaScript y .NET lee los archivos suministrados sin ejecutar la aplicación. La captura en ejecución inicia el objetivo seleccionado o interactúa con él con los permisos de tu usuario; cada guía describe sus efectos.
<a id="choosing-a-deep-analysis-provider"></a>
Los formatos nativos y los sistemas anfitriones compatibles varían según el proveedor. Consulta [configuración de Hopper y Ghidra](docs/installation.md#hopper), la [guía de IDA](docs/ida-provider.md) y la [compatibilidad experimental de Ghidra en Windows](docs/windows-ghidra-p0.md). Ghidra también admite [análisis de DOS de 16 bits](docs/ghidra-dos.md). Para elegir un proveedor, consulta la [guía de CLI](docs/cli.md#choose-a-provider). Comprueba la [disponibilidad por versión](docs/installation.md#released-package-and-main) de las funciones añadidas después de la última versión publicada en npm.
## Casos prácticos
### DX-Ball: reconstruir un cálculo de panoramización de sonido
Sigue una llamada de sonido hasta la función auxiliar que convierte la posición en panoramización, inspecciona las instrucciones y transforma el pseudocódigo incompleto en C. La reconstrucción supera 3.205 casos con el x86 original y reproduce los 63 bytes de la función compilada.
[Lee el caso práctico](https://rea.tools/showcase/dx-ball/) · [Repositorio de reconstrucción](https://github.com/N0zoM1z0/dx-ball)
### Notion: seguir el puente del portapapeles de Electron
Encuentra la API del portapapeles del proceso de renderizado, síguela a través de preload e IPC hasta el proceso principal e inspecciona el formato enriquecido del portapapeles.
[Lee el caso práctico](https://rea.tools/showcase/notion/)
### TH04: recuperar el cálculo de un anillo de proyectiles en DOS
Inspecciona las instrucciones de 16 bits del juego original para PC-98, recupera los cálculos de ángulos fijos y dirigidos, y compara el C++ reconstruido con la salida del compilador de la época.
[Lee el caso práctico](https://rea.tools/showcase/th04/) · [Repositorio de reconstrucción](https://github.com/N0zoM1z0/th04)
Si has usado REA para investigar algo interesante, nos gustaría verlo. Comparte tu caso en una [issue](https://github.com/morluto/rea/issues) o una [pull request](https://github.com/morluto/rea/pulls), incluyendo el objetivo, tu pregunta, cómo te ayudó REA y qué encontraste.
## Preguntas frecuentes
<details>
<summary><strong>¿Qué agentes pueden usar REA?</strong></summary>
Cualquier agente que admita servidores MCP locales. La configuración prepara los [agentes compatibles](docs/installation.md#supported-agents); otros clientes pueden usar el [registro manual de MCP](docs/installation.md#mcp-registry).
</details>
<details>
<summary><strong>¿Necesito Hopper, Ghidra o IDA?</strong></summary>
El análisis nativo en profundidad utiliza uno de ellos. La inspección estática de JavaScript y .NET funciona sin un motor de análisis nativo. La configuración puede instalar Hopper después de tu aprobación; Ghidra e IDA usan las instalaciones que ya tienes. Consulta [configuración de proveedores](docs/installation.md#hopper).
</details>
<details>
<summary><strong>¿Tengo que iniciar Hopper primero?</strong></summary>
REA inicia Hopper cuando una operación lo necesita. En macOS, puede aparecer un diálogo al iniciarlo por primera vez para elegir el modo de demostración o activar tu licencia. Consulta [inicio de Hopper y resolución de problemas](docs/installation.md#launcher-paths-and-troubleshooting).
</details>
<details>
<summary><strong>¿Qué hace la instalación de la skill desde skills.sh?</strong></summary>
La skill proporciona instrucciones de investigación a tu agente. Usa `rea setup` para registrar el servidor MCP de REA e instalar las instrucciones correspondientes; después reinicia tu agente. Consulta [instalación solo de la skill](docs/installation.md#skill-only-installation).
</details>
<details>
<summary><strong>¿Qué código devuelve REA?</strong></summary>
El análisis nativo devuelve pseudocódigo y ensamblador. El análisis de JavaScript/Electron recupera módulos y sus relaciones. Tu agente utiliza estos hallazgos para escribir y probar una implementación; los [casos prácticos](#casos-prácticos) ofrecen ejemplos completos.
</details>
<details>
<summary><strong>¿REA sube mi aplicación?</strong></summary>
REA analiza los objetivos localmente. Tu agente recibe los resultados de las herramientas, y su proveedor de modelos tiene su propia política de datos.
</details>
<details>
<summary><strong>¿Qué debo hacer si encuentro un error?</strong></summary>
Actualiza primero; una versión reciente puede haber solucionado el problema.
Para una CLI instalada mediante npm:
```bash
rea update
```
Para configurar el agente mediante `npx`:
```bash
npx rea-agents@latest setup
```
Si usas un agente, completa la [actualización de la configuración](#actualiza-rea) y reinícialo. Repite la misma tarea. Si el problema continúa, [abre una issue](https://github.com/morluto/rea/issues) con tu versión de REA, el tipo de objetivo, los pasos para reproducirlo y la salida del error.
</details>
## Documentación
Empieza con las [guías prácticas](https://rea.tools/guides/) del sitio web. Para opciones exactas, requisitos y contratos de resultados:
- [Instalación y configuración](docs/installation.md): registro de agentes, configuración de proveedores, actualizaciones y desinstalación.
- [Preparación y resolución de problemas](docs/installation.md#check-readiness-for-your-task): diagnóstico de un agente o motor de análisis concreto.
- [CLI y Evidence](docs/cli.md): comandos, selección de proveedores, instantáneas, importación/exportación y estados de salida.
- [Contratos de MCP](docs/mcp-contracts.md) y [prompts para agentes](docs/mcp-prompts.md): resultados de herramientas, sesiones e investigaciones guiadas.
- [Catálogo de herramientas](docs/mcp-contracts.md#generated-catalog): inventario de herramientas, proveedores y comandos de CLI generado durante la compilación.
- [Hoja de ruta](docs/roadmap.md): trabajo previsto y seguimiento de capacidades.
Informa de vulnerabilidades siguiendo [SECURITY.md](SECURITY.md).
## Contribuir
¡Nos encantaría que ayudaras con REA! [Abre una issue](https://github.com/morluto/rea/issues) para comunicar un error o proponer una función, o [envía una pull request](https://github.com/morluto/rea/pulls) para mejorar el código o la documentación.
Consulta [CONTRIBUTING.md](CONTRIBUTING.md) para preparar el entorno de desarrollo y las comprobaciones, la [guía de pruebas](docs/testing.md) para los procesos de verificación y el [mapa de arquitectura](docs/architecture.mermaid) para la estructura del proyecto.
## Enlaces del proyecto
[Sitio web](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [Seguridad](SECURITY.md)
## Historial de estrellas
🎉 **30.000 estrellas en GitHub: ¡gracias!**
Gracias a quienes usan REA, informan de errores, solicitan funciones, prueban compilaciones y contribuyen con correcciones.
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="Historial de estrellas de REA en GitHub" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
</picture>
</a>
## Aviso legal
REA proporciona herramientas para la investigación, el análisis y la reconstrucción mediante ingeniería inversa conforme a la ley. Eres responsable de obtener las autorizaciones necesarias y cumplir las leyes aplicables. El proyecto no respalda usos ilegales ni no autorizados.
## Licencia
[MIT](LICENSE)
+266
View File
@@ -0,0 +1,266 @@
<div align="center" dir="rtl">
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · **فارسی**
# REA: مهندسی معکوس هر چیزی
### یک MCP برای مهندسی معکوس فایل‌های باینری، برنامه‌ها و رفتار زمان اجرا.
**قابلیتی دیده‌اید که دوستش دارید؟ سازوکارش را تا سطح باینری درک کنید.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55" /></a>
**[وب‌سایت](https://rea.tools/) · [راهنماها](https://rea.tools/guides/) · [نمونه‌های عملی](https://rea.tools/showcase/)**
[شروع سریع](#شروع-سریع) · [REA چگونه کار می‌کند؟](#rea-چگونه-کار-میکند) · [چه چیزهایی را میتوان تحلیل کرد؟](#چه-چیزهایی-را-میتوان-تحلیل-کرد) · [نمونه‌های عملی](#نمونههای-عملی) · [پرسش‌های متداول](#پرسشهای-متداول) · [مستندات](#مستندات)
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="اجرای پل تحلیل REA در Hopper هنگام بررسی یک فایل باینری بومی" width="1200" />
<br />
<table aria-label="انجمن REA"><tr><td align="center" width="360"><a href="https://discord.gg/GkcryMnJDM"><img src="docs/assets/discord.svg" height="42" alt="Discord" /><br /><strong>به انجمن مهندسی معکوس بپیوندید</strong></a><br /><sub>دیسکورد · پرسش و پاسخ · نمایش دستاوردها</sub></td></tr></table>
<br />
</div>
---
<div dir="rtl">
قابلیتی در یک برنامه دیده‌اید که می‌خواهید در محصول خودتان داشته باشید؟ از ایجنت خود بخواهید با REA آن را بررسی کند. ایجنت می‌تواند بدون دسترسی به کد منبع برنامه، نحوهٔ کار قابلیت را بررسی و توضیح دهد، شواهد را نشان دهد و نسخه‌ای مشابه برای پروژهٔ شما بسازد.
REA ایجنت شما را به ابزارهایی برای بررسی باینری‌های بومی، برنامه‌های JavaScript و Electron، اسمبلی‌های .NET و وب‌سایت‌ها متصل می‌کند. همین ابزارها از ترمینال نیز قابل استفاده‌اند. تحلیل به‌صورت محلی انجام می‌شود و نتایج شامل شواهد و محدودیت‌های پشت هر نتیجه‌گیری هستند.
فرایند راه‌اندازی، REA را در ایجنت شما ثبت می‌کند و دستورالعمل‌های گردش‌کار متناظر را نصب می‌کند. تحلیل بومی می‌تواند از نصب موجود Hopper یا Ghidra استفاده کند؛ همچنین راه‌اندازی می‌تواند با تأیید شما Hopper را به‌صورت اختیاری نصب کند. تحلیل ایستای JavaScript به هیچ‌یک از این موتورهای تحلیل نیاز ندارد.
> برای دستورالعمل‌های راه‌اندازی، راهنماهای تصویری و مطالعات موردی واقعی، **[به وب‌سایت REA مراجعه کنید](https://rea.tools/)**.
## شروع سریع
### ایجنت خود را راه‌اندازی کنید
پس از نصب Node.js و npm، دستور زیر را اجرا کنید:
```bash
npx rea-agents setup
```
ایجنت‌های موردنظر را انتخاب کنید، تغییرات پیشنهادی را بررسی و تأیید کنید. فرایند راه‌اندازی، سرور MCP مربوط به REA و دستورالعمل‌های گردش‌کار متناظر را اضافه می‌کند و از پیکربندی موجود نسخهٔ پشتیبان می‌گیرد. پس از آن ایجنت خود را مجدداً راه‌اندازی کنید.
راه‌اندازی از Claude Code، Codex، Cursor، Gemini CLI، Grok Build و [ایجنت‌های دیگر](docs/installation.md#supported-agents) پشتیبانی می‌کند. برای پیکربندی ارائه‌دهنده و ثبت دستی MCP، [نصب و راه‌اندازی](docs/installation.md) را ببینید.
### از ایجنت خود بپرسید
```text
بررسی کن جست‌وجو در برنامهٔ Notes چگونه کار می‌کند، شواهدش را نشان بده و قابلیتی مشابه برای پروژهٔ من بساز.
```
به‌جای Notes، نام برنامهٔ هدف و قابلیتی را قرار دهید که می‌خواهید درک کنید.
### استفاده از ترمینال
برای بررسی پوشهٔ استخراج‌شدهٔ یک برنامهٔ JavaScript/Electron یا فایل ASAR:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
نتیجه شامل ماژول‌ها، importها، مرزهای Electron و شواهد مربوط به آن‌هاست. مسیر را با مسیر برنامهٔ هدف جایگزین کنید؛ مثلاً در ویندوز `"D:/apps/example"`.
برای نصب دستور `rea` جهت استفادهٔ مداوم:
```bash
npm install --global rea-agents
rea --help
```
برای تحلیل بومی ابتدا یک ارائه‌دهنده پیکربندی کنید. برای دستورات بومی، انتخاب ارائه‌دهنده، snapshotها و اسکریپت‌نویسی، [راهنمای CLI و شواهد](docs/cli.md) را ببینید.
### به‌روزرسانی REA
REA به‌سرعت تغییر می‌کند و نسخه‌های جدید معمولاً شامل رفع اشکال‌های متعددی هستند. نصب خود را به‌روز نگه دارید.
برای CLI نصب‌شده از طریق npm:
```bash
rea update
```
برای به‌روزرسانی ثبت ایجنت‌ها و skill، دستور setup نمایش‌داده‌شده توسط به‌روزرسانی را اجرا کنید.
اگر از `npx` استفاده می‌کنید، راه‌اندازی ایجنت را با دستور زیر به‌روز کنید:
```bash
npx rea-agents@latest setup
```
تغییرات راه‌اندازی را بررسی کنید و ایجنت را دوباره اجرا کنید. برای اجرای یک‌بارهٔ دستورات CLI، از `npx rea-agents@latest` و سپس دستور موردنظر استفاده کنید.
## REA چگونه کار می‌کند؟
ایجنت شما از طریق MCP، REA را فراخوانی می‌کند تا هدف را بررسی و کد مرتبط را ردیابی کند. REA یافته‌ها را همراه با شواهد برمی‌گرداند. ایجنت از این نتایج برای طرح پرسش‌های تکمیلی، توضیح رفتار یا نوشتن و آزمایش یک پیاده‌سازی استفاده می‌کند. دستورات CLI نیز از همین گردش‌کارها بهره می‌برند.
![جریان بررسی REA: ایجنت دربارهٔ هدف محلی سؤال می‌پرسد، REA آن را با ابزارهای تحلیل بررسی و ردیابی می‌کند و ایجنت از کد، ارجاعات و ناشناخته‌های بازگردانده‌شده برای توضیح، پیاده‌سازی و آزمایش استفاده می‌کند.](website/public/assets/figures/rea-investigation-flow.svg)
[نمایش تصویر در اندازهٔ کامل](website/public/assets/figures/rea-investigation-flow.svg).
<a id="current-status"></a>
## چه چیزهایی را می‌توان تحلیل کرد؟
REA به Node.js 22.x (>=22.19)، 24.x (>=24.11) یا 26+، به‌همراه npm نیاز دارد. ابزارهای اضافی و پشتیبانی سیستم میزبان به نوع هدف بستگی دارند:
| هدف | خروجی REA | پیش‌نیازها و راهنما |
| --------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| باینری‌های بومی | شبه‌کد، اسمبلی، رشته‌ها، نمادها، فراخوانی‌ها و ارجاعات | Hopper، Ghidra یا IDA؛ [تحلیل بومی](https://rea.tools/guides/native/) |
| ساختار ELF آفلاین | بخش‌ها، سگمنت‌ها، نمادها و relocationهای اصلی و گزینه‌های احتمالی کاهش آسیب‌پذیری ایستا | pwntools تأمین‌شده توسط فراخواننده روی Linux x64؛ [تشخیص باینری](docs/binary-diagnostics.md) |
| بایت‌کد EVM | selectorهای dispatch، offsetهای بایتی، آرگومان‌های استنباط‌شده و قابلیت تغییر وضعیت | دادهٔ خام/hex محلی؛ [راهنمای EVM آفلاین](docs/evm-bytecode.md) |
| کرش‌های ثبت‌شدهٔ لینوکس | یادداشت‌های خام، رجیسترها/سیگنال‌های تمام threadهای ثبت‌شده و گزینه‌های احتمالی نگاشت | pwntools تأمین‌شده توسط فراخواننده؛ GDB/pwndbg اختیاری؛ [کرش‌های ثبت‌شده](docs/recorded-crashes.md) |
| JavaScript / Electron | ماژول‌ها، importها، source mapها، مسیرها، IPC و ارتباط افزونه‌های بومی | Node.js و npm؛ [تحلیل برنامه](https://rea.tools/guides/javascript/) |
| وب‌سایت‌ها | ساختار صفحه، اسکریپت‌ها، مشاهدات شبکه و اسکرین‌شات‌های درخواستی | مرورگر مبتنی بر Chrome؛ [تحلیل مرورگر](https://rea.tools/guides/browser/) |
| داده‌های ضبط‌شدهٔ شبکه | درخواست‌ها، پاسخ‌ها، payloadهای آشکارشده و مکان آن‌ها در منبع | HAR؛ ابزار mitmdump در لینوکس برای ضبط‌های بومی mitmproxy؛ [راهنمای ضبط شبکه](docs/web-network-captures.md) |
| اسمبلی‌های .NET | فراداده، دستورالعمل‌های CIL، وابستگی‌های بومی اعلام‌شده و مقایسهٔ buildها | بررسی ایستا؛ [راهنمای کد مدیریت‌شده](docs/managed-code-analysis.md) |
| فایل‌های APK اندروید | تعاریف Manifest، کلاس‌ها، متدهای دیکامپایل‌شده و ارجاعات | JADX بدون رابط گرافیکی و JDK کامل در Linux/macOS؛ [راهنمای اندروید](docs/android-analysis.md) |
| Firmware | ناحیه‌ها، نتایج استخراج و تحویل به ابزارهای تحلیل بومی | Binwalk / Unblob در لینوکس؛ [راهنمای Firmware](docs/firmware-analysis.md) |
| بسته‌ها و منابع | فهرست فایل‌ها، digestها، فایل‌های plist، ساختار bundle اپل و منابع استخراج‌شده | [راهنمای آرتیفکت و JavaScript](docs/javascript-artifact-reconstruction.md)، [برنامه‌های اپل](docs/apple-application-analysis.md) |
| رفتار فرایند | خروجی ترمینال، تعاملات، وضعیت خروج، مشاهدات سیستم فایل و مقایسهٔ اجراها | Linux/macOS با PTY بومی؛ [ضبط فرایند](docs/process-capture.md) |
بررسی ایستای JavaScript و .NET فایل‌های ارائه‌شده را بدون اجرای برنامه می‌خواند. ضبط زمان اجرا، هدف انتخاب‌شده را با مجوزهای کاربری شما اجرا می‌کند یا با آن تعامل دارد؛ آثار این عملیات در راهنمای مربوط به هر نوع ضبط توضیح داده شده‌اند.
<a id="choosing-a-deep-analysis-provider"></a>
فرمت‌های بومی و پشتیبانی سیستم میزبان بر اساس ارائه‌دهنده متفاوت‌اند. [راه‌اندازی Hopper و Ghidra](docs/installation.md#hopper)، [راهنمای IDA](docs/ida-provider.md) و [پشتیبانی آزمایشی Ghidra در ویندوز](docs/windows-ghidra-p0.md) را ببینید. Ghidra همچنین از [تحلیل DOS شانزده‌بیتی](docs/ghidra-dos.md) پشتیبانی می‌کند. برای انتخاب ارائه‌دهنده، به [راهنمای CLI](docs/cli.md#choose-a-provider) مراجعه کنید. برای قابلیت‌هایی که پس از آخرین انتشار npm اضافه شده‌اند، [وضعیت انتشار](docs/installation.md#released-package-and-main) را بررسی کنید.
## نمونه‌های عملی
### DX-Ball: بازسازی محاسبهٔ موقعیت استریوی صدا
یک فراخوانی صوتی را تا تابع کمکی تبدیل موقعیت به pan دنبال کنید، دستورالعمل‌ها را بررسی کنید و شبه‌کد ناقص را به C تبدیل کنید. بازسازی حاصل، هر ۳٬۲۰۵ مورد آزمایش x86 اصلی را با موفقیت پشت سر می‌گذارد و هر ۶۳ بایت تابع کامپایل‌شده را دقیقاً بازتولید می‌کند.
[مطالعهٔ موردی](https://rea.tools/showcase/dx-ball/) · [مخزن بازسازی](https://github.com/N0zoM1z0/dx-ball)
### Notion: ردیابی پل کلیپ‌بورد Electron
API کلیپ‌بورد در renderer را پیدا کنید، مسیر آن را از preload و IPC تا فرایند اصلی دنبال کنید و قالب غنی کلیپ‌بورد را بررسی کنید.
[مطالعهٔ موردی](https://rea.tools/showcase/notion/)
### TH04: بازیابی محاسبهٔ حلقهٔ گلوله در DOS
دستورالعمل‌های ۱۶بیتی بازی اصلی PC-98 را بررسی کنید، محاسبات زاویهٔ ثابت و هدف‌گیری‌شده را بازیابی کنید و کد C++ بازسازی‌شده را با خروجی کامپایلر تاریخی مقایسه کنید.
[مطالعهٔ موردی](https://rea.tools/showcase/th04/) · [مخزن بازسازی](https://github.com/N0zoM1z0/th04)
اگر از REA برای بررسی موضوع جالبی استفاده کرده‌اید، خوشحال می‌شویم آن را به اشتراک بگذارید. یک [issue](https://github.com/morluto/rea/issues) یا [pull request](https://github.com/morluto/rea/pulls) ثبت کنید و هدف، پرسش خود، نقش REA و یافته‌هایتان را توضیح دهید.
## پرسش‌های متداول
<details><summary><strong>کدام ایجنت‌ها می‌توانند از REA استفاده کنند؟</strong></summary>
هر ایجنتی که از سرورهای MCP محلی پشتیبانی کند. فرایند setup، [ایجنت‌های پشتیبانی‌شده](docs/installation.md#supported-agents) را پیکربندی می‌کند؛ سایر کلاینت‌ها می‌توانند از [ثبت دستی MCP](docs/installation.md#mcp-registry) استفاده کنند.
</details>
<details><summary><strong>آیا به Hopper، Ghidra یا IDA نیاز دارم؟</strong></summary>
تحلیل عمیق بومی به یکی از آن‌ها نیاز دارد. بررسی ایستای JavaScript و .NET بدون موتور تحلیل بومی کار می‌کند. setup می‌تواند با تأیید شما Hopper را نصب کند؛ Ghidra و IDA از نصب‌های موجود شما استفاده می‌کنند. [راه‌اندازی ارائه‌دهنده](docs/installation.md#hopper) را ببینید.
</details>
<details><summary><strong>آیا باید ابتدا Hopper را اجرا کنم؟</strong></summary>
REA زمانی که عملیاتی به Hopper نیاز داشته باشد آن را اجرا می‌کند. در macOS، ممکن است هنگام نخستین اجرا پنجره‌ای برای انتخاب حالت آزمایشی یا فعال‌سازی مجوز نمایش داده شود. [راه‌اندازی Hopper و رفع اشکال](docs/installation.md#launcher-paths-and-troubleshooting) را ببینید.
</details>
<details><summary><strong>نصب skill از skills.sh چه کاری انجام می‌دهد؟</strong></summary>
این skill دستورالعمل‌های بررسی را برای ایجنت فراهم می‌کند. برای ثبت سرور MCP مربوط به REA و نصب دستورالعمل‌های متناظر، `rea setup` را اجرا کنید و سپس ایجنت را مجدداً راه‌اندازی کنید. [نصب مستقل skill](docs/installation.md#skill-only-installation) را ببینید.
</details>
<details><summary><strong>REA چه نوع کدی برمی‌گرداند؟</strong></summary>
تحلیل بومی، شبه‌کد و اسمبلی برمی‌گرداند. تحلیل JavaScript/Electron ماژول‌ها و روابط میان آن‌ها را بازیابی می‌کند. ایجنت شما از این یافته‌ها برای نوشتن و آزمایش یک پیاده‌سازی استفاده می‌کند؛ [نمونه‌های عملی](#نمونههای-عملی) مثال‌های گام‌به‌گام ارائه می‌دهند.
</details>
<details><summary><strong>آیا REA برنامهٔ من را آپلود می‌کند؟</strong></summary>
REA اهداف را به‌صورت محلی تحلیل می‌کند. ایجنت شما نتایج ابزارها را دریافت می‌کند و ارائه‌دهندهٔ مدل آن سیاست مستقل خود را دربارهٔ داده‌ها دارد.
</details>
<details><summary><strong>اگر با خطا مواجه شدم چه کنم؟</strong></summary>
ابتدا به‌روزرسانی کنید؛ ممکن است مشکل در نسخه‌ای جدید رفع شده باشد.
برای CLI نصب‌شده با npm:
```bash
rea update
```
برای راه‌اندازی ایجنت با `npx`:
```bash
npx rea-agents@latest setup
```
اگر از ایجنت استفاده می‌کنید، [به‌روزرسانی راه‌اندازی](#بهروزرسانی-rea) را تکمیل و ایجنت را مجدداً اجرا کنید. همان کار را دوباره امتحان کنید. اگر مشکل ادامه داشت، یک [issue ثبت کنید](https://github.com/morluto/rea/issues) و نسخهٔ REA، نوع هدف، مراحل بازتولید و خروجی خطا را ذکر کنید.
</details>
## مستندات
از [راهنماهای عملی وب‌سایت](https://rea.tools/guides/) شروع کنید. برای گزینه‌های دقیق، پیش‌نیازها و قراردادهای خروجی:
- [نصب و راه‌اندازی](docs/installation.md): ثبت ایجنت، پیکربندی ارائه‌دهنده، به‌روزرسانی و حذف نصب.
- [آمادگی و رفع اشکال](docs/installation.md#check-readiness-for-your-task): عیب‌یابی یک ایجنت یا موتور تحلیل.
- [CLI و شواهد](docs/cli.md): دستورات، انتخاب ارائه‌دهنده، snapshotها، ورود/خروج داده و وضعیت‌های خروج.
- [قراردادهای MCP](docs/mcp-contracts.md) و [پرامپت‌های ایجنت](docs/mcp-prompts.md): نتایج ابزارها، نشست‌ها و بررسی‌های هدایت‌شده.
- [فهرست ابزارها](docs/mcp-contracts.md#generated-catalog): فهرست تولیدشده هنگام build از ابزارها، ارائه‌دهندگان و دستورات CLI.
- [نقشهٔ راه](docs/roadmap.md): برنامه‌های آینده و پیگیری قابلیت‌ها.
آسیب‌پذیری‌ها را از طریق [SECURITY.md](SECURITY.md) گزارش کنید.
## مشارکت
از کمک شما به REA استقبال می‌کنیم! برای گزارش خطا یا پیشنهاد قابلیت، یک [issue باز کنید](https://github.com/morluto/rea/issues) یا برای بهبود کد و مستندات [pull request بفرستید](https://github.com/morluto/rea/pulls).
برای راه‌اندازی محیط توسعه و بررسی‌ها، [CONTRIBUTING.md](CONTRIBUTING.md)، برای مراحل اعتبارسنجی [آزمایش‌ها](docs/testing.md) و برای ساختار پروژه [نقشهٔ معماری](docs/architecture.mermaid) را ببینید.
## لینک‌های پروژه
[وب‌سایت](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [امنیت](SECURITY.md)
## تاریخچهٔ ستاره‌ها
🎉 **۳۰٬۰۰۰ ستارهٔ GitHub — سپاسگزاریم!**
از همهٔ کسانی که از REA استفاده می‌کنند، خطاها را گزارش می‌دهند، قابلیت‌های جدید پیشنهاد می‌کنند، buildها را آزمایش می‌کنند و در بهبود پروژه مشارکت دارند سپاسگزاریم.
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date"><picture><source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" /><source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" /><img alt="تاریخچهٔ ستاره‌های GitHub پروژهٔ REA" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" /></picture></a>
## سلب مسئولیت
REA ابزارهایی برای پژوهش، تحلیل و بازسازی قانونی در حوزهٔ مهندسی معکوس فراهم می‌کند. مسئولیت دریافت مجوزهای لازم و رعایت قوانین مربوط بر عهدهٔ شماست. این پروژه استفادهٔ غیرقانونی یا بدون مجوز را تأیید نمی‌کند.
## مجوز
[MIT](LICENSE)
</div>
+286
View File
@@ -0,0 +1,286 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · **Français** · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA : rétro-ingénierie de tout
### Un seul MCP pour la rétro-ingénierie des binaires, des applications et du comportement à l'exécution.
**Repérez une fonctionnalité qui vous plaît. Comprenez son fonctionnement, jusqu'au niveau du binaire.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[Site web](https://rea.tools/) · [Guides](https://rea.tools/guides/) · [Études de cas](https://rea.tools/showcase/)**
[Démarrage rapide](#démarrage-rapide) · [Comment fonctionne REA](#comment-fonctionne-rea) · [Ce que vous pouvez analyser](#ce-que-vous-pouvez-analyser) · [Études de cas](#études-de-cas) · [Questions fréquentes](#questions-fréquentes) · [Documentation](#documentation)
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA lance son pont d'analyse dans Hopper pendant l'inspection d'un binaire natif" width="1200" />
<br />
<table aria-label="REA community">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
<img src="docs/assets/discord.svg" height="42" alt="Discord" /><br />
<strong>Rejoignez la communauté de rétro-ingénierie</strong>
</a><br />
<sub>Discord · Questions et réponses · Partage de projets</sub>
</td>
</tr>
</table>
<br />
</div>
---
Vous avez repéré dans une application une fonctionnalité que vous aimeriez avoir dans votre propre produit ? Demandez à votre agent de l'étudier avec REA. Il peut inspecter l'application sans son code source, expliquer le fonctionnement de la fonctionnalité, présenter les preuves et en construire une version pour votre projet.
REA connecte votre agent à des outils d'inspection de binaires natifs, d'applications JavaScript et Electron, d'assemblys .NET et de sites web. Les mêmes outils sont aussi disponibles depuis votre terminal. L'analyse s'exécute localement, et chaque conclusion s'accompagne des preuves et des limites sur lesquelles elle repose.
Setup enregistre REA auprès de votre agent et installe les instructions de workflow correspondantes. L'analyse native peut utiliser une installation existante de Hopper ou de Ghidra ; Setup peut aussi installer Hopper après votre accord. L'analyse statique JavaScript ne nécessite aucun de ces moteurs.
> **[Visitez le site web de REA](https://rea.tools/)** pour les instructions d'installation, des guides illustrés et des études de cas réelles.
## Démarrage rapide
### Configurer votre agent
Une fois Node.js et npm installés, exécutez :
```bash
npx rea-agents setup
```
Choisissez vos agents, vérifiez les modifications proposées et approuvez-les. Setup ajoute le serveur MCP de REA et les instructions de workflow correspondantes, et sauvegarde la configuration existante. Redémarrez ensuite votre agent.
Setup prend en charge Claude Code, Codex, Cursor, Gemini CLI, Grok Build et [d'autres agents](docs/installation.md#supported-agents). Consultez [Installation et configuration](docs/installation.md) pour la configuration des fournisseurs et l'enregistrement MCP manuel.
### Interroger votre agent
```text
Comprends comment fonctionne la recherche dans l'app Notes, montre-moi les preuves
et construis une fonctionnalité similaire pour mon projet.
```
Remplacez Notes par votre application cible et la fonctionnalité que vous voulez comprendre.
### Utiliser le terminal
Inspectez le répertoire extrait ou l'archive ASAR d'une application JavaScript/Electron :
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
Le résultat comprend les modules, les imports, les frontières Electron et les preuves associées. Remplacez le chemin par votre cible, par exemple `"D:/apps/example"` sous Windows.
Pour installer la commande `rea` pour un usage régulier :
```bash
npm install --global rea-agents
rea --help
```
Pour l'analyse native, configurez d'abord un fournisseur. Consultez le [guide CLI et Evidence](docs/cli.md) pour les commandes natives, le choix du fournisseur, les snapshots et l'utilisation dans des scripts.
### Mettre à jour REA
REA évolue rapidement, et les nouvelles versions apportent fréquemment des corrections de bugs. Maintenez votre installation à jour.
Pour une CLI installée avec npm :
```bash
rea update
```
Pour actualiser les enregistrements de vos agents et le skill, exécutez la commande setup affichée par la mise à jour.
Si vous utilisez `npx`, mettez à jour la configuration de votre agent avec :
```bash
npx rea-agents@latest setup
```
Vérifiez les modifications proposées par Setup et redémarrez votre agent. Pour des commandes CLI ponctuelles, utilisez `npx rea-agents@latest` suivi de la commande.
## Comment fonctionne REA
Votre agent appelle REA via MCP pour inspecter la cible et suivre le code pertinent. REA renvoie ses constatations avec les preuves correspondantes. L'agent s'en sert pour poser des questions complémentaires, expliquer le comportement, ou écrire et tester une implémentation. Les commandes CLI utilisent les mêmes workflows.
![Flux d'investigation de REA : votre agent pose une question sur une cible locale, REA l'inspecte et la trace avec des outils d'analyse, et l'agent utilise le code, les références et les inconnues renvoyés pour expliquer, implémenter et tester.](website/public/assets/figures/rea-investigation-flow.svg)
[Ouvrir la figure en taille réelle](website/public/assets/figures/rea-investigation-flow.svg).
<a id="current-status"></a>
## Ce que vous pouvez analyser
REA nécessite Node.js 22.x (>=22.19), 24.x (>=24.11) ou 26+, ainsi que npm. Les outils supplémentaires et les hôtes pris en charge dépendent de la cible :
| Cible | Ce que REA renvoie | Prérequis et guide |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Binaires natifs | Pseudo-code, assembleur, chaînes, symboles, appels et références | Hopper, Ghidra ou IDA ; [analyse native](https://rea.tools/guides/native/) |
| Structure ELF hors ligne | Sections, segments, symboles et relocations d'origine, et protections statiques candidates | pwntools fourni par l'utilisateur sous Linux x64 ; [diagnostics binaires](docs/binary-diagnostics.md) |
| Bytecode EVM | Sélecteurs de dispatch, offsets en octets, arguments inférés et mutabilité | Fichier local brut ou hexadécimal ; [guide EVM hors ligne](docs/evm-bytecode.md) |
| Crashs Linux enregistrés | Notes brutes, registres et signaux de chaque thread enregistré, et correspondances de mappings facultatives | pwntools fourni par l'utilisateur ; GDB/pwndbg en option ; [crashs enregistrés](docs/recorded-crashes.md) |
| JavaScript / Electron | Modules, imports, source maps, routes, IPC et relations avec les modules natifs | Node.js et npm ; [analyse d'applications](https://rea.tools/guides/javascript/) |
| Sites web | Structure des pages, scripts, observations réseau et captures d'écran demandées | Un navigateur de la famille Chrome ; [analyse du navigateur](https://rea.tools/guides/browser/) |
| Captures réseau enregistrées | Requêtes, réponses, payloads exposés et emplacements dans le code source | HAR ; mitmdump sous Linux pour les captures mitmproxy natives ; [guide des captures](docs/web-network-captures.md) |
| Assemblys .NET | Métadonnées, instructions CIL, dépendances natives déclarées et comparaisons de builds | Inspection statique ; [guide du code managé](docs/managed-code-analysis.md) |
| APK Android | Déclarations du manifeste, classes, méthodes décompilées et références | JADX headless et un JDK complet sous Linux/macOS ; [guide Android](docs/android-analysis.md) |
| Firmware | Régions, résultats d'extraction et passage de relais vers l'analyse native | Binwalk / Unblob sous Linux ; [guide firmware](docs/firmware-analysis.md) |
| Paquets et ressources | Inventaires de fichiers, empreintes, plists, anatomie des bundles Apple et ressources extraites | [Guide artefacts et JavaScript](docs/javascript-artifact-reconstruction.md), [applications Apple](docs/apple-application-analysis.md) |
| Comportement des processus | Sortie du terminal, interactions, code de sortie, observations du système de fichiers et comparaisons | Linux/macOS avec un PTY natif ; [capture de processus](docs/process-capture.md) |
L'inspection statique JavaScript et .NET lit les fichiers fournis sans exécuter l'application. La capture à l'exécution lance la cible choisie ou interagit avec elle avec vos permissions utilisateur ; chaque guide d'exécution décrit ses effets.
<a id="choosing-a-deep-analysis-provider"></a>
Les formats natifs et les hôtes pris en charge varient selon le fournisseur. Consultez [la configuration de Hopper et Ghidra](docs/installation.md#hopper), le [guide IDA](docs/ida-provider.md) et [la prise en charge expérimentale de Ghidra sous Windows](docs/windows-ghidra-p0.md). Ghidra prend aussi en charge [l'analyse DOS 16 bits](docs/ghidra-dos.md). Pour les gros binaires, augmentez son délai de démarrage avec `REA_GHIDRA_STARTUP_TIMEOUT_MS`. Pour le choix du fournisseur, consultez le [guide CLI](docs/cli.md#choose-a-provider). Vérifiez la [disponibilité dans les versions publiées](docs/installation.md#released-package-and-main) pour les fonctionnalités ajoutées depuis la dernière version npm.
## Études de cas
### DX-Ball : reconstruire un calcul de panoramique sonore
Suivez un appel sonore jusqu'à la fonction qui convertit une position en panoramique, inspectez ses instructions et transformez un pseudo-code incomplet en C. La reconstruction réussit 3 205 cas issus du x86 d'origine et reproduit à l'identique les 63 octets de la fonction compilée.
[Lire l'étude de cas](https://rea.tools/showcase/dx-ball/) · [Dépôt de la reconstruction](https://github.com/N0zoM1z0/dx-ball)
### Notion : suivre le pont du presse-papiers d'Electron
Trouvez l'API de presse-papiers du renderer, suivez-la à travers le preload et l'IPC jusqu'au processus principal, puis inspectez le format de presse-papiers enrichi.
[Lire l'étude de cas](https://rea.tools/showcase/notion/)
### TH04 : retrouver un calcul d'anneau de projectiles sous DOS
Inspectez les instructions 16 bits du jeu PC-98 d'origine, retrouvez les calculs d'angles fixes et visés, et comparez le C++ reconstruit avec la sortie du compilateur d'époque.
[Lire l'étude de cas](https://rea.tools/showcase/th04/) · [Dépôt de la reconstruction](https://github.com/N0zoM1z0/th04)
Si vous avez utilisé REA sur un projet intéressant, nous serions ravis de le découvrir. Partagez votre cas dans une [issue](https://github.com/morluto/rea/issues) ou une [pull request](https://github.com/morluto/rea/pulls), en précisant la cible, votre question, la manière dont REA vous a aidé et ce que vous avez découvert.
## Questions fréquentes
<details>
<summary><strong>Quels agents peuvent utiliser REA ?</strong></summary>
Tout agent compatible avec les serveurs MCP locaux. Setup configure les [agents pris en charge](docs/installation.md#supported-agents) ; les autres clients peuvent utiliser [l'enregistrement MCP manuel](docs/installation.md#mcp-registry).
</details>
<details>
<summary><strong>Ai-je besoin de Hopper, Ghidra ou IDA ?</strong></summary>
L'analyse native approfondie utilise l'un d'eux. L'inspection statique JavaScript et .NET fonctionne sans moteur d'analyse native. Setup peut installer Hopper après votre accord ; Ghidra et IDA utilisent vos installations existantes. Consultez [la configuration des fournisseurs](docs/installation.md#hopper).
</details>
<details>
<summary><strong>Dois-je lancer Hopper au préalable ?</strong></summary>
REA lance Hopper lorsqu'une opération en a besoin. Sous macOS, une boîte de dialogue de premier lancement peut vous demander de choisir le mode démo ou d'activer votre licence. Consultez [Démarrage et dépannage de Hopper](docs/installation.md#launcher-paths-and-troubleshooting).
</details>
<details>
<summary><strong>Que fait l'installation du skill depuis skills.sh ?</strong></summary>
Le skill fournit des instructions d'investigation à votre agent. Utilisez `rea setup` pour enregistrer le serveur MCP de REA et installer les instructions correspondantes, puis redémarrez votre agent. Consultez [l'installation du skill seul](docs/installation.md#skill-only-installation).
</details>
<details>
<summary><strong>Quel code REA renvoie-t-il ?</strong></summary>
L'analyse native renvoie du pseudo-code et de l'assembleur. L'analyse JavaScript/Electron récupère les modules et leurs relations. Votre agent s'appuie sur ces résultats pour écrire et tester une implémentation ; les [études de cas](#études-de-cas) en donnent des exemples concrets.
</details>
<details>
<summary><strong>REA envoie-t-il mon application en ligne ?</strong></summary>
REA analyse les cibles localement. Votre agent reçoit les résultats des outils, et son fournisseur de modèle applique sa propre politique de données.
</details>
<details>
<summary><strong>Que faire si je rencontre un bug ?</strong></summary>
Commencez par mettre à jour ; une version récente l'a peut-être déjà corrigé.
Pour une CLI installée avec npm :
```bash
rea update
```
Pour une configuration d'agent via `npx` :
```bash
npx rea-agents@latest setup
```
Si vous utilisez un agent, terminez l'[actualisation de la configuration](#mettre-à-jour-rea) et redémarrez-le. Relancez la même tâche. Si le problème persiste, [ouvrez une issue](https://github.com/morluto/rea/issues) en indiquant votre version de REA, le type de cible, les étapes pour reproduire le problème et la sortie d'erreur.
</details>
## Documentation
Commencez par les [guides pas à pas](https://rea.tools/guides/) du site web. Pour les options exactes, les prérequis et les contrats de résultats :
- [Installation et configuration](docs/installation.md) : enregistrement des agents, configuration des fournisseurs, mises à jour et désinstallation.
- [Vérification et dépannage](docs/installation.md#check-readiness-for-your-task) : diagnostiquer un agent ou un moteur d'analyse précis.
- [CLI et Evidence](docs/cli.md) : commandes, choix du fournisseur, snapshots, import/export et codes de sortie.
- [Contrats MCP](docs/mcp-contracts.md) et [prompts pour agents](docs/mcp-prompts.md) : résultats des outils, sessions et investigations guidées.
- [Catalogue des outils](docs/mcp-contracts.md#generated-catalog) : inventaire des outils, fournisseurs et commandes CLI, généré lors du build.
- [Feuille de route](docs/roadmap.md) : travaux prévus et suivi des capacités.
Signalez les vulnérabilités via [SECURITY.md](SECURITY.md).
## Contribuer
Votre aide sur REA est la bienvenue ! [Ouvrez une issue](https://github.com/morluto/rea/issues) pour signaler un bug ou proposer une fonctionnalité, ou [envoyez une pull request](https://github.com/morluto/rea/pulls) pour améliorer le code ou la documentation.
Consultez [CONTRIBUTING.md](CONTRIBUTING.md) pour l'environnement de développement et les vérifications, la page [tests](docs/testing.md) pour les filières de vérification, et la [carte de l'architecture](docs/architecture.mermaid) pour la structure du projet.
## Liens du projet
[Site web](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [Sécurité](SECURITY.md)
## Historique des étoiles
🎉 **30 000 étoiles GitHub — merci !**
Merci à toutes les personnes qui utilisent REA, signalent des bugs, proposent des fonctionnalités, testent les builds et contribuent des correctifs.
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="Historique des étoiles GitHub de REA" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
</picture>
</a>
## Avertissement
REA fournit des outils destinés à la recherche, à l'analyse et à la reconstruction légales en rétro-ingénierie. Il vous appartient d'obtenir toute autorisation nécessaire et de respecter les lois applicables. Le projet n'encourage aucune utilisation illégale ou non autorisée.
## Licence
[MIT](LICENSE)
+176 -274
View File
@@ -1,27 +1,36 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · **日本語** · [한국어](README_ko.md) · [العربية](README_ar.md)
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · **日本語** · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA:あらゆるものをリバースエンジニアリング
### バイナリ、アプリ、実行時の動作をひとつの MCP でリバースエンジニアリング。
### バイナリ、アプリケーション、実行時の動作を、ひとつの MCP でリバースエンジニアリング。
**気に入った機能を見つけたら、その仕組みをバイナリのレベルまで理解する。**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](#調査ツールカタログ)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[ウェブサイト(英語)](https://morluto.github.io/rea/) · [ガイド](https://morluto.github.io/rea/guides/) · [DX-Ball の事例](https://morluto.github.io/rea/showcase/dx-ball/)**
**[ウェブサイト](https://rea.tools/) · [ガイド](https://rea.tools/guides/) · [事例](https://rea.tools/showcase/)**
[クイックスタート](#クイックスタート) · [現在の対応状況](#現在の対応状況) · [バイナリから動作へ](#バイナリから動作へ) · [調査ツールカタログ](#調査ツールカタログ) · [ロードマップ](#ロードマップ) · [仕組み](#仕組み)
[クイックスタート](#クイックスタート) · [REA の仕組み](#rea-の仕組み) · [分析できる対象](#分析できる対象) · [事例](#事例) · [よくある質問](#よくある質問) · [ドキュメント](#ドキュメント)
<table aria-label="REA community">
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA が Hopper 内で分析ブリッジを起動し、ネイティブバイナリを調べる様子" width="1200" />
<br />
<table aria-label="REA コミュニティ">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
@@ -35,342 +44,231 @@
<br />
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="Hopper 内で解析ブリッジを起動し、ネイティブバイナリを調べる REA" width="1200" />
</div>
---
自分のプロダクトにも欲しい機能を他のアプリで見つけたら、REA を使ってエージェントに調査を依頼できます。エージェントはソースコードがなくてもアプリを調べ、機能の仕組みを説明して根拠を示し、あなたのプロジェクト向けの実装まで進められます。
アプリに、自分の製品にも取り入れたい機能を見つけましたか?エージェントに REA で調査を依頼してください。ソースコードがなくてもアプリを調べ、機能の仕組みを説明し、根拠を示したうえで、あなたのプロジェクト向けに実装できます。
REA は、ネイティブバイナリ、JavaScript/Electron アプリ、.NET アセンブリ、Web サイトを調べるためのツールをエージェントに提供します。同じツールはターミナルからも使えます。解析はローカルで実行され、結果には各結論の根拠と制限が含まれます。
REA はエージェントを分析ツールにつなぎ、ネイティブバイナリ、JavaScript や Electron のアプリ、.NET アセンブリ、ウェブサイトを調べられるようにします。同じツールをターミナルからも使えます。分析はローカルで実行され、結果には各結論を裏付ける根拠と制限事項が含まれます。
セットアップは REA をエージェントに登録し、対応するワークフロー手順もインストールします。ネイティブ解析には既存の Hopper または Ghidra を使えます。承認すれば、セットアップで Hopper をインストールすることもできます。静的な JavaScript 解析にはどちらのエンジンも不要です。
セットアップはエージェントに REA を登録し、対応するワークフローの手順をインストールします。ネイティブ分析には既存の Hopper または Ghidra を使えます。承認すれば、セットアップで Hopper をインストールすることもできます。静的な JavaScript 分析には、どちらのエンジンも必要ありません。
## エージェントに頼むだけ
[セットアップ](#クイックスタート)後にエージェントを再起動し、次のように依頼します:
```text
メモアプリの検索機能を調べて根拠を示し、私のプロジェクト向けに同様の機能を実装してください。
```
「メモアプリ」を調べたいアプリに置き換えてください。まずは概要の調査を依頼することもできます。
## バイナリから動作へ
| 逆コンパイル | 理解 | 再現 |
| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| ネイティブアプリや実行ファイルから、プロシージャ、疑似コード、アセンブリ、文字列、シンボル、セグメント、メタデータを復元します。 | 呼び出し元、呼び出し先、クロスリファレンス、コールグラフをたどり、機能やアルゴリズムの実際の動作を説明できる状態にします。 | エージェントが得た知見を、あなたの技術スタック、画面、要件に合うプロダクト機能へ変えます。 |
REA は結論に至った根拠を示します。元のソースコードを復元できる、あるいはアプリ全体を自動的に複製できるとは主張しません。
## REA を選ぶ理由
| | |
| -------------------------- | -------------------------------------------------------------------------------------------------------- |
| **エージェント向け** | アプリの動作を質問すると、エージェントが推測ではなく実際に調べて答えます。 |
| **CLI と MCP** | 同じリバースエンジニアリング機能を、ターミナルからもエージェントからも使えます。 |
| **ガイド付きセットアップ** | エージェントの設定、既存の解析ツールへの接続、承認したうえでの Hopper のインストールに対応します。 |
| **洞察からコードへ** | 機能を理解したら、同じコーディングセッションで自分のプロダクト向けに実装できます。 |
| **ローカルで解析** | 解析は対応するローカルホストで実行されます。REA がアプリをホスト型の解析サービスへ送ることはありません。 |
| **コンテキストを維持** | 質問のたびに解析をやり直すことなく、複数のアプリを続けて調査できます。 |
> **[REA のウェブサイト](https://rea.tools/)**で、セットアップ手順、図解ガイド、実際の事例を確認できます。
## クイックスタート
### セットアップを実行(推奨)
### エージェントを設定する
REA をエージェントで使えるように設定します:
Node.js と npm がインストールされている環境で実行します。
```bash
npx rea-agents setup
```
セットアップでは、まず REA を使うエージェントを選び(複数選択可)、具体的なパスと変更内容を確認してから承認します。既存の REA 登録は最初から選択されています。新しく検出されたエージェントは選択肢に表示されますが、検出されただけでは選択されません。選択したエージェントには、MCP 接続と REA のガイド付きワークフローが追加されます。Hopper のインストールは別の任意項目で、個別に承認が必要です。既存の Ghidra のインストール先を登録することもできます。
使うエージェントを選び、予定されている変更を確認して承認してください。セットアップは REA の MCP サーバーと対応するワークフローの手順を追加し、既存の設定をバックアップします。完了後にエージェントを再起動してください。
セットアップは変更を適用する前に内容を表示し、既存の設定をバックアップします。要件とセットアップのオプションは[インストールガイド](docs/installation.md)を参照してください。
セットアップは Claude Code、Codex、Cursor、Gemini CLI、Grok Build、および[その他のエージェント](docs/installation.md#supported-agents)に対応しています。プロバイダーの設定と MCP の手動登録については、[インストールとセットアップ](docs/installation.md)を参照してください。
### エージェントで使う(推奨)
### エージェントに依頼する
セットアップ後にエージェントを再起動し、理解したいアプリや機能を[説明してください](#エージェントに頼むだけ)。Hopper はデモモードでも動作します。初回起動の画面が表示されたら、デモを選ぶか既存のライセンスを入力してください。
REA は Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、Command Code、VS Code に対応しています。セットアップでは既存の REA 登録が最初から選択され、それ以外の検出済みエージェントは選択するまで対象になりません。その他のエージェントは[手動の MCP 設定](#他のコーディングエージェントで使う)を使えます。
### AI コーディングアシスタント向けスキル(任意)
AI コーディングアシスタントにスキルを追加すると、より豊富なコンテキストを利用できます:
```bash
npx skills add morluto/rea --skill reverse-engineer-anything
```text
Notes アプリの検索機能の仕組みを調べ、根拠を示して、私のプロジェクトにも同様の機能を実装してください。
```
このスキルは REA の調査ワークフローを提供します。REA をエージェントに接続して解析ツールを設定するには、上記のセットアップを実行してください。セットアップは既定でバージョンの一致したスキルをインストールします。このコマンドはリポジトリ版のスキルをインストールします。
Notes を対象のアプリに置き換え、理解したい機能を指定してください。
### ターミナルで最初の結果を得る
### ターミナルを使う
展開済みの JavaScript/Electron アプリのディレクトリまたは ASAR に対して、次を実行します:
展開済みの JavaScript/Electron アプリのディレクトリ、または ASAR を調べます。
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
パスは調査対象に置き換えてください(Windows では `"D:/apps/example"` など)。MCP の設定、Hopper、Ghidra を必要とせず、アプリを実行することもなく、Evidence、復元したグラフ、制限、未解決事項をその場で返します。ネイティブアプリの場合は、先にエンジンを設定してから、そのアプリのパスを指定して `analyze` を使います。`doctor` は問題を診断したいときに使うもので、解析のたびに実行する必要はありません。
結果にはモジュール、インポート、Electron の境界と、それらの根拠が含まれます。パスは対象に合わせて変更してください。Windows では、たとえば `"D:/apps/example"` を指定します。
### 作業に合わせて準備状況を確認する
オプションなしの `rea doctor` は連携全体の監査です。検出したすべてのエージェント登録、インストール済みのスキル、すべての任意の解析エンジンを確認するため、今の作業には支障がなくても `healthy: false` を報告することがあります。作業に合わせて確認範囲を指定してください:
| 作業 | 準備状況の確認 |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------- |
| 静的な JavaScript/Electron 解析 | 不要です。`analyze-javascript-application` を直接実行します。 |
| 特定の解析エンジンのトラブルシューティング | `rea doctor --provider ghidra --json`(または `hopper`、`ida`) |
| 特定のエージェントの MCP 登録を確認 | `rea doctor --client codex --json`([クライアント ID](docs/installation.md#supported-agents)) |
| インストール済みのスキルを確認 | `rea doctor --skill --json` |
範囲を指定したレポートでは、`healthy` と終了ステータスを決めるのは `scope_checks` だけです。それ以外の項目は `informational_checks` に表示され、今の作業のために修正する必要はありません。たとえば、使っていないエンジンが見つからなくても対処は不要です。
ネイティブ対象に対応するエンジンが複数インストールされている場合、REA は自動ではどれも選びません。対象を開くと `code: "capability_unavailable"` と `details.selection_reason: "ambiguous"` で失敗し、`details.candidate_ids` に候補が示されます。CLI の `--provider` か `open_binary` の `provider_id` で一度選ぶか、常用する設定として `REA_ANALYSIS_PROVIDER` を指定してください。詳しくは[英語版の解説](README.md#choosing-a-deep-analysis-provider)を参照してください。
### rea コマンドをインストール
コマンドラインツールをインストールします:
```bash
curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh | bash
```
Node.js と npm を先にインストールしておいてください。ターミナルで実行すると、インストーラーは `rea` を追加してセットアップを開始します。
npm でインストールしてから、セットアップを実行することもできます:
```bash
npm install --global rea-agents
rea setup
```
### 要件
静的な JavaScript 解析に必要なのは Node.js と npm だけです。ホストと外部ツールの前提条件は使うワークフローによって異なり、ネイティブ解析プロバイダーの対応プラットフォームは各ガイドで説明しています。
- macOS 12 以降
- Ubuntu 24.04+、Fedora 41+、64 ビット Arch Linux、または CachyOS
- Node.js 22.x (>=22.19)、24.x (>=24.11)、または 26+
- npm(特定のバージョンは不要で、REA が npm をインストールすることもありません)
ネイティブバイナリの詳細な解析には Hopper、Ghidra、IDA Pro のいずれかが必要です。Hopper は独自のライセンスを持つ別製品で、デモ版でもベンダーが定める制限の範囲で解析できます。Ghidra と IDA は、利用者が用意したものを使います。
Ghidra は Linux x64 と macOS x64/arm64 に対応しています。Ghidra 12.1.x と、そのインストールが宣言する 64 ビットの完全な JDK(`application.java.min` から `application.java.max` まで)を別途インストールし、REA で使うように設定してください。現行の 12.1 リリースは JDK 21 以降を必要とし、上限はありません。ブリッジは Ghidra 12.1.4 と JDK 21 で検証しています。macOS では、ホストのアーキテクチャに合うネイティブデコンパイラーも必要です。
```bash
export GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra_12.1.4_PUBLIC
export JAVA_HOME=/absolute/path/to/jdk-21 # java と javac が PATH から解決できる場合は省略可
rea doctor --provider ghidra --json
rea setup
```
セットアップはこれらのインストールを確認し、承認後にパスを保存します。Ghidra、Java、Node.js、npm、Homebrew のインストールや更新は行いません。
IDA Pro は、すでに動作している [mrexodia/ida-pro-mcp](https://github.com/mrexodia/ida-pro-mcp) の MCP 登録を再利用します。開いている GUI の対象を読み取り専用で解析するか(`attached`、既定)、データベースのスーパーバイザーで指定したバイナリをヘッドレスで開いて解析します(`headless`)。REA が attached の GUI データベースを保存したり閉じたりすることはありません。最初の実環境での検証は、Windows の GUI と Windows x64 のヘッドレス IDA 9.3 が対象です。設定方法と対応範囲は [IDA プロバイダーガイド](docs/ida-provider.md)を参照してください。
```bash
export REA_IDA_MCP_CONFIG=/absolute/path/to/ida-mcp.json
rea function /absolute/path/to/program main --provider ida --json
```
リポジトリの main と npm 4.1.0 には、ローカル NTFS 上のネイティブ x86-64 PE アプリケーション(マネージドコードと DLL を除く)を対象とする、実験的な Windows x64 Ghidra P0 対応が含まれています。Job Object、プライベート DACL、パスの受け入れ検査の各制御も同梱されています。古い npm パッケージでこの機能を使えるか確認するには[リリース境界](docs/installation.md#released-package-and-main)を、前提条件と検証済みの範囲は [Windows Ghidra P0](docs/windows-ghidra-p0.md) を参照してください。
### トラブルシューティング
`npx -y rea-agents@latest doctor` は、ホスト、依存関係、解析ツール、エージェント設定を変更せずに確認します。構造化された診断結果が必要な場合は `--json` を追加してください。
Linux では実行可能な `/opt/hopper/bin/Hopper` を優先し、利用できない場合は `~/.local/share/rea/hopper/bin/Hopper` を自動的に確認します。それ以外の場所にインストールした場合は `HOPPER_LAUNCHER_PATH` を設定してください。ファイルがあるのに解析エンジンが見つからないと報告される場合は、実際の Hopper のパスに対して `ldd /absolute/path/to/Hopper | grep 'not found'` を実行し、不足しているライブラリを確認してください。詳しくは [Hopper ガイド](docs/installation.md#hopper)を参照してください。
### 更新とアンインストール
- `rea update` は現在の REA インストールを更新します。
- `rea uninstall` は REA が管理するエージェント登録とスキルだけを削除します。Hopper、Node.js、Evidence ファイル、キャプチャ、無関係なスキル、他の MCP サーバーは残ります。
- `rea uninstall --purge-data` は `~/.rea/cache` と `~/.rea/state` も削除します。それらを消したい場合にだけ使ってください。
## 現在の対応状況
リポジトリの現在の機能とプラットフォーム要件は[英語の対応状況ガイド](README.md#current-status)で説明しています。main は [npm リリース](docs/installation.md#released-package-and-main)より先行する場合があります。
- Ghidra は Linux x64、macOS x64/arm64、実験的な Windows x64 P0 境界で 25 種類の読み取り専用操作を提供します。Linux と macOS では、セッション内での関数名とエントリーコメントのアトミックな編集にも対応します。Windows P0 には変更の権限がなく、Ghidra には REA から操作できる GUI 機能はありません。
- IDA アダプターは、ドキュメントに記載された読み取り専用の関数・文字列操作を提供します。
- 静的な Android APK の検査は Linux と macOS に対応し、別途用意したヘッドレス JADX と完全な JDK が必要です。現在のメタデータブリッジは macOS arm64 で検証しています。詳しくは [Android 解析](docs/android-analysis.md)を参照してください。
- ブラウザー、Electron、プロセスの各リクエストは対象、操作、ライフサイクルを直接指定し、ホストで実際に許可されたアクセスの範囲で動作します。REA 側で別途権限を付与する必要はありません。セットアップによる設定の書き込みと Hopper のインストールには、引き続き具体的な計画の承認が必要です。
- `rea capabilities` と `rea providers` はバイナリセッションのプロバイダーと補助操作を説明するもので、ブラウザー、Android、アプリのすべてのワークフローの一覧ではありません。MCP の機能全体は、接続中の MCP ツール一覧と `binary_session` が示すツールの利用可否で確認してください。
## ひとつのプロンプトで調査を完結
```text
メモアプリをリバースエンジニアリングし、オフライン検索機能の仕組みを説明して、
TypeScript と SQLite を使って私のプロジェクト向けに実装してください。
```
| 手順 | エージェントの処理 | REA ツール |
| ---: | ------------------------------ | ---------------------------------------------------------------- |
| 1 | バイナリを開いて識別 | `open_binary`, `binary_overview` |
| 2 | オフライン検索の手掛かりを探す | `search_strings`, `search_procedures`, `list_names` |
| 3 | 手掛かりを実行コードに結び付け | `find_xrefs_to_name`, `xrefs`, `procedure_callers` |
| 4 | 関連する制御フローを復元 | `get_call_graph`, `procedure_callees`, `procedure_info` |
| 5 | 関連する処理を逆コンパイル | `procedure_pseudo_code`, `procedure_assembly`, `batch_decompile` |
| 6 | プロジェクトに機能を実装 | 技術スタック、プロダクト、要件に合わせたコード |
手順 1〜5 のアプリ解析は REA が担当します。手順 6 は、アプリについて得た知見をもとに、エージェントが通常の編集ツールとテストツールで行います。
## エージェントにできること
- 気に入った機能を調査し、自分のプロダクトに合わせた形で実装する。
- ソースコードが手に入らない機能の仕組みを説明する。
- アプリの認証、保存、更新、ネットワーク処理の流れを復元する。
- 文書化されていない形式やインターフェースについて、文書化に必要な構造を復元する。
- 文字列やシンボルから、疑わしい動作を実装しているコードまでたどる。
- 復元した動作を、プロダクト機能、テスト、移行メモ、移植版、相互運用できる代替実装に変える。
- マングルされたシンボルをひとつずつ手作業で読み解かなくても、Swift と Objective-C のメタデータを解析する。
- Hopper に名前、コメント、ブックマークを残し、人とエージェントの解析を互いに活かす。
## 調査ツールカタログ
| ツール分類 | 数 | 用途 |
| -------------------- | --: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ネイティブ検査 | 41 | 関数、疑似コード、アセンブリ、文字列、シンボル、呼び出し、参照、注釈、バイト読み取り、ファイルオフセット |
| 調査ワークフロー | 14 | アプリ概要、関数ドシエ、ネイティブ API とディスパッチ、一括逆コンパイル、機能トレース、コールパス、コールグラフ、Swift と Objective-C の検出 |
| ネイティブバイナリ | 10 | macOS の Mach-O メタデータ、署名、plist、アーキテクチャ、Swift シンボル、LLDB による呼び出し観測;Linux の ELF レイアウト、静的な保護機構の証拠と記録済みクラッシュ |
| 成果物グラフ | 7 | ディレクトリとパッケージの目録、コンパイル済み Interface Builder ファイル、Apple アセットカタログ、Mach-O dylib のロード解決、抽出、オフライン EVM インターフェース推定 |
| マネージド PE/CLI | 7 | .NET の識別情報、メタデータ、CIL 命令、ネイティブ依存関係、再構築結果のインポート、ビルド比較 |
| ファームウェア | 2 | Linux ファームウェア領域の検査と明示的な展開 |
| Android APK | 5 | パッケージと manifest の宣言、クラス検索、メンバー一覧、メソッドの逆コンパイル、静的な被参照 |
| ブラウザー観察 | 12 | ページ構成、ネットワークメタデータ、スクリプト、ソースマップ、WebMCP の検出、スクリーンショット、キャプチャ比較 |
| Electron 解析 | 5 | レンダラーの観察、静的なアプリのマッピング、静的結果と実行時結果の照合 |
| JavaScript 実行時 | 2 | Node/Electron Inspector の対象検出、スクリプトの位置、実行コンテキストのイベント |
| アプリワークフロー | 13 | 取得した Web スクリプトのエクスポート、Android/Apple の目録の投影、層をまたぐ機能トレース、ビルド比較、過去のソースとの対応付け、静的な戻り値形状の比較、再構築の検証 |
| ワークスペースと観察 | 21 | セッション、Evidence バンドル、ナビゲーションの文脈、プロセス・成果物・関数の比較、未解決事項の記録 |
## ロードマップ
[現在の対応状況](#現在の対応状況)は提供済みの機能です。今後の作業は次のとおりです。
- **現在:** ツール、プロバイダー、セットアップの選択肢、バージョンの変更に合わせてドキュメントを正確に保ち、Hopper と Ghidra で検証するネイティブバイナリのアーキテクチャと間接呼び出しの範囲を広げます。
- **次:** 機能トレースに静的抽出と実行時の観察を追加し、難読化された .NET アセンブリの比較を改善し、プロセス、プロトコル、ファイルシステム、再接続、バージョン比較の範囲を広げます。
- **その後:** ブラウザーと Electron のシナリオ操作を増やし、LLDB や Frida などによるネイティブアプリの実行時観察と、他の解析ツールや対象の評価を進めます。
セットアップでは、すでにエージェント連携と Hopper のインストールを選べます。その他の解析ツールのインストールへの対応は今後の作業です。[インストールのロードマップ](docs/roadmap.md)と[解析ツールの評価](docs/provider-evaluation.md)を参照してください。
## 他のコーディングエージェントで使う
セットアップは Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、Command Code、VS Code に対応しています。既存の REA 登録は最初から選択され、それ以外の検出済みエージェントは選択するまで対象になりません。ローカル MCP サーバーに対応するエージェントであれば、次の設定でも接続できます。
<!-- x-release-please-start-version -->
```json
{
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents@6.0.0", "mcp"]
}
}
}
```
<!-- x-release-please-end -->
常用する登録では、パッケージのバージョンを 1 つに固定してください。`rea setup` はその固定バージョンを維持し、同梱のスキルも同時に更新します。
## 仕組み
```mermaid
flowchart LR
Agent["コーディングエージェント"] --> REA["REA<br/>CLI + MCP"]
Terminal["ターミナル"] --> REA
REA --> Hopper["Hopper プロバイダー"]
REA --> Ghidra["Ghidra プロバイダー<br/>インベントリ + 関数解析 + 注釈"]
REA --> Ida["IDA MCP プロバイダー<br/>attached GUI または headless データベース"]
Hopper --> App["あなたのアプリ"]
Ghidra --> App
Ida --> App
```
CLI と MCP サーバーは、同じアプリケーションワークフローと Evidence の契約を使います。ターミナルのコマンドは完了すると自身のブリッジセッションを解放し、エージェントのセッションは調査中の対象と Evidence を保持できます。REA のセッションを閉じても、利用中の Hopper アプリは終了しません。
## CLI
上のエージェントワークフローが、REA を使う最も簡単な方法です。ターミナルからアプリの概要を一度だけ調べる場合は、次を実行します。
```bash
npx -y rea-agents@latest analyze /Applications/Notes.app
```
直接の逆コンパイルやその他のオプションは、`npx -y rea-agents@latest --help` で確認できます。
グローバルな `rea` コマンドとしてもインストールできます。
日常的に使う場合は、`rea` コマンドをインストールできます。
```bash
npm install --global rea-agents
rea --help
rea mcp
```
REA は Mac の `.app` フォルダーを直接開けます。エージェントがアプリを名前で見つけられない場合は、インストール場所を伝えてください。
ネイティブ分析には、先にプロバイダーの設定が必要です。ネイティブ分析のコマンド、プロバイダーの選択、スナップショット、スクリプトでの利用は、[CLI と Evidence のガイド](docs/cli.md)を参照してください。
## Hopper アプリの動作
### REA を更新する
REA は操作に必要になった時点で Hopper を起動します。Hopper のランチャーは内部でアプリをアクティブにするため、REA がバックグラウンドでの起動を要求しても、macOS では Hopper のウィンドウやダイアログが前面に出る場合があります。デモやライセンスの画面では操作が必要になることがあります。
REA は更新が速く、新しいリリースには頻繁にバグ修正が含まれます。インストールしたバージョンを最新に保ってください。
Hopper は解析リクエストを一度に 1 つずつ処理します。待機をキャンセルしても、Hopper 内ですでに実行中の処理は止まりません。処理中かどうかはセッションが報告します。セッションを閉じると REA のブリッジと一時ソケットディレクトリを削除しますが、利用中の Hopper アプリは終了しません。後片付けを確認できなかった場合、`close_binary` は `cleanup_incomplete` と対象のリソースを報告します。
npm でインストールした CLI の場合:
## セキュリティモデル
```bash
rea update
```
解析はローカルで実行されます。REA は Hopper や Ghidra とは認証付きのプライベートなローカルソケットで、IDA とは設定済みのローカル MCP 登録を通じて通信します。Ghidra セッションは隔離された一時プロジェクトを使い、ユーザーの Ghidra プロジェクトを開いたり変更したりしません。
エージェントの登録とスキルを更新するには、更新処理が表示するセットアップコマンドを実行してください。
これはサンドボックスではありません。解析ツールと起動した対象は現在のユーザー権限で動作し、同じ OS ユーザー権限で動く悪意のあるプロセスは防げません。Windows Ghidra P0 では、ネイティブの Job Object、保護されたプライベート DACL、ハンドルベースの受け入れ検査を自動的に使います。脆弱性は [SECURITY.md](SECURITY.md) に記載された非公開の手順で報告してください。
`npx` を使っている場合は、次のコマンドでエージェントの設定を更新します。
## FAQ
```bash
npx rea-agents@latest setup
```
<details><summary><strong>Hopper を先に起動しておく必要はありますか?</strong></summary>
設定の変更を確認し、エージェントを再起動してください。単発の CLI 操作では、`npx rea-agents@latest` に続けてコマンドを指定します。
いいえ。REA は必要になった時点で Hopper を起動します。すでに起動している Hopper でも使えますが、既存の GUI ドキュメントには接続せず、新しい解析ドキュメントを開きます。
## REA の仕組み
エージェントは MCP 経由で REA を呼び出し、対象を調べて関連するコードを追跡します。REA は調査結果と根拠を返します。エージェントはそれを使って追加の質問をしたり、動作を説明したり、実装を書いてテストしたりします。CLI コマンドも同じワークフローを使います。
![REA の調査フロー:エージェントがローカルの対象について質問し、REA が分析ツールで調査・追跡します。エージェントは返されたコード、参照、未解明の点を使い、説明、実装、テストを行います。](website/public/assets/figures/rea-investigation-flow.svg)
[原寸大の図を開く](website/public/assets/figures/rea-investigation-flow.svg)。
<a id="current-status"></a>
## 分析できる対象
REA には Node.js 22.x(>=22.19)、24.x(>=24.11)、または 26+ と npm が必要です。追加のツールや対応ホストは、対象によって異なります。
| 対象 | REA が返すもの | 要件とガイド |
| -------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| ネイティブバイナリ | 疑似コード、アセンブリ、文字列、シンボル、呼び出し、参照 | Hopper、Ghidra、または IDA;[ネイティブ分析](https://rea.tools/guides/native/) |
| オフライン ELF レイアウト | セクション、セグメント、元のシンボル/再配置情報、静的解析によるセキュリティ対策の候補 | Linux x64 で利用者が用意する pwntools;[バイナリ診断](docs/binary-diagnostics.md) |
| EVM バイトコード | ディスパッチセレクター、バイトオフセット、推定された引数と状態変更の可否 | ローカルの生バイト/16 進数の入力ファイル;[オフライン EVM ガイド](docs/evm-bytecode.md) |
| 記録済みの Linux クラッシュ | 生の note レコード、記録された全スレッドのレジスター/シグナル、任意のマッピング候補 | 利用者が用意する pwntools;GDB/pwndbg は任意;[記録済みクラッシュ](docs/recorded-crashes.md) |
| JavaScript / Electron | モジュール、インポート、ソースマップ、ルート、IPC、ネイティブアドオンの関係 | Node.js と npm;[アプリケーション分析](https://rea.tools/guides/javascript/) |
| ウェブサイト | ページ構造、スクリプト、ネットワークの観測結果、依頼されたスクリーンショット | Chrome 系ブラウザー;[ブラウザー分析](https://rea.tools/guides/browser/) |
| 保存済みのネットワークキャプチャ | リクエスト、レスポンス、取得可能なペイロード、ソース内の位置 | HAR;mitmproxy ネイティブ形式のキャプチャには Linux 上の mitmdump;[キャプチャガイド](docs/web-network-captures.md) |
| .NET アセンブリ | メタデータ、CIL 命令、宣言されたネイティブ依存関係、ビルドの比較 | 静的な検査;[マネージドコードのガイド](docs/managed-code-analysis.md) |
| Android APK | マニフェストの宣言、クラス、逆コンパイルされたメソッド、参照 | Linux/macOS 上のヘッドレス JADX と完全な JDK;[Android ガイド](docs/android-analysis.md) |
| ファームウェア | 領域、抽出結果、ネイティブ分析への引き継ぎ | Linux 上の Binwalk / Unblob;[ファームウェアガイド](docs/firmware-analysis.md) |
| パッケージとリソース | ファイル一覧、ダイジェスト、plist、Apple バンドルの構造、抽出されたリソース | [アーティファクトと JavaScript のガイド](docs/javascript-artifact-reconstruction.md)、[Apple アプリケーション](docs/apple-application-analysis.md) |
| プロセスの動作 | ターミナル出力、対話、終了とファイルシステムの観測結果、実行の比較 | ネイティブ PTY を備えた Linux/macOS;[プロセスキャプチャ](docs/process-capture.md) |
静的な JavaScript と .NET の検査は、指定されたファイルを読み取り、アプリケーションを実行しません。実行時キャプチャは、あなたのユーザー権限で対象を実行したり、対象とやり取りしたりします。それぞれの実行時ガイドに、どのような操作が行われるかを記載しています。
<a id="choosing-a-deep-analysis-provider"></a>
対応するネイティブ形式とホストは、プロバイダーによって異なります。[Hopper と Ghidra のセットアップ](docs/installation.md#hopper)、[IDA ガイド](docs/ida-provider.md)、[Windows 向け Ghidra の実験的対応](docs/windows-ghidra-p0.md)を参照してください。Ghidra は [16 ビット DOS 分析](docs/ghidra-dos.md)にも対応しています。プロバイダーの選択は [CLI ガイド](docs/cli.md#choose-a-provider)を参照してください。最新の npm リリース以降に追加された機能については、[リリースでの利用可否](docs/installation.md#released-package-and-main)を確認してください。
## 事例
### DX-Ball:音声のパン計算を再構築する
音声の呼び出しから位置をパンに変換する補助関数をたどり、命令を調べ、不完全な疑似コードを C に書き直します。再構築した実装は、元の x86 に対する 3,205 ケースの検証を通過し、コンパイル後の関数の全 63 バイトを再現しています。
[事例を読む](https://rea.tools/showcase/dx-ball/) ·
[再構築リポジトリ](https://github.com/N0zoM1z0/dx-ball)
### Notion:Electron のクリップボードブリッジを追跡する
レンダラーのクリップボード API を見つけ、preload と IPC を経由してメインプロセスまでたどり、リッチなクリップボード形式を調べます。
[事例を読む](https://rea.tools/showcase/notion/)
### TH04:DOS の弾幕リングの計算を復元する
元の PC-98 ゲームの 16 ビット命令を調べ、固定角度と自機を狙う角度の計算を復元し、再構築した C++ を当時のコンパイラーの出力と比較します。
[事例を読む](https://rea.tools/showcase/th04/) ·
[再構築リポジトリ](https://github.com/N0zoM1z0/th04)
REA で興味深い対象を調べたら、ぜひ共有してください。[issue](https://github.com/morluto/rea/issues) または [pull request](https://github.com/morluto/rea/pulls) に、対象、調べたかったこと、REA がどう役立ったか、何が分かったかを記載してください。
## よくある質問
<details>
<summary><strong>どのエージェントで REA を使えますか?</strong></summary>
ローカル MCP サーバーに対応するエージェントなら使えます。セットアップは[対応エージェント](docs/installation.md#supported-agents)を設定します。その他のクライアントでは、[MCP の手動登録](docs/installation.md#mcp-registry)を利用できます。
</details>
<details><summary><strong>REA に Hopper は含まれていますか?</strong></summary>
<details>
<summary><strong>Hopper、Ghidra、IDA は必要ですか?</strong></summary>
含まれていません。セットアップで Hopper をインストールできますが、Hopper は独自のライセンスを持つ別製品です。REA は、エージェントが Hopper を使えるようにする CLI、MCP サーバー、ワークフローを提供します。
深いネイティブ分析には、そのいずれかを使います。静的な JavaScript と .NET の検査には、ネイティブ分析エンジンは必要ありません。セットアップは承認後に Hopper をインストールできます。Ghidra と IDA は既存のインストールを使います。[プロバイダーのセットアップ](docs/installation.md#hopper)を参照してください。
</details>
<details><summary><strong>REA は Ghidra や Java をインストールしたり同梱したりしますか?</strong></summary>
<details>
<summary><strong>先に Hopper を起動する必要はありますか?</strong></summary>
いいえ。REA は既存の Ghidra のインストールに接続します。[要件](#要件)にある Ghidra と Java のパスを設定してから、セットアップを実行してください。
REA は必要な操作の際に Hopper を起動します。macOS では、初回起動時にデモモードを選ぶか、ライセンスを有効化するよう求められることがあります。[Hopper の起動とトラブルシューティング](docs/installation.md#launcher-paths-and-troubleshooting)を参照してください。
</details>
<details><summary><strong>アプリはアップロードされますか?</strong></summary>
<details>
<summary><strong>skills.sh からスキルをインストールすると何ができますか?</strong></summary>
REA にホスト型の解析サービスはありません。現在のプロバイダーは、成果物の解析と動作のキャプチャをローカルで行います。エージェントやモデルの提供元には独自のデータポリシーがあるため、別途確認してください。
スキルはエージェントに調査の手順を提供します。`rea setup` で REA の MCP サーバーを登録し、対応する手順をインストールしてから、エージェントを再起動してください。[スキルのみのインストール](docs/installation.md#skill-only-installation)を参照してください。
</details>
<details><summary><strong>元のソースコードを復元できますか?</strong></summary>
<details>
<summary><strong>REA はどのようなコードを返しますか?</strong></summary>
元のソースコードを確実に復元できるデコンパイラーはありません。REA はエージェントに疑似コード、アセンブリ、シンボル、文字列、メタデータ、それらの関係を提供し、観察された動作の説明や互換性のある再現に役立てられるようにします。
ネイティブ分析は疑似コードとアセンブリを返します。JavaScript/Electron 分析はモジュールとその関係を復元します。エージェントはその結果を使って実装を書き、テストします。[事例](#事例)に具体的な手順があります。
</details>
## 開発
<details>
<summary><strong>REA はアプリをアップロードしますか?</strong></summary>
開発環境、アーキテクチャ、テスト、リリース手順は [CONTRIBUTING.md](CONTRIBUTING.md) を参照してください。
REA は対象をローカルで分析します。エージェントはツールの結果を受け取り、そのモデルプロバイダーには独自のデータポリシーがあります。
</details>
<details>
<summary><strong>バグに遭遇したらどうすればよいですか?</strong></summary>
まず更新してください。最近のリリースですでに修正されている可能性があります。
npm でインストールした CLI の場合:
```bash
rea update
```
`npx` でエージェントを設定している場合:
```bash
npx rea-agents@latest setup
```
エージェントを使っている場合は、[設定の更新](#rea-を更新する)を完了し、再起動してください。同じタスクをもう一度試し、問題が続く場合は [issue を作成](https://github.com/morluto/rea/issues)して、REA のバージョン、対象の種類、再現手順、エラー出力を記載してください。
</details>
## ドキュメント
まずはウェブサイトの[実践ガイド](https://rea.tools/guides/)を参照してください。正確なオプション、前提条件、結果の契約については、以下を参照してください。
- [インストールとセットアップ](docs/installation.md):エージェントの登録、プロバイダーの設定、更新、アンインストール。
- [準備状況の確認とトラブルシューティング](docs/installation.md#check-readiness-for-your-task):特定のエージェントや分析エンジンを診断。
- [CLI と Evidence](docs/cli.md):コマンド、プロバイダーの選択、スナップショット、インポート/エクスポート、終了ステータス。
- [MCP 契約](docs/mcp-contracts.md)と[エージェント向けプロンプト](docs/mcp-prompts.md):ツールの結果、セッション、ガイド付き調査。
- [ツールカタログ](docs/mcp-contracts.md#generated-catalog):ビルド時に生成されるツール、プロバイダー、CLI コマンドの一覧。
- [ロードマップ](docs/roadmap.md):計画中の作業と機能の進捗。
脆弱性の報告は [SECURITY.md](SECURITY.md) に従ってください。
## 貢献する
REA への貢献を歓迎します![issue を作成](https://github.com/morluto/rea/issues)してバグや機能の提案を報告したり、[pull request を送信](https://github.com/morluto/rea/pulls)してコードやドキュメントを改善したりできます。
開発環境とチェックは [CONTRIBUTING.md](CONTRIBUTING.md)、検証レーンは[テストガイド](docs/testing.md)、プロジェクト構造は[アーキテクチャ図](docs/architecture.mermaid)を参照してください。
## プロジェクトリンク
[ウェブサイト](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [セキュリティ](SECURITY.md)
## スター履歴
🎉 **GitHub スターが 20,000 を突破しました。ありがとうございます!**
🎉 **GitHub スター 30,000 件、ありがとうございます!**
REA を使い、問題を報告し、ビルドをテストし、修正を届けてくださる皆さんに感謝します。
REA の利用、バグ報告、機能の要望、ビルドのテスト、修正への貢献に感謝します。
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
@@ -380,6 +278,10 @@ REA を使い、問題を報告し、ビルドをテストし、修正を届け
</picture>
</a>
## 免責事項
REA は、合法的なリバースエンジニアリングの研究、分析、再構築のためのツールを提供します。必要な許可を得て、適用される法律に従う責任は利用者にあります。このプロジェクトは違法または無許可の利用を支持しません。
## ライセンス
[MIT](LICENSE)
+184 -231
View File
@@ -1,325 +1,274 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · [日本語](README_ja.md) · **한국어** · [العربية](README_ar.md)
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · **한국어** · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA: 무엇이든 리버스 엔지니어링
### 에이전트로 앱의 동작부터 네이티브 바이너리까지 조사하세요.
### 하나의 MCP로 바이너리, 애플리케이션, 런타임 동작을 리버스 엔지니어링합니다.
**마음에 드는 기능을 찾고, 작동 방식을 이해하고, 원하는 방식으로 구현하세요.**
**마음에 드는 기능을 발견했다면, 바이너리 수준까지 작동 원리를 이해하세요.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](#조사-도구-카탈로그)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[웹사이트(영문)](https://morluto.github.io/rea/) · [가이드](https://morluto.github.io/rea/guides/) · [DX-Ball 사례](https://morluto.github.io/rea/showcase/dx-ball/)**
**[웹사이트](https://rea.tools/) · [가이드](https://rea.tools/guides/) · [사례](https://rea.tools/showcase/)**
[빠른 시작](#빠른-시작) · [현재 지원 범위](#현재-지원-범위) · [바이너리에서 동작까지](#바이너리에서-동작까지) · [조사 도구 카탈로그](#조사-도구-카탈로그) · [로드맵](#로드맵) · [작동 방식](#작동-방식)
[빠른 시작](#빠른-시작) · [REA의 작동 방식](#rea의-작동-방식) · [분석할 수 있는 대상](#분석할-수-있는-대상) · [사례](#사례) · [자주 묻는 질문](#자주-묻는-질문) · [문서](#문서)
<table aria-label="REA community">
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA가 Hopper 안에서 분석 브리지를 실행해 네이티브 바이너리를 검사하는 모습" width="1200" />
<br />
<table aria-label="REA 커뮤니티">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
<img src="docs/assets/discord.svg" height="42" alt="Discord" /><br />
<strong>리버스 엔지니어링 커뮤니티에 참여하세요</strong>
</a><br />
<sub>Discord · 질문과 답변 · 작업 공유</sub>
<sub>Discord · 질문과 답변 · 결과 공유</sub>
</td>
</tr>
</table>
<br />
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="Hopper에서 분석 브리지를 시작하고 네이티브 바이너리를 조사하는 REA" width="1200" />
</div>
---
앱에서 마음에 드는 기능을 찾았다면 에이전트에게 REA로 조사해 달라고 요청하세요. 소스 코드가 없어도 앱을 분석하고, 기능의 동작과 근거를 설명한 뒤, 프로젝트에 맞는 기능을 구현할 수 있습니다.
앱에서 내 제품에도 넣고 싶은 기능을 발견했나요? 에이전트에게 REA로 조사해 달라고 요청하세요. 소스 코드가 없어도 앱을 검사하고, 기능의 작동 원리를 설명하고, 근거를 보여 주고, 프로젝트에 맞는 기능을 구현할 수 있습니다.
REA는 네이티브 바이너리, JavaScript/Electron 앱, .NET 어셈블리, 웹사이트 분석 도구를 제공합니다. 에이전트와 터미널에서 같은 도구를 사용할 수 있습니다. 분석은 로컬에서 실행되며 결과에는 근거와 제한 사항이 포함됩니다.
REA는 에이전트를 분석 도구에 연결해 네이티브 바이너리, JavaScript와 Electron 앱, .NET 어셈블리, 웹사이트를 검사할 수 있게 합니다. 터미널에서도 같은 도구를 사용할 수 있습니다. 분석은 로컬에서 실행되며, 결과에는 각 결론의 근거와 한계가 포함됩니다.
Setup은 에이전트를 설정하고 기존 Hopper 또는 Ghidra 설치에 연결합니다. 승인 후 Hopper를 설치할 수도 있습니다.
설정 과정은 에이전트에 REA를 등록하고 해당 버전에 맞는 워크플로 지침을 설치합니다. 네이티브 분석에는 기존 Hopper 또는 Ghidra 설치를 사용할 수 있습니다. 승인하면 설정 과정에서 Hopper를 설치할 수도 있습니다. 정적 JavaScript 분석에는 두 엔진 모두 필요하지 않습니다.
## 에이전트에게 바로 요청하기
[설정](#빠른-시작)을 마친 뒤 에이전트를 다시 시작하고 요청하세요.
```text
메모 앱의 검색 기능이 어떻게 동작하는지 조사하고 근거를 보여 주세요.
그다음 제 프로젝트에 비슷한 기능을 구현해 주세요.
```
메모 앱 대신 조사할 앱을 지정하거나 먼저 개요를 요청할 수 있습니다.
## 바이너리에서 동작까지
| 디컴파일 | 이해 | 재현 |
| ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| 네이티브 앱이나 실행 파일에서 프로시저, 의사 코드, 어셈블리, 문자열, 심볼, 세그먼트, 메타데이터를 복구합니다. | 호출자, 피호출자, 상호 참조, 호출 그래프를 따라 기능이나 알고리즘의 실제 동작을 설명합니다. | 에이전트가 배운 내용을 사용자의 기술 스택, 화면, 요구 사항에 맞는 제품 기능으로 만듭니다. |
REA는 분석을 바이너리 증거에 근거하게 합니다. 원본 소스 코드를 복원하거나 앱 전체를 자동으로 복제한다고 주장하지 않습니다.
## REA를 사용하는 이유
| | |
| ------------------- | --------------------------------------------------------------------------------------------------- |
| **에이전트용** | 앱의 동작에 대해 질문하고 추측 대신 에이전트가 조사하게 합니다. |
| **CLI와 MCP** | 터미널과 에이전트에서 동일한 리버스 엔지니어링 기능을 사용합니다. |
| **안내식 설정** | 에이전트 설정, 기존 분석 도구 연결, 승인 후 Hopper 설치를 지원합니다. |
| **통찰에서 코드로** | 기능을 이해한 뒤 같은 코딩 세션에서 자신의 제품에 맞는 버전을 구현합니다. |
| **로컬 분석** | 분석은 지원되는 로컬 호스트에서 실행되며 REA는 바이너리를 호스팅 분석 서비스에 업로드하지 않습니다. |
| **컨텍스트 유지** | 질문마다 분석을 처음부터 시작하지 않고 여러 앱을 연속으로 조사합니다. |
> **[REA 웹사이트](https://rea.tools/)**에서 설정 방법, 그림으로 설명한 가이드, 실제 사례를 확인하세요.
## 빠른 시작
### 설정 실행하기(권장)
### 에이전트 설정
REA를 에이전트에 연결하세요.
Node.js와 npm이 설치된 환경에서 다음 명령을 실행하세요.
```bash
npx rea-agents setup
```
Setup은 먼저 연결할 에이전트를 여러 개 선택하도록 안내합니다. 기존 REA 등록은 기본 선택되며, 감지만 된 클라이언트는 자동 선택되지 않습니다. 아직 설정되지 않은 클라이언트도 직접 선택할 수 있습니다. 정확한 경로와 변경 사항을 검토한 뒤 승인하세요. 선택한 에이전트에는 기본적으로 REA 워크플로가 설치됩니다. Hopper는 별도의 선택 사항이며 따로 승인해야 합니다. 기존 Ghidra 경로도 등록할 수 있습니다.
에이전트를 선택하고, 예정된 변경 사항을 검토한 뒤 승인하세요. 설정 과정은 기존 설정을 백업하고 REA의 MCP 서버와 버전에 맞는 워크플로 지침을 추가합니다. 완료 후 에이전트를 다시 시작하세요.
변경 전에 계획을 표시하고 기존 설정을 백업합니다. 요구 사항과 추가 옵션은 [설치 안내](docs/installation.md)를 참고하세요.
설정은 Claude Code, Codex, Cursor, Gemini CLI, Grok Build 및 [다른 에이전트](docs/installation.md#supported-agents)를 지원합니다. 제공자 설정과 수동 MCP 등록은 [설치 및 설정](docs/installation.md)을 참고하세요.
### 에이전트에서 사용하기
### 에이전트에게 요청
설정 후 에이전트를 다시 시작하고 조사할 앱이나 기능을 설명하세요. REA는 Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity, GitHub Copilot CLI, Command Code, VS Code를 지원합니다. 기존 REA 등록은 기본 선택되고, 그 밖의 감지된 클라이언트는 직접 선택해야 합니다. 다른 에이전트는 아래 MCP 설정을 사용할 수 있습니다.
Hopper는 데모 모드로 사용할 수 있습니다. 첫 실행 안내가 나오면 데모를 선택하거나 기존 라이선스를 입력하세요.
### AI 코딩 어시스턴트용 스킬(선택)
AI 코딩 어시스턴트에 스킬을 추가하면 더 풍부한 컨텍스트를 얻을 수 있습니다.
```bash
npx skills add morluto/rea --skill reverse-engineer-anything
```text
Notes 앱의 검색 기능이 어떻게 작동하는지 조사하고, 근거를 보여 준 다음, 내 프로젝트에 비슷한 기능을 구현해 줘.
```
이 스킬은 REA의 조사 워크플로를 제공합니다. 위 Setup을 실행해 REA를 에이전트에 연결하고 분석 도구를 설정하세요. Setup은 기본적으로 버전에 맞는 스킬을 설치합니다. 이 명령은 저장소 버전의 스킬을 설치합니다.
Notes를 대상 앱으로 바꾸고 이해하고 싶은 기능을 지정하세요.
압축을 해제한 JavaScript/Electron 앱 트리나 ASAR라면 MCP 설정이나 네이티브 엔진 없이 바로 분석할 수 있습니다.
### 터미널 사용
압축을 푼 JavaScript/Electron 앱 디렉터리 또는 ASAR를 검사합니다.
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
경로를 조사 대상의 경로로 바꾸세요(Windows에서는 `"D:/apps/example"` 같은 경로). 이 정적 워크플로에서는 이런 경로도 지원됩니다. 네이티브 분석에는 전용 엔진 설정이 필요합니다.
결과에는 모듈, 임포트, Electron 경계와 관련 근거가 포함됩니다. 경로를 분석할 대상으로 바꾸세요. Windows에서는 `"D:/apps/example"` 같은 경로를 사용할 수 있습니다.
### rea 명령 설치하기
명령줄 도구를 설치하세요.
```bash
curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh | bash
```
Node.js와 npm이 먼저 설치되어 있어야 합니다. 터미널에서 실행하면 설치 프로그램이 `rea`를 설치하고 설정을 시작합니다.
npm으로 설치한 뒤 설정을 실행할 수도 있습니다.
```bash
npm install --global rea-agents
rea setup
```
### 요구 사항
- macOS 12 이상
- Ubuntu 24.04+, Fedora 41+, 64비트 Arch Linux 또는 CachyOS
- Node.js 22.x (>=22.19), 24.x (>=24.11) 또는 26+와 npm
네이티브 바이너리 분석에는 Hopper 또는 Ghidra가 필요합니다. Hopper는 별도 소프트웨어입니다. 데모에는 공급업체의 제한이 있지만 유료 라이선스가 필수는 아닙니다.
Ghidra는 Linux x64와 macOS x64/arm64를 지원합니다. Ghidra 12.1.x와 그 설치본이 선언한 완전한 64비트 JDK(`application.java.min`부터 `application.java.max`까지)를 별도로 설치한 뒤 REA에서 사용하도록 설정하세요. 현재 12.1 릴리스는 JDK 21 이상을 요구하고 상한은 없습니다. 브리지는 Ghidra 12.1.4와 JDK 21에서 검증됩니다. macOS에서는 호스트 아키텍처에 맞는 네이티브 디컴파일러도 필요합니다.
Setup은 설치를 확인하고 경로를 저장할 수 있습니다. Ghidra, Java, Node.js, npm, Homebrew를 설치하거나 업데이트하지 않습니다.
저장소 main과 npm 4.1.0에는 로컬 NTFS상의 네이티브 x86-64 PE 애플리케이션(비관리, 비 DLL 대상)을 위한 실험적인 Windows x64 Ghidra P0 지원이 포함되어 있습니다. Job Object, 비공개 DACL, 경로 승인 제어가 함께 제공됩니다. 이전 npm 패키지에 이 기능이 있을 것으로 기대하기 전에 [릴리스 경계](docs/installation.md#released-package-and-main)를 확인하세요. 전제 조건과 검증 범위는 [Windows Ghidra P0](docs/windows-ghidra-p0.md)를 참고하세요.
### 문제 해결
`npx -y rea-agents@latest doctor`는 호스트, 의존성, 분석 도구, 에이전트 설정을 변경 없이 확인합니다. 구조화된 진단에는 `--json`을 추가하세요.
Linux에서는 실행 가능한 `/opt/hopper/bin/Hopper`를 우선 사용하고, 사용할 수 없으면 `~/.local/share/rea/hopper/bin/Hopper`를 자동으로 확인합니다. 다른 위치에는 `HOPPER_LAUNCHER_PATH`를 설정하세요. 파일이 있는데도 분석 엔진이 없다고 하면 실제 Hopper 경로에 대해 `ldd /absolute/path/to/Hopper | grep 'not found'`를 실행해 누락된 라이브러리를 확인하세요. 자세한 내용은 [Hopper 안내](docs/installation.md#hopper)를 참고하세요.
### 업데이트와 제거
- `rea update`는 현재 REA 설치를 업데이트합니다.
- `rea uninstall`은 REA가 관리하는 에이전트 등록과 워크플로 파일을 제거합니다. Hopper는 유지합니다.
- `rea uninstall --purge-data`는 REA 캐시와 상태도 삭제합니다. 해당 데이터를 지우려는 경우에만 사용하세요.
## 현재 지원 범위
저장소의 현재 기능과 플랫폼 요구 사항은 [영문 지원 가이드](README.md#current-status)에 설명되어 있습니다. main은 [npm 릴리스](docs/installation.md#released-package-and-main)보다 앞선 내용이 포함될 수 있습니다.
- Ghidra는 Linux x64, macOS x64/arm64, 실험적인 Windows x64 P0 경계에서 25개의 읽기 전용 작업을 제공합니다. Linux/macOS에서는 원자적 세션 함수 주석도 추가로 지원합니다. Windows P0는 읽기 전용이며 Ghidra에는 GUI 제어가 없습니다.
- 정적 Android 검사는 별도로 준비한 JADX/Java가 필요합니다. 실제 제공자 검증은 Linux를 대상으로 합니다. [Android 분석](docs/android-analysis.md)을 참고하세요.
- 브라우저, Electron, 프로세스 요청은 대상·동작·수명 주기를 직접 지정하며 호스트의 실제 접근에 의존합니다. 별도의 REA 권한 승인은 필요 없습니다. Setup의 설정 쓰기와 Hopper 설치는 여전히 정확한 계획에 대한 승인이 필요합니다.
- `rea capabilities`는 모든 앱/런타임 도구의 목록이 아니라 바이너리 세션 작업을 설명합니다. 연결된 MCP 도구 목록과 관련 가이드를 참고하세요.
## 하나의 프롬프트로 전체 조사
```text
메모 앱을 리버스 엔지니어링해 오프라인 검색 기능의 작동 방식을 설명한 다음,
TypeScript와 SQLite를 사용해 제 프로젝트에 맞는 버전을 구현해 주세요.
```
| 단계 | 에이전트 작업 | REA 도구 |
| ---: | ----------------------- | ---------------------------------------------------------------- |
| 1 | 바이너리 열기 및 식별 | `open_binary`, `binary_overview` |
| 2 | 오프라인 검색 단서 찾기 | `search_strings`, `search_procedures`, `list_names` |
| 3 | 단서를 실행 코드에 연결 | `find_xrefs_to_name`, `xrefs`, `procedure_callers` |
| 4 | 제어 흐름 재구성 | `get_call_graph`, `procedure_callees`, `procedure_info` |
| 5 | 관련 루틴 디컴파일 | `procedure_pseudo_code`, `procedure_assembly`, `batch_decompile` |
| 6 | 프로젝트에 기능 구현 | 기술 스택, 제품, 요구 사항에 맞는 코드 |
REA는 1~5단계의 바이너리 분석을 처리합니다. 6단계는 에이전트가 일반 파일 편집 및 테스트 도구로 수행합니다.
## 에이전트가 할 수 있는 일
- 소스 코드가 없는 기능의 동작을 설명합니다.
- 앱의 인증, 저장소, 업데이트 또는 네트워크 흐름을 재구성합니다.
- 문서화되지 않은 형식이나 인터페이스의 구조를 복구합니다.
- 문자열이나 심볼에서 의심스러운 동작을 구현한 코드까지 추적합니다.
- 한 세션에서 두 앱 버전을 전환하며 구현 경로를 비교합니다.
- 마음에 드는 기능을 조사하고 자신의 제품에 맞는 버전으로 구현합니다.
- 복구한 동작을 제품 기능, 테스트, 마이그레이션 문서, 포팅 또는 상호 운용 가능한 대체품으로 변환합니다.
- Swift 및 Objective-C 메타데이터를 분석합니다.
- Hopper에 이름, 주석, 북마크를 남겨 사람과 에이전트의 분석을 연결합니다.
## 조사 도구 카탈로그
| 도구 분류 | 수 | 용도 |
| ----------------- | --: | ---------------------------------------------------------------------------------------------------------------------------------------- |
| 네이티브 검사 | 41 | 함수, 의사 코드, 어셈블리, 문자열, 심볼, 호출, 참조, 주석, 바이트 읽기, 파일 오프셋 |
| 조사 워크플로 | 14 | 앱 개요, 함수 기록서, 네이티브 API와 디스패치, 일괄 디컴파일, 기능 추적, 호출 경로, 호출 그래프, Swift와 Objective-C 탐색 |
| 네이티브 바이너리 | 10 | macOS의 Mach-O 메타데이터, 서명, plist, 아키텍처, Swift 심볼과 LLDB 호출 관찰; Linux의 ELF 레이아웃, 정적 보호 증거와 기록된 크래시 |
| 아티팩트 그래프 | 7 | 디렉터리와 패키지 목록, 컴파일된 Interface Builder 파일, Apple 애셋 카탈로그, Mach-O dylib 로드 해석, 추출, 오프라인 EVM 인터페이스 추론 |
| 관리 PE/CLI | 7 | .NET 식별, 메타데이터, CIL 명령어, 네이티브 종속성, 재구성 가져오기, 빌드 비교 |
| 펌웨어 | 2 | Linux 펌웨어 영역 검사 및 명시적 추출 |
| Android APK | 5 | 패키지와 manifest 선언, 클래스 검색, 멤버 목록, 메서드 디컴파일, 정적 참조 |
| 브라우저 관찰 | 12 | 페이지 구조, 네트워크 메타데이터, 스크립트, 소스 맵, WebMCP 탐색, 스크린샷, 캡처 비교 |
| Electron 분석 | 5 | 렌더러 관찰, 정적 앱 매핑, 정적·런타임 결과 연결 |
| JavaScript 런타임 | 2 | Node/Electron Inspector 대상 탐색, 스크립트 위치, 실행 컨텍스트 이벤트 |
| 앱 워크플로 | 13 | 캡처한 웹 스크립트 내보내기, 계층 간 기능 추적, 빌드 비교, 히스토리 소스 매핑, 정적 반환 구조 비교, 재구현 검증 |
| 작업 공간과 관찰 | 21 | 세션, 근거 번들, 탐색 컨텍스트, 프로세스·아티팩트·함수 비교, 미해결 항목 기록 |
## 로드맵
다음 작업은 네이티브 대상 검증 확대, JavaScript와 .NET의 버전 비교, 런타임 관찰과 재구현 검증입니다. Setup은 에이전트 연결과 Hopper 설치 선택을 이미 지원합니다. 다른 분석 도구 설치는 향후 작업입니다. [설치 로드맵](docs/roadmap.md)과 [분석 도구 평가](docs/provider-evaluation.md)를 참고하세요.
## 다른 코딩 에이전트에서 사용하기
Setup은 Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity, GitHub Copilot CLI, Command Code, VS Code를 지원합니다. 기존 REA 등록은 기본 선택되고, 그 밖의 감지된 클라이언트는 직접 선택해야 합니다. 로컬 MCP 서버를 지원하는 에이전트는 다음 설정으로 연결할 수 있습니다.
<!-- x-release-please-start-version -->
```json
{
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents@6.0.0", "mcp"]
}
}
}
```
<!-- x-release-please-end -->
## 작동 방식
```mermaid
flowchart LR
Agent["코딩 에이전트"] --> REA["REA<br/>CLI + MCP"]
Terminal["터미널"] --> REA
REA --> Hopper["Hopper 분석 작업"]
Hopper --> App["사용자의 앱"]
REA --> Ghidra["Ghidra 읽기 전용 분석<br/>인벤토리 + 함수 분석"]
Ghidra --> App
```
CLI와 MCP 서버는 같은 분석 워크플로를 사용합니다. 터미널 명령은 완료 후 자신의 브리지 세션을 해제하고, 에이전트 세션은 조사 중 연결을 유지할 수 있습니다. REA 세션을 닫아도 사용자가 이용 중인 Hopper 앱은 종료하지 않습니다.
## CLI
위의 에이전트 워크플로가 REA를 사용하는 가장 쉬운 방법입니다. 터미널에서 앱을 한 번 빠르게 살펴보려면 다음을 실행하세요.
```bash
npx -y rea-agents@latest analyze /Applications/Notes.app
```
직접 디컴파일하는 방법과 기타 옵션은 `npx -y rea-agents@latest --help`에서 확인할 수 있습니다.
전역 `rea` 명령으로 설치할 수도 있습니다.
자주 사용할 경우 `rea` 명령을 설치하세요.
```bash
npm install --global rea-agents
rea --help
rea mcp
```
REA는 macOS의 `.app` 폴더를 직접 열 수 있습니다. 에이전트가 앱을 찾지 못하면 설치 위치를 알려 주세요.
네이티브 분석에는 먼저 제공자를 설정해야 합니다. 네이티브 명령, 제공자 선택, 스냅샷, 스크립트 사용은 [CLI 및 Evidence 가이드](docs/cli.md)를 참고하세요.
## Hopper 앱 동작
### REA 업데이트
REA는 필요할 때 Hopper를 시작합니다. Hopper 런처는 내부적으로 앱을 활성화하므로 대상을 열 때 Hopper가 다른 창 앞으로 나타날 수 있습니다. REA는 백그라운드 시작을 요청하지만 항상 뒤에 머무르는 것을 보장할 수 없습니다.
REA는 빠르게 바뀌며 새 릴리스에는 버그 수정이 자주 포함됩니다. 설치된 버전을 최신으로 유지하세요.
명시적인 형식과 아키텍처 인자로 일반적인 FAT/ARM 선택 대화상자를 피하지만, 다른 Hopper 또는 macOS 대화상자에는 사람이 응답해야 할 수 있습니다. 세션 종료 시 브리지와 소켓 디렉터리를 제거하지만 사용자가 작업 중인 Hopper 앱은 종료하지 않습니다.
npm으로 설치한 CLI에서는 다음 명령을 사용합니다.
## 보안 모델
```bash
rea update
```
각 세션은 무작위 capability token과 현재 사용자 전용 Unix 소켓을 사용합니다. Ghidra 세션은 격리된 임시 프로젝트도 사용하며 사용자 소유 Ghidra 프로젝트를 열거나 수정하지 않습니다. 이는 샌드박스가 아니며 같은 운영 체제 사용자로 실행되는 악성 프로세스를 방어하지 않습니다. 신뢰할 수 없는 바이너리를 열면 선택한 로컬 공급자가 현재 사용자 권한으로 분석합니다. 취약점은 [SECURITY.md](SECURITY.md)의 비공개 절차로 신고하세요.
에이전트 등록과 스킬을 갱신하려면 업데이트 과정에서 출력된 설정 명령을 실행하세요.
## FAQ
`npx`를 사용한다면 다음 명령으로 에이전트 설정을 갱신하세요.
<details><summary><strong>Hopper를 미리 실행해야 하나요?</strong></summary>
```bash
npx rea-agents@latest setup
```
아니요. REA가 필요할 때 시작합니다. Hopper가 실행 중이어도 사용할 수 있지만 기존 GUI 문서에 연결하는 대신 새 분석 문서를 엽니다.
설정 변경 사항을 검토하고 에이전트를 다시 시작하세요. 일회성 CLI 작업에는 `npx rea-agents@latest` 뒤에 필요한 명령을 붙여 실행하세요.
## REA의 작동 방식
에이전트는 MCP를 통해 REA를 호출하여 대상을 검사하고 관련 코드를 추적합니다. REA는 조사 결과와 근거를 반환합니다. 에이전트는 이를 바탕으로 추가 질문을 하고, 동작을 설명하거나, 구현을 작성하고 테스트합니다. CLI 명령도 같은 워크플로를 사용합니다.
![REA 조사 흐름: 에이전트가 로컬 대상에 대해 질문하면 REA가 분석 도구로 대상을 검사하고 추적합니다. 에이전트는 반환된 코드, 참조, 확인되지 않은 사항을 이용해 설명하고 구현하고 테스트합니다.](website/public/assets/figures/rea-investigation-flow.svg)
[원본 크기의 그림 열기](website/public/assets/figures/rea-investigation-flow.svg).
<a id="current-status"></a>
## 분석할 수 있는 대상
REA에는 Node.js 22.x(>=22.19), 24.x(>=24.11) 또는 26+와 npm이 필요합니다. 추가 도구와 호스트 지원은 대상에 따라 다릅니다.
| 대상 | REA가 반환하는 내용 | 요구 사항 및 가이드 |
| --------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| 네이티브 바이너리 | 의사 코드, 어셈블리, 문자열, 심볼, 호출, 참조 | Hopper, Ghidra 또는 IDA; [네이티브 분석](https://rea.tools/guides/native/) |
| 오프라인 ELF 레이아웃 | 섹션, 세그먼트, 원본 심볼/재배치 정보, 정적 분석으로 얻은 보안 완화 기법 후보 | Linux x64에서 호출자가 제공하는 pwntools; [바이너리 진단](docs/binary-diagnostics.md) |
| EVM 바이트코드 | 디스패치 선택자, 바이트 오프셋, 추론한 인자와 상태 변경 가능 여부 | 로컬 원시 바이트/16진수 입력 파일; [오프라인 EVM 가이드](docs/evm-bytecode.md) |
| 기록된 Linux 크래시 | 원시 note 레코드, 기록된 모든 스레드의 레지스터/시그널, 선택적 매핑 후보 | 호출자가 제공하는 pwntools; GDB/pwndbg는 선택 사항; [기록된 크래시](docs/recorded-crashes.md) |
| JavaScript / Electron | 모듈, 임포트, 소스 맵, 라우트, IPC, 네이티브 애드온 관계 | Node.js와 npm; [애플리케이션 분석](https://rea.tools/guides/javascript/) |
| 웹사이트 | 페이지 구조, 스크립트, 네트워크 관찰 결과, 요청한 스크린샷 | Chrome 계열 브라우저; [브라우저 분석](https://rea.tools/guides/browser/) |
| 저장된 네트워크 캡처 | 요청, 응답, 접근 가능한 페이로드, 소스 위치 | HAR; mitmproxy 네이티브 캡처에는 Linux의 mitmdump; [캡처 가이드](docs/web-network-captures.md) |
| .NET 어셈블리 | 메타데이터, CIL 명령, 선언된 네이티브 의존성, 빌드 비교 | 정적 검사; [관리 코드 가이드](docs/managed-code-analysis.md) |
| Android APK | 매니페스트 선언, 클래스, 디컴파일된 메서드, 참조 | Linux/macOS의 헤드리스 JADX와 전체 JDK; [Android 가이드](docs/android-analysis.md) |
| 펌웨어 | 영역, 추출 결과, 네이티브 분석으로 전달할 정보 | Linux의 Binwalk / Unblob; [펌웨어 가이드](docs/firmware-analysis.md) |
| 패키지 및 리소스 | 파일 목록, 다이제스트, plist, Apple 번들 구조, 추출한 리소스 | [아티팩트 및 JavaScript 가이드](docs/javascript-artifact-reconstruction.md), [Apple 애플리케이션](docs/apple-application-analysis.md) |
| 프로세스 동작 | 터미널 출력, 상호작용, 종료 및 파일 시스템 관찰 결과, 실행 비교 | 네이티브 PTY를 지원하는 Linux/macOS; [프로세스 캡처](docs/process-capture.md) |
정적 JavaScript 및 .NET 검사는 제공된 파일을 읽으며 애플리케이션을 실행하지 않습니다. 런타임 캡처는 사용자의 권한으로 선택한 대상을 실행하거나 대상과 상호작용합니다. 각 런타임 가이드에서 그 영향을 설명합니다.
<a id="choosing-a-deep-analysis-provider"></a>
지원하는 네이티브 형식과 호스트는 제공자마다 다릅니다. [Hopper 및 Ghidra 설정](docs/installation.md#hopper), [IDA 가이드](docs/ida-provider.md), [실험적 Windows Ghidra 지원](docs/windows-ghidra-p0.md)을 참고하세요. Ghidra는 [16비트 DOS 분석](docs/ghidra-dos.md)도 지원합니다. 제공자 선택은 [CLI 가이드](docs/cli.md#choose-a-provider)를 참고하세요. 최신 npm 릴리스 이후 추가된 기능은 [릴리스에서의 사용 가능 여부](docs/installation.md#released-package-and-main)를 확인하세요.
## 사례
### DX-Ball: 사운드 패닝 계산 재구현
사운드 호출에서 위치를 패닝 값으로 변환하는 보조 함수까지 추적하고, 명령을 검사하여 불완전한 의사 코드를 C로 옮깁니다. 재구현한 코드는 원본 x86에 대한 3,205개 테스트 케이스를 통과했으며, 컴파일된 함수의 63바이트 전체를 재현합니다.
[사례 읽기](https://rea.tools/showcase/dx-ball/) ·
[재구현 저장소](https://github.com/N0zoM1z0/dx-ball)
### Notion: Electron 클립보드 브리지 추적
렌더러의 클립보드 API를 찾고, preload와 IPC를 거쳐 메인 프로세스까지 추적한 뒤, 서식 있는 클립보드 형식을 검사합니다.
[사례 읽기](https://rea.tools/showcase/notion/)
### TH04: DOS 원형 탄막 계산 복원
원본 PC-98 게임의 16비트 명령을 검사하고 고정 각도와 조준 각도의 계산을 복원합니다. 재구현한 C++를 당시 컴파일러의 출력과 비교합니다.
[사례 읽기](https://rea.tools/showcase/th04/) ·
[재구현 저장소](https://github.com/N0zoM1z0/th04)
REA로 흥미로운 대상을 분석했다면 알려 주세요. [issue](https://github.com/morluto/rea/issues) 또는 [pull request](https://github.com/morluto/rea/pulls)에 대상, 조사하려던 질문, REA가 어떻게 도움이 되었는지, 무엇을 알아냈는지를 담아 공유하세요.
## 자주 묻는 질문
<details>
<summary><strong>어떤 에이전트에서 REA를 사용할 수 있나요?</strong></summary>
로컬 MCP 서버를 지원하는 에이전트라면 사용할 수 있습니다. 설정 과정은 [지원하는 에이전트](docs/installation.md#supported-agents)를 구성합니다. 다른 클라이언트는 [수동 MCP 등록](docs/installation.md#mcp-registry)을 이용할 수 있습니다.
</details>
<details><summary><strong>REA에 Hopper가 포함되나요?</strong></summary>
<details>
<summary><strong>Hopper, Ghidra 또는 IDA가 필요한가요?</strong></summary>
아니요. Setup이 Hopper를 설치할 수 있지만 Hopper는 자체 라이선스가 필요한 별도 소프트웨어입니다. REA는 CLI, MCP 서버, 에이전트용 워크플로를 제공합니다.
심층 네이티브 분석에는 이 중 하나를 사용합니다. 정적 JavaScript 및 .NET 검사에는 네이티브 분석 엔진이 필요하지 않습니다. 설정 과정에서 승인 후 Hopper를 설치할 수 있으며, Ghidra와 IDA는 기존 설치를 사용합니다. [제공자 설정](docs/installation.md#hopper)을 참고하세요.
</details>
<details><summary><strong>바이너리가 업로드되나요?</strong></summary>
<details>
<summary><strong>Hopper를 먼저 실행해야 하나요?</strong></summary>
REA에는 호스팅 분석 서비스가 없습니다. 로컬 Unix 소켓을 통해 Hopper를 조작합니다. 에이전트나 모델 공급자의 데이터 정책은 별도로 확인하세요.
REA는 작업에 필요할 때 Hopper를 실행합니다. macOS에서는 처음 실행할 때 데모 모드를 선택하거나 라이선스를 활성화하라는 대화 상자가 나타날 수 있습니다. [Hopper 실행 및 문제 해결](docs/installation.md#launcher-paths-and-troubleshooting)을 참고하세요.
</details>
<details><summary><strong>원본 소스 코드를 복구할 수 있나요?</strong></summary>
<details>
<summary><strong>skills.sh에서 설치하는 스킬은 어떤 역할을 하나요?</strong></summary>
보장할 수 없습니다. REA는 의사 코드, 어셈블리, 심볼, 문자열, 메타데이터 및 관계를 제공하여 에이전트가 관찰된 동작을 설명하거나 호환되게 재현하도록 돕습니다.
스킬은 에이전트에게 조사 지침을 제공합니다. `rea setup`으로 REA의 MCP 서버를 등록하고 버전에 맞는 지침을 설치한 뒤 에이전트를 다시 시작하세요. [스킬만 설치하기](docs/installation.md#skill-only-installation)를 참고하세요.
</details>
## 개발
<details>
<summary><strong>REA는 어떤 코드를 반환하나요?</strong></summary>
개발 환경, 아키텍처, 테스트, 릴리스 지침은 [CONTRIBUTING.md](CONTRIBUTING.md)를 참고하세요.
네이티브 분석은 의사 코드와 어셈블리를 반환합니다. JavaScript/Electron 분석은 모듈과 그 관계를 복원합니다. 에이전트는 이 결과를 사용해 구현을 작성하고 테스트합니다. [사례](#사례)에서 구체적인 예를 볼 수 있습니다.
</details>
<details>
<summary><strong>REA가 내 앱을 업로드하나요?</strong></summary>
REA는 대상을 로컬에서 분석합니다. 에이전트는 도구 결과를 받으며, 에이전트의 모델 제공자에는 별도의 데이터 정책이 있습니다.
</details>
<details>
<summary><strong>버그가 발생하면 어떻게 해야 하나요?</strong></summary>
먼저 업데이트하세요. 최근 릴리스에서 이미 수정되었을 수 있습니다.
npm으로 설치한 CLI에서는 다음 명령을 사용합니다.
```bash
rea update
```
`npx`로 에이전트를 설정했다면 다음 명령을 사용합니다.
```bash
npx rea-agents@latest setup
```
에이전트를 사용 중이라면 [설정 갱신](#rea-업데이트)을 완료하고 다시 시작하세요. 같은 작업을 다시 시도하세요. 문제가 계속되면 REA 버전, 대상 유형, 재현 단계, 오류 출력을 포함해 [issue를 등록](https://github.com/morluto/rea/issues)하세요.
</details>
## 문서
웹사이트의 [실습 가이드](https://rea.tools/guides/)부터 읽어 보세요. 정확한 옵션, 사전 요구 사항, 결과 계약은 다음 문서를 참고하세요.
- [설치 및 설정](docs/installation.md): 에이전트 등록, 제공자 설정, 업데이트, 제거.
- [준비 상태 확인 및 문제 해결](docs/installation.md#check-readiness-for-your-task): 특정 에이전트 또는 분석 엔진 진단.
- [CLI 및 Evidence](docs/cli.md): 명령, 제공자 선택, 스냅샷, 가져오기/내보내기, 종료 상태.
- [MCP 계약](docs/mcp-contracts.md) 및 [에이전트 프롬프트](docs/mcp-prompts.md): 도구 결과, 세션, 안내에 따른 조사.
- [도구 카탈로그](docs/mcp-contracts.md#generated-catalog): 빌드 시 생성되는 도구, 제공자, CLI 명령 목록.
- [로드맵](docs/roadmap.md): 계획된 작업과 기능 진행 상황.
취약점은 [SECURITY.md](SECURITY.md)에 따라 보고하세요.
## 기여
REA에 도움을 보태 주세요! [issue를 등록](https://github.com/morluto/rea/issues)해 버그나 기능을 제안하거나, [pull request를 보내](https://github.com/morluto/rea/pulls) 코드와 문서를 개선할 수 있습니다.
개발 환경과 검사 항목은 [CONTRIBUTING.md](CONTRIBUTING.md), 검증 절차는 [테스트 가이드](docs/testing.md), 프로젝트 구조는 [아키텍처 지도](docs/architecture.mermaid)를 참고하세요.
## 프로젝트 링크
[웹사이트](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [보안](SECURITY.md)
## 스타 기록
🎉 **GitHub 스타 20,000개, 감사합니다!**
🎉 **GitHub 스타 30,000개, 감사합니다!**
REA를 사용하고, 버그를 제보하고, 빌드를 테스트하고, 수정에 기여해 주신 모든 분께 감사드립니다.
REA를 사용하고, 버그를 보고하고, 기능을 요청하고, 빌드를 테스트하고, 수정에 기여해 주신 모든 분께 감사드립니다.
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
@@ -329,6 +278,10 @@ REA를 사용하고, 버그를 제보하고, 빌드를 테스트하고, 수정
</picture>
</a>
## 면책 조항
REA는 합법적인 리버스 엔지니어링 연구, 분석, 재구성을 위한 도구를 제공합니다. 필요한 권한을 얻고 관련 법률을 준수할 책임은 사용자에게 있습니다. 이 프로젝트는 불법적이거나 허가받지 않은 사용을 지지하지 않습니다.
## 라이선스
[MIT](LICENSE)
+285
View File
@@ -0,0 +1,285 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · **Polski** · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA: Reverse Engineer Anything
### Jeden serwer MCP do inżynierii wstecznej plików binarnych, aplikacji i zachowania w czasie wykonywania.
**Widzisz funkcję, która Ci się podoba? Dowiedz się, jak działa, aż do poziomu kodu binarnego.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[Strona internetowa](https://rea.tools/) · [Przewodniki](https://rea.tools/guides/) · [Przykłady zastosowań](https://rea.tools/showcase/)**
[Szybki start](#szybki-start) · [Jak działa REA](#jak-działa-rea) · [Co możesz analizować](#co-możesz-analizować) · [Przykłady zastosowań](#przykłady-zastosowań) · [FAQ](#faq) · [Dokumentacja](#dokumentacja)
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA uruchamia most analityczny w Hopperze podczas analizy natywnego pliku binarnego" width="1200" />
<br />
<table aria-label="Społeczność REA">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
<img src="docs/assets/discord.svg" height="42" alt="Discord" /><br />
<strong>Dołącz do społeczności inżynierii wstecznej</strong>
</a><br />
<sub>Discord · Pytania i odpowiedzi · Pokaż swój projekt</sub>
</td>
</tr>
</table>
<br />
</div>
---
Widzisz w aplikacji funkcję, którą chcesz mieć we własnym produkcie? Poproś swojego agenta, aby zbadał ją za pomocą REA. Może przeanalizować aplikację bez jej kodu źródłowego, wyjaśnić działanie funkcji, pokazać dowody i stworzyć jej wersję dla Twojego projektu.
REA łączy Twojego agenta z narzędziami do analizy natywnych plików binarnych, aplikacji JavaScript i Electron, zestawów .NET oraz stron internetowych. Z tych samych narzędzi możesz korzystać w terminalu. Analiza odbywa się lokalnie, a wyniki zawierają dowody i ograniczenia stojące za każdym wnioskiem.
Konfigurator rejestruje REA w Twoim agencie i instaluje pasujące instrukcje pracy. Analiza kodu natywnego może korzystać z istniejącej instalacji Hoppera lub Ghidry; konfigurator może też zainstalować Hoppera po uzyskaniu zgody. Statyczna analiza JavaScript nie wymaga żadnego z tych silników.
> **[Odwiedź stronę REA](https://rea.tools/)**, aby znaleźć instrukcje konfiguracji, ilustrowane przewodniki i rzeczywiste studia przypadków.
## Szybki start
### Skonfiguruj agenta
Po zainstalowaniu Node.js i npm uruchom:
```bash
npx rea-agents setup
```
Wybierz swoich agentów, sprawdź proponowane zmiany i zatwierdź je. Konfigurator dodaje serwer MCP REA i pasujące instrukcje pracy, tworząc kopie zapasowe istniejącej konfiguracji. Następnie uruchom agenta ponownie.
Konfigurator obsługuje Claude Code, Codex, Cursor, Gemini CLI, Grok Build i [innych agentów](docs/installation.md#supported-agents). Konfigurację dostawców analizy i ręczną rejestrację MCP opisuje przewodnik [instalacji i konfiguracji](docs/installation.md).
### Zapytaj agenta
```text
Sprawdź, jak działa wyszukiwanie w aplikacji Notes, pokaż mi dowody i stwórz podobną funkcję dla mojego projektu.
```
Zastąp Notes wybraną aplikacją i wskaż funkcję, którą chcesz zrozumieć.
### Korzystaj z terminala
Przeanalizuj rozpakowany katalog aplikacji JavaScript/Electron lub archiwum ASAR:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
Wynik obejmuje moduły, importy, granice komponentów Electron oraz dowody. Zastąp ścieżkę ścieżką do swojego celu, na przykład `"D:/apps/example"` w systemie Windows.
Aby zainstalować polecenie `rea` do regularnego użytku:
```bash
npm install --global rea-agents
rea --help
```
Przed analizą kodu natywnego skonfiguruj dostawcę analizy. Polecenia analizy natywnej, wybór dostawcy, migawki i użycie w skryptach opisuje [przewodnik CLI i Evidence](docs/cli.md).
### Zaktualizuj REA
REA szybko się rozwija, a nowe wydania często zawierają poprawki błędów. Dbaj o aktualność swojej instalacji.
Dla CLI zainstalowanego przez npm:
```bash
rea update
```
Aby odświeżyć rejestracje agentów i umiejętność, uruchom polecenie konfiguracji wyświetlone po aktualizacji.
Jeśli używasz `npx`, zaktualizuj konfigurację agenta poleceniem:
```bash
npx rea-agents@latest setup
```
Sprawdź zmiany konfiguracji i uruchom agenta ponownie. Do jednorazowych poleceń CLI używaj `npx rea-agents@latest`, a następnie nazwy polecenia.
## Jak działa REA
Twój agent wywołuje REA przez MCP, aby analizować cel i śledzić istotny kod. REA zwraca ustalenia wraz z dowodami. Agent wykorzystuje je do zadawania dalszych pytań, wyjaśniania zachowania lub pisania i testowania implementacji. Polecenia CLI korzystają z tych samych procedur.
![Przebieg badania z REA: agent pyta o lokalny cel, REA analizuje go i śledzi za pomocą narzędzi analitycznych, a agent korzysta ze zwróconego kodu, odwołań i niewiadomych, aby wyjaśnić działanie, stworzyć implementację i ją przetestować.](website/public/assets/figures/rea-investigation-flow.svg)
[Otwórz ilustrację w pełnym rozmiarze](website/public/assets/figures/rea-investigation-flow.svg).
<a id="current-status"></a>
## Co możesz analizować
REA wymaga Node.js 22.x (>=22.19), 24.x (>=24.11) lub 26+ oraz npm. Dodatkowe narzędzia i obsługiwane platformy zależą od celu analizy:
| Cel | Co zwraca REA | Wymagania i przewodnik |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Natywne pliki binarne | Pseudokod, kod asemblera, ciągi znaków, symbole, wywołania i odwołania | Hopper, Ghidra lub IDA; [analiza kodu natywnego](https://rea.tools/guides/native/) |
| Układ pliku ELF w trybie offline | Sekcje, segmenty, oryginalne symbole/relokacje i potencjalne mechanizmy zabezpieczeń wskazane przez analizę statyczną | pwntools dostarczone przez użytkownika w systemie Linux x64; [diagnostyka plików binarnych](docs/binary-diagnostics.md) |
| Kod bajtowy EVM | Selektory wywołań, przesunięcia bajtowe, wywnioskowane argumenty i mutowalność | Lokalny plik z surowymi bajtami lub zapisem szesnastkowym; [przewodnik EVM offline](docs/evm-bytecode.md) |
| Zarejestrowane awarie systemu Linux | Surowe rekordy note, rejestry/sygnały każdego zarejestrowanego wątku i opcjonalni kandydaci na mapowania | pwntools dostarczone przez użytkownika; opcjonalnie GDB/pwndbg; [zarejestrowane awarie](docs/recorded-crashes.md) |
| JavaScript / Electron | Moduły, importy, mapy źródeł, trasy, IPC i powiązania z natywnymi dodatkami | Node.js i npm; [analiza aplikacji](https://rea.tools/guides/javascript/) |
| Strony internetowe | Struktura strony, skrypty, obserwacje sieciowe i zamówione zrzuty ekranu | Przeglądarka z rodziny Chrome; [analiza w przeglądarce](https://rea.tools/guides/browser/) |
| Zapisane przechwycenia ruchu sieciowego | Żądania, odpowiedzi, dostępne dane przesyłane i lokalizacje źródłowe | HAR; mitmdump w systemie Linux dla natywnych przechwyceń mitmproxy; [przewodnik przechwytywania](docs/web-network-captures.md) |
| Zestawy .NET | Metadane, instrukcje CIL, zadeklarowane zależności natywne i porównania kompilacji | Analiza statyczna; [przewodnik kodu zarządzanego](docs/managed-code-analysis.md) |
| Pakiety APK dla Androida | Deklaracje manifestu, klasy, zdekompilowane metody i odwołania | JADX bez interfejsu graficznego i pełny JDK w systemie Linux/macOS; [przewodnik Androida](docs/android-analysis.md) |
| Oprogramowanie układowe | Regiony, wyniki wyodrębniania i przekazanie do analizy kodu natywnego | Binwalk / Unblob w systemie Linux; [przewodnik oprogramowania układowego](docs/firmware-analysis.md) |
| Pakiety i zasoby | Spisy plików, skróty, pliki plist, struktura pakietów Apple i wyodrębnione zasoby | [Przewodnik artefaktów i JavaScript](docs/javascript-artifact-reconstruction.md), [aplikacje Apple](docs/apple-application-analysis.md) |
| Zachowanie procesów | Wyjście terminala, interakcje, obserwacje zakończenia i systemu plików oraz porównania uruchomień | Linux/macOS z natywnym PTY; [przechwytywanie procesów](docs/process-capture.md) |
Statyczna analiza JavaScript i .NET odczytuje dostarczone pliki bez uruchamiania aplikacji. Przechwytywanie w czasie wykonywania uruchamia wybrany cel lub wchodzi z nim w interakcje z uprawnieniami Twojego użytkownika; skutki opisano w odpowiednich przewodnikach.
<a id="choosing-a-deep-analysis-provider"></a>
Obsługiwane formaty natywne i platformy zależą od dostawcy analizy. Zobacz [konfigurację Hoppera i Ghidry](docs/installation.md#hopper), [przewodnik IDA](docs/ida-provider.md) oraz [eksperymentalną obsługę Ghidry w systemie Windows](docs/windows-ghidra-p0.md). Ghidra obsługuje też [analizę 16-bitowego DOS-a](docs/ghidra-dos.md). Wybór dostawcy opisuje [przewodnik CLI](docs/cli.md#choose-a-provider). Sprawdź [dostępność w wydaniach](docs/installation.md#released-package-and-main) dla funkcji dodanych po najnowszym wydaniu npm.
## Przykłady zastosowań
### DX-Ball: odtworzenie obliczania panoramy dźwięku
Prześledź wywołanie dźwięku do funkcji pomocniczej przeliczającej pozycję na panoramę, sprawdź instrukcje i przekształć niepełny pseudokod w C. Odtworzona implementacja przechodzi 3 205 przypadków dla oryginalnego x86 i odtwarza wszystkie 63 bajty skompilowanej funkcji.
[Przeczytaj studium przypadku](https://rea.tools/showcase/dx-ball/) · [Repozytorium odtworzonej implementacji](https://github.com/N0zoM1z0/dx-ball)
### Notion: śledzenie mostu schowka w Electron
Znajdź API schowka w procesie renderującym, prześledź je przez preload i IPC do procesu głównego, a następnie sprawdź format schowka wraz z danymi formatowania.
[Przeczytaj studium przypadku](https://rea.tools/showcase/notion/)
### TH04: odtworzenie obliczania pierścienia pocisków w DOS-ie
Sprawdź 16-bitowe instrukcje oryginalnej gry na PC-98, odtwórz obliczenia kątów stałych i skierowanych na cel oraz porównaj odtworzony C++ z wynikiem historycznego kompilatora.
[Przeczytaj studium przypadku](https://rea.tools/showcase/th04/) · [Repozytorium odtworzonej implementacji](https://github.com/N0zoM1z0/th04)
Jeśli użyłeś REA do czegoś ciekawego, chętnie to zobaczymy. Opisz swój przypadek w [zgłoszeniu](https://github.com/morluto/rea/issues) lub [pull requeście](https://github.com/morluto/rea/pulls), podając cel analizy, pytanie, sposób, w jaki REA pomogło, i swoje ustalenia.
## FAQ
<details>
<summary><strong>Którzy agenci mogą korzystać z REA?</strong></summary>
Każdy agent obsługujący lokalne serwery MCP. Konfigurator konfiguruje [obsługiwanych agentów](docs/installation.md#supported-agents); inne klienty mogą użyć [ręcznej rejestracji MCP](docs/installation.md#mcp-registry).
</details>
<details>
<summary><strong>Czy potrzebuję Hoppera, Ghidry lub IDA?</strong></summary>
Dogłębna analiza kodu natywnego używa jednego z nich. Statyczna analiza JavaScript i .NET działa bez silnika analizy natywnej. Konfigurator może zainstalować Hoppera po uzyskaniu zgody; Ghidra i IDA korzystają z istniejących instalacji. Zobacz [konfigurację dostawców analizy](docs/installation.md#hopper).
</details>
<details>
<summary><strong>Czy muszę najpierw uruchomić Hoppera?</strong></summary>
REA uruchamia Hoppera, gdy wymaga tego operacja. W systemie macOS przy pierwszym uruchomieniu okno dialogowe może poprosić o wybór trybu demonstracyjnego lub aktywację licencji. Zobacz [uruchamianie Hoppera i rozwiązywanie problemów](docs/installation.md#launcher-paths-and-troubleshooting).
</details>
<details>
<summary><strong>Co daje zainstalowanie umiejętności z skills.sh?</strong></summary>
Umiejętność dostarcza agentowi instrukcje prowadzenia analizy. Użyj `rea setup`, aby zarejestrować serwer MCP REA i zainstalować pasujące instrukcje, a następnie uruchom agenta ponownie. Zobacz [instalację samej umiejętności](docs/installation.md#skill-only-installation).
</details>
<details>
<summary><strong>Jaki kod zwraca REA?</strong></summary>
Analiza kodu natywnego zwraca pseudokod i kod asemblera. Analiza JavaScript/Electron odzyskuje moduły i ich powiązania. Agent wykorzystuje te ustalenia do napisania i przetestowania implementacji; [przykłady zastosowań](#przykłady-zastosowań) pokazują ten proces w praktyce.
</details>
<details>
<summary><strong>Czy REA przesyła moją aplikację?</strong></summary>
REA analizuje cele lokalnie. Twój agent otrzymuje wyniki narzędzi, a dostawca jego modelu ma własną politykę dotyczącą danych.
</details>
<details>
<summary><strong>Co zrobić, gdy napotkam błąd?</strong></summary>
Najpierw zaktualizuj REA; najnowsze wydanie mogło już naprawić ten problem.
Dla CLI zainstalowanego przez npm:
```bash
rea update
```
Dla konfiguracji agenta przez `npx`:
```bash
npx rea-agents@latest setup
```
Jeśli używasz agenta, wykonaj [odświeżenie konfiguracji](#zaktualizuj-rea) i uruchom go ponownie. Ponów tę samą operację. Jeśli problem pozostaje, [otwórz zgłoszenie](https://github.com/morluto/rea/issues), podając wersję REA, typ celu analizy, kroki odtworzenia problemu i komunikaty błędu.
</details>
## Dokumentacja
Zacznij od [przewodników z przykładami](https://rea.tools/guides/) na stronie internetowej. Dokładne opcje, wymagania wstępne i kontrakty wyników opisano tutaj:
- [Instalacja i konfiguracja](docs/installation.md): rejestracja agentów, konfiguracja dostawców analizy, aktualizacje i odinstalowanie.
- [Gotowość i rozwiązywanie problemów](docs/installation.md#check-readiness-for-your-task): diagnostyka jednego agenta lub silnika analizy.
- [CLI i Evidence](docs/cli.md): polecenia, wybór dostawcy, migawki, import/eksport i kody zakończenia.
- [Kontrakty MCP](docs/mcp-contracts.md) i [prompty dla agentów](docs/mcp-prompts.md): wyniki narzędzi, sesje i prowadzone analizy.
- [Katalog narzędzi](docs/mcp-contracts.md#generated-catalog): generowany podczas kompilacji wykaz narzędzi, dostawców i poleceń CLI.
- [Plan rozwoju](docs/roadmap.md): planowane prace i śledzenie funkcjonalności.
Zgłaszaj podatności zgodnie z [SECURITY.md](SECURITY.md).
## Współtworzenie
Chętnie przyjmiemy Twoją pomoc w rozwoju REA! [Otwórz zgłoszenie](https://github.com/morluto/rea/issues), aby poinformować o błędzie lub zaproponować funkcję, albo [wyślij pull request](https://github.com/morluto/rea/pulls) z ulepszeniami kodu lub dokumentacji.
Zobacz [CONTRIBUTING.md](CONTRIBUTING.md), aby poznać konfigurację środowiska programistycznego i kontrole, [testowanie](docs/testing.md), aby poznać ścieżki weryfikacji, oraz [mapę architektury](docs/architecture.mermaid), aby poznać strukturę projektu.
## Linki projektu
[Strona internetowa](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Zgłoszenia](https://github.com/morluto/rea/issues) · [Bezpieczeństwo](SECURITY.md)
## Historia gwiazdek
🎉 **30 000 gwiazdek na GitHubie — dziękujemy!**
Dziękujemy wszystkim, którzy używają REA, zgłaszają błędy, proponują nowe funkcje, testują kompilacje i przesyłają poprawki.
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="Historia gwiazdek REA na GitHubie" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
</picture>
</a>
## Zastrzeżenie
REA dostarcza narzędzia do zgodnych z prawem badań z zakresu inżynierii wstecznej, analizy i odtwarzania. Odpowiadasz za uzyskanie wymaganych zezwoleń i przestrzeganie obowiązującego prawa. Projekt nie popiera działań nielegalnych ani nieautoryzowanych.
## Licencja
[MIT](LICENSE)
+285
View File
@@ -0,0 +1,285 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · **Português (Brasil)** · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA: Faça engenharia reversa de qualquer coisa
### Um único MCP para engenharia reversa de binários, aplicativos e comportamento em execução.
**Viu uma funcionalidade de que gostou? Entenda como ela funciona, até o nível binário.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[Site](https://rea.tools/) · [Guias](https://rea.tools/guides/) · [Casos de uso](https://rea.tools/showcase/)**
[Início rápido](#início-rápido) · [Como o REA funciona](#como-o-rea-funciona) · [O que você pode analisar](#o-que-você-pode-analisar) · [Casos de uso](#casos-de-uso) · [Perguntas frequentes](#perguntas-frequentes) · [Documentação](#documentação)
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA inicia sua ponte de análise dentro do Hopper enquanto inspeciona um binário nativo" width="1200" />
<br />
<table aria-label="Comunidade REA">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
<img src="docs/assets/discord.svg" height="42" alt="Discord" /><br />
<strong>Participe da comunidade de engenharia reversa</strong>
</a><br />
<sub>Discord · Perguntas e respostas · Compartilhe seus resultados</sub>
</td>
</tr>
</table>
<br />
</div>
---
Viu uma funcionalidade em um aplicativo que gostaria de ter no seu próprio produto? Peça ao seu agente para investigá-la com o REA. Ele pode inspecionar o aplicativo sem o código-fonte, explicar como a funcionalidade funciona, apresentar as evidências e criar uma versão para o seu projeto.
O REA conecta seu agente a ferramentas para inspecionar binários nativos, aplicativos JavaScript e Electron, assemblies .NET e sites. Você também pode usar as mesmas ferramentas pelo terminal. A análise acontece localmente, e os resultados incluem as evidências e limitações por trás de cada conclusão.
A configuração registra o REA no seu agente e instala as instruções de fluxo de trabalho correspondentes. A análise nativa pode usar uma instalação existente do Hopper ou do Ghidra; a configuração também pode instalar o Hopper opcionalmente, mediante aprovação. A análise estática de JavaScript não precisa de nenhum dos dois motores.
> **[Visite o site do REA](https://rea.tools/)** para instruções de configuração, guias ilustrados e estudos de caso reais.
## Início rápido
### Configure seu agente
Com Node.js e npm instalados, execute:
```bash
npx rea-agents setup
```
Escolha seus agentes, revise as alterações propostas e aprove-as. A configuração adiciona o servidor MCP do REA e as instruções de fluxo de trabalho correspondentes, com backups das configurações existentes. Reinicie seu agente depois.
A configuração oferece suporte ao Claude Code, Codex, Cursor, Gemini CLI, Grok Build e [outros agentes](docs/installation.md#supported-agents). Consulte [instalação e configuração](docs/installation.md) para configurar provedores e registrar o MCP manualmente.
### Peça ao seu agente
```text
Investigue como a busca funciona no aplicativo Notes, mostre as evidências e crie uma funcionalidade semelhante para o meu projeto.
```
Substitua Notes pelo aplicativo alvo e indique a funcionalidade que deseja entender.
### Use o terminal
Inspecione um diretório extraído de um aplicativo JavaScript/Electron ou um arquivo ASAR:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
O resultado inclui módulos, importações, limites do Electron e suas evidências. Substitua o caminho pelo do seu alvo, como `"D:/apps/example"` no Windows.
Para instalar o comando `rea` e usá-lo regularmente:
```bash
npm install --global rea-agents
rea --help
```
Para análise nativa, configure primeiro um provedor. Consulte o [guia de CLI e Evidence](docs/cli.md) para comandos nativos, seleção de provedores, snapshots e uso em scripts.
### Atualize o REA
O REA muda rapidamente, e novas versões incluem correções frequentes de bugs. Mantenha sua instalação atualizada.
Para uma CLI instalada pelo npm:
```bash
rea update
```
Para atualizar os registros dos agentes e a skill, execute o comando de configuração exibido pela atualização.
Se você usa `npx`, atualize a configuração do agente com:
```bash
npx rea-agents@latest setup
```
Revise as alterações de configuração e reinicie seu agente. Para comandos avulsos da CLI, use `npx rea-agents@latest` seguido do comando.
## Como o REA funciona
Seu agente chama o REA por MCP para inspecionar o alvo e rastrear o código relevante. O REA retorna os resultados com suas evidências. O agente usa esses resultados para fazer novas perguntas, explicar o comportamento ou escrever e testar uma implementação. Os comandos da CLI usam os mesmos fluxos de trabalho.
![Fluxo de investigação do REA: seu agente pergunta sobre um alvo local, o REA o inspeciona e rastreia com ferramentas de análise, e o agente usa o código, as referências e os pontos desconhecidos retornados para explicar, implementar e testar.](website/public/assets/figures/rea-investigation-flow.svg)
[Abra a figura em tamanho completo](website/public/assets/figures/rea-investigation-flow.svg).
<a id="current-status"></a>
## O que você pode analisar
O REA requer Node.js 22.x (>=22.19), 24.x (>=24.11) ou 26+, além do npm. As ferramentas adicionais e os sistemas anfitriões compatíveis dependem do alvo:
| Alvo | O que o REA retorna | Requisitos e guia |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Binários nativos | Pseudocódigo, assembly, strings, símbolos, chamadas e referências | Hopper, Ghidra ou IDA; [análise nativa](https://rea.tools/guides/native/) |
| Estrutura ELF sem execução | Seções, segmentos, símbolos/relocações originais e possíveis mecanismos de mitigação identificados por análise estática | pwntools fornecido pelo chamador no Linux x64; [diagnóstico de binários](docs/binary-diagnostics.md) |
| Bytecode EVM | Seletores de despacho, offsets de bytes, argumentos inferidos e mutabilidade | Entrada local com bytes brutos ou representação hexadecimal; [guia de EVM sem execução](docs/evm-bytecode.md) |
| Falhas do Linux registradas | Registros note brutos, registradores/sinais de cada thread registrada e candidatos opcionais a mapeamentos | pwntools fornecido pelo chamador; GDB/pwndbg opcionais; [falhas registradas](docs/recorded-crashes.md) |
| JavaScript / Electron | Módulos, importações, source maps, rotas, IPC e relações com complementos nativos | Node.js e npm; [análise de aplicativos](https://rea.tools/guides/javascript/) |
| Sites | Estrutura da página, scripts, observações de rede e capturas de tela solicitadas | Um navegador da família Chrome; [análise do navegador](https://rea.tools/guides/browser/) |
| Capturas de rede salvas | Requisições, respostas, payloads acessíveis e localizações de origem | HAR; mitmdump no Linux para capturas no formato nativo do mitmproxy; [guia de capturas](docs/web-network-captures.md) |
| Assemblies .NET | Metadados, instruções CIL, dependências nativas declaradas e comparações de builds | Inspeção estática; [guia de código gerenciado](docs/managed-code-analysis.md) |
| APKs Android | Declarações do manifesto, classes, métodos descompilados e referências | JADX sem interface gráfica e um JDK completo no Linux/macOS; [guia de Android](docs/android-analysis.md) |
| Firmware | Regiões, resultados de extração e encaminhamentos à análise nativa | Binwalk / Unblob no Linux; [guia de firmware](docs/firmware-analysis.md) |
| Pacotes e recursos | Inventários de arquivos, digests, plists, estrutura de bundles Apple e recursos extraídos | [Guia de artefatos e JavaScript](docs/javascript-artifact-reconstruction.md), [aplicativos Apple](docs/apple-application-analysis.md) |
| Comportamento de processos | Saída do terminal, interações, observações de encerramento e do sistema de arquivos, e comparações entre execuções | Linux/macOS com PTY nativo; [captura de processos](docs/process-capture.md) |
A inspeção estática de JavaScript e .NET lê os arquivos fornecidos sem executar o aplicativo. A captura em execução inicia o alvo selecionado ou interage com ele usando as permissões do seu usuário; cada guia descreve seus efeitos.
<a id="choosing-a-deep-analysis-provider"></a>
Os formatos nativos e os sistemas anfitriões compatíveis variam por provedor. Consulte [configuração do Hopper e Ghidra](docs/installation.md#hopper), o [guia do IDA](docs/ida-provider.md) e o [suporte experimental ao Ghidra no Windows](docs/windows-ghidra-p0.md). O Ghidra também oferece [análise de DOS de 16 bits](docs/ghidra-dos.md). Para selecionar um provedor, consulte o [guia da CLI](docs/cli.md#choose-a-provider). Verifique a [disponibilidade nas versões](docs/installation.md#released-package-and-main) das funcionalidades adicionadas após a versão mais recente no npm.
## Casos de uso
### DX-Ball: reconstruir um cálculo de panorâmica de áudio
Siga uma chamada de áudio até a função auxiliar que converte posição em panorâmica, inspecione as instruções e transforme pseudocódigo incompleto em C. A reconstrução passa em 3.205 casos com o x86 original e reproduz todos os 63 bytes da função compilada.
[Leia o estudo de caso](https://rea.tools/showcase/dx-ball/) · [Repositório da reconstrução](https://github.com/N0zoM1z0/dx-ball)
### Notion: rastrear a ponte da área de transferência do Electron
Encontre a API da área de transferência do renderizador, siga-a por preload e IPC até o processo principal e inspecione o formato da área de transferência com seus dados de formatação.
[Leia o estudo de caso](https://rea.tools/showcase/notion/)
### TH04: recuperar o cálculo de um anel de projéteis no DOS
Inspecione as instruções de 16 bits do jogo original para PC-98, recupere os cálculos de ângulos fixos e direcionados e compare o C++ reconstruído com a saída do compilador da época.
[Leia o estudo de caso](https://rea.tools/showcase/th04/) · [Repositório da reconstrução](https://github.com/N0zoM1z0/th04)
Se você usou o REA em algo interessante, gostaríamos de conhecer. Compartilhe seu caso em uma [issue](https://github.com/morluto/rea/issues) ou um [pull request](https://github.com/morluto/rea/pulls), incluindo o alvo, sua pergunta, como o REA ajudou e o que descobriu.
## Perguntas frequentes
<details>
<summary><strong>Quais agentes podem usar o REA?</strong></summary>
Qualquer agente que ofereça suporte a servidores MCP locais. A configuração prepara os [agentes compatíveis](docs/installation.md#supported-agents); outros clientes podem usar o [registro manual do MCP](docs/installation.md#mcp-registry).
</details>
<details>
<summary><strong>Preciso do Hopper, Ghidra ou IDA?</strong></summary>
A análise nativa aprofundada usa um desses motores. A inspeção estática de JavaScript e .NET funciona sem um motor de análise nativa. A configuração pode instalar o Hopper após aprovação; Ghidra e IDA usam as instalações existentes. Consulte [configuração dos provedores](docs/installation.md#hopper).
</details>
<details>
<summary><strong>Preciso iniciar o Hopper antes?</strong></summary>
O REA inicia o Hopper quando uma operação precisa dele. No macOS, um diálogo na primeira execução pode pedir que você escolha o modo de demonstração ou ative a licença. Consulte [inicialização do Hopper e solução de problemas](docs/installation.md#launcher-paths-and-troubleshooting).
</details>
<details>
<summary><strong>O que faz a instalação da skill pelo skills.sh?</strong></summary>
A skill fornece instruções de investigação ao seu agente. Use `rea setup` para registrar o servidor MCP do REA e instalar as instruções correspondentes, depois reinicie seu agente. Consulte [instalação apenas da skill](docs/installation.md#skill-only-installation).
</details>
<details>
<summary><strong>Que código o REA retorna?</strong></summary>
A análise nativa retorna pseudocódigo e assembly. A análise de JavaScript/Electron recupera módulos e suas relações. Seu agente usa esses resultados para escrever e testar uma implementação; os [casos de uso](#casos-de-uso) apresentam exemplos completos.
</details>
<details>
<summary><strong>O REA envia meu aplicativo para algum servidor?</strong></summary>
O REA analisa os alvos localmente. Seu agente recebe os resultados das ferramentas, e o provedor do modelo tem sua própria política de dados.
</details>
<details>
<summary><strong>O que devo fazer se encontrar um bug?</strong></summary>
Atualize primeiro; uma versão recente pode já ter corrigido o problema.
Para uma CLI instalada pelo npm:
```bash
rea update
```
Para configurar o agente por `npx`:
```bash
npx rea-agents@latest setup
```
Se você usa um agente, conclua a [atualização da configuração](#atualize-o-rea) e reinicie-o. Tente executar a mesma tarefa novamente. Se o problema persistir, [abra uma issue](https://github.com/morluto/rea/issues) com sua versão do REA, o tipo de alvo, os passos para reproduzir e a saída do erro.
</details>
## Documentação
Comece pelos [guias práticos](https://rea.tools/guides/) do site. Para opções exatas, pré-requisitos e contratos dos resultados:
- [Instalação e configuração](docs/installation.md): registro de agentes, configuração de provedores, atualizações e desinstalação.
- [Prontidão e solução de problemas](docs/installation.md#check-readiness-for-your-task): diagnóstico de um agente ou motor de análise específico.
- [CLI e Evidence](docs/cli.md): comandos, seleção de provedores, snapshots, importação/exportação e status de saída.
- [Contratos MCP](docs/mcp-contracts.md) e [prompts para agentes](docs/mcp-prompts.md): resultados das ferramentas, sessões e investigações guiadas.
- [Catálogo de ferramentas](docs/mcp-contracts.md#generated-catalog): inventário de ferramentas, provedores e comandos da CLI gerado durante o build.
- [Roadmap](docs/roadmap.md): trabalho planejado e acompanhamento das capacidades.
Relate vulnerabilidades conforme [SECURITY.md](SECURITY.md).
## Contribuir
Sua ajuda com o REA é bem-vinda! [Abra uma issue](https://github.com/morluto/rea/issues) para relatar um bug ou sugerir uma funcionalidade, ou [envie um pull request](https://github.com/morluto/rea/pulls) para melhorar o código ou a documentação.
Consulte [CONTRIBUTING.md](CONTRIBUTING.md) para configurar o ambiente de desenvolvimento e as verificações, o [guia de testes](docs/testing.md) para os fluxos de verificação e o [mapa da arquitetura](docs/architecture.mermaid) para a estrutura do projeto.
## Links do projeto
[Site](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [Segurança](SECURITY.md)
## Histórico de estrelas
🎉 **30.000 estrelas no GitHub — muito obrigado!**
Obrigado a todos que usam o REA, relatam bugs, sugerem funcionalidades, testam builds e contribuem com correções.
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="Histórico de estrelas do REA no GitHub" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
</picture>
</a>
## Aviso legal
O REA fornece ferramentas para pesquisa, análise e reconstrução por engenharia reversa dentro da lei. Você é responsável por obter as autorizações necessárias e cumprir as leis aplicáveis. O projeto não apoia usos ilegais ou não autorizados.
## Licença
[MIT](LICENSE)
+285
View File
@@ -0,0 +1,285 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · **Русский** · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA: Реверс-инжиниринг чего угодно
### Один MCP для реверс-инжиниринга бинарных файлов, приложений и поведения во время выполнения.
**Нашли интересную функцию? Разберитесь, как она работает, вплоть до двоичного кода.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[Сайт](https://rea.tools/) · [Руководства](https://rea.tools/guides/) · [Примеры](https://rea.tools/showcase/)**
[Быстрый старт](#быстрый-старт) · [Как работает REA](#как-работает-rea) · [Что можно анализировать](#что-можно-анализировать) · [Примеры](#примеры) · [Частые вопросы](#частые-вопросы) · [Документация](#документация)
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA запускает мост анализа внутри Hopper при исследовании нативного бинарного файла" width="1200" />
<br />
<table aria-label="Сообщество REA">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
<img src="docs/assets/discord.svg" height="42" alt="Discord" /><br />
<strong>Присоединяйтесь к сообществу реверс-инжиниринга</strong>
</a><br />
<sub>Discord · Вопросы и ответы · Обмен результатами</sub>
</td>
</tr>
</table>
<br />
</div>
---
Увидели в приложении функцию, которую хотите добавить в свой продукт? Попросите агента исследовать её с помощью REA. Он может изучить приложение без исходного кода, объяснить работу функции, показать подтверждающие данные и создать подходящую реализацию для вашего проекта.
REA подключает вашего агента к инструментам для исследования нативных бинарных файлов, приложений JavaScript и Electron, сборок .NET и сайтов. Те же инструменты доступны в терминале. Анализ выполняется локально, а результаты содержат данные и ограничения, на которых основан каждый вывод.
Настройка регистрирует REA в вашем агенте и устанавливает соответствующие инструкции по работе. Для нативного анализа можно использовать уже установленный Hopper или Ghidra; настройка также может установить Hopper с вашего согласия. Для статического анализа JavaScript не нужен ни один из этих движков.
> **[Посетите сайт REA](https://rea.tools/)**: там есть инструкции по настройке, иллюстрированные руководства и реальные исследования.
## Быстрый старт
### Настройте агента
Если Node.js и npm установлены, выполните:
```bash
npx rea-agents setup
```
Выберите агентов, проверьте предлагаемые изменения и подтвердите их. Настройка добавляет MCP-сервер REA и соответствующие рабочие инструкции, сохраняя резервные копии существующих настроек. После этого перезапустите агента.
Настройка поддерживает Claude Code, Codex, Cursor, Gemini CLI, Grok Build и [других агентов](docs/installation.md#supported-agents). Настройка провайдеров и ручная регистрация MCP описаны в разделе [установка и настройка](docs/installation.md).
### Задайте вопрос агенту
```text
Исследуй, как работает поиск в приложении Notes, покажи подтверждающие данные и создай похожую функцию для моего проекта.
```
Замените Notes нужным приложением и укажите функцию, которую хотите понять.
### Используйте терминал
Исследуйте распакованный каталог приложения JavaScript/Electron или файл ASAR:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
Результат включает модули, импорты, границы Electron и подтверждающие данные. Замените путь своим, например `"D:/apps/example"` в Windows.
Для регулярного использования установите команду `rea`:
```bash
npm install --global rea-agents
rea --help
```
Для нативного анализа сначала настройте провайдер. Нативные команды, выбор провайдера, снимки и работу со скриптами описывает [руководство по CLI и Evidence](docs/cli.md).
### Обновите REA
REA быстро развивается, и новые версии часто содержат исправления ошибок. Регулярно обновляйте установленную версию.
Для CLI, установленного через npm:
```bash
rea update
```
Чтобы обновить регистрации агентов и skill, выполните команду настройки, которую выведет обновление.
Если используете `npx`, обновите настройку агента командой:
```bash
npx rea-agents@latest setup
```
Проверьте изменения настроек и перезапустите агента. Для разовых команд CLI используйте `npx rea-agents@latest`, а затем нужную команду.
## Как работает REA
Агент вызывает REA через MCP, чтобы исследовать цель и проследить связанный код. REA возвращает результаты вместе с подтверждающими данными. Агент использует их для уточняющих вопросов, объяснения поведения или написания и тестирования реализации. Команды CLI используют те же рабочие процессы.
![Процесс исследования с REA: агент задаёт вопрос о локальной цели, REA исследует её инструментами анализа, а агент использует возвращённый код, ссылки и неизвестные сведения для объяснения, реализации и тестирования.](website/public/assets/figures/rea-investigation-flow.svg)
[Открыть рисунок в полном размере](website/public/assets/figures/rea-investigation-flow.svg).
<a id="current-status"></a>
## Что можно анализировать
REA требует Node.js 22.x (>=22.19), 24.x (>=24.11) или 26+, а также npm. Дополнительные инструменты и поддерживаемые системы зависят от цели:
| Цель | Что возвращает REA | Требования и руководство |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Нативные бинарные файлы | Псевдокод, ассемблер, строки, символы, вызовы и ссылки | Hopper, Ghidra или IDA; [нативный анализ](https://rea.tools/guides/native/) |
| Структура ELF без запуска | Секции, сегменты, исходные символы/релокации и возможные механизмы защиты по данным статического анализа | pwntools, предоставленный вызывающей стороной, в Linux x64; [диагностика бинарных файлов](docs/binary-diagnostics.md) |
| Байт-код EVM | Селекторы диспетчеризации, смещения байтов, предполагаемые аргументы и изменяемость состояния | Локальные необработанные байты или шестнадцатеричное представление; [руководство по EVM без запуска](docs/evm-bytecode.md) |
| Записанные сбои Linux | Необработанные записи note, регистры/сигналы каждого записанного потока и необязательные кандидаты сопоставления с памятью | pwntools, предоставленный вызывающей стороной; необязательные GDB/pwndbg; [записанные сбои](docs/recorded-crashes.md) |
| JavaScript / Electron | Модули, импорты, карты исходного кода, маршруты, IPC и связи с нативными дополнениями | Node.js и npm; [анализ приложений](https://rea.tools/guides/javascript/) |
| Сайты | Структура страницы, скрипты, наблюдения за сетью и запрошенные снимки экрана | Браузер семейства Chrome; [анализ браузера](https://rea.tools/guides/browser/) |
| Сохранённые сетевые захваты | Запросы, ответы, доступные полезные данные и позиции в источнике | HAR; mitmdump в Linux для захватов в собственном формате mitmproxy; [руководство по сетевым захватам](docs/web-network-captures.md) |
| Сборки .NET | Метаданные, инструкции CIL, объявленные нативные зависимости и сравнения сборок | Статическое исследование; [руководство по управляемому коду](docs/managed-code-analysis.md) |
| Android APK | Объявления манифеста, классы, декомпилированные методы и ссылки | JADX без графического интерфейса и полный JDK в Linux/macOS; [руководство по Android](docs/android-analysis.md) |
| Прошивки | Области, результаты извлечения и передача в нативный анализ | Binwalk / Unblob в Linux; [руководство по прошивкам](docs/firmware-analysis.md) |
| Пакеты и ресурсы | Списки файлов, хеши, plist, структура пакетов Apple и извлечённые ресурсы | [Руководство по артефактам и JavaScript](docs/javascript-artifact-reconstruction.md), [приложения Apple](docs/apple-application-analysis.md) |
| Поведение процессов | Вывод терминала, взаимодействия, наблюдения за завершением и файловой системой, сравнения запусков | Linux/macOS с нативным PTY; [захват процессов](docs/process-capture.md) |
Статическое исследование JavaScript и .NET читает предоставленные файлы, не запуская приложение. Захват во время выполнения запускает выбранную цель или взаимодействует с ней с правами вашего пользователя; соответствующие руководства описывают последствия.
<a id="choosing-a-deep-analysis-provider"></a>
Нативные форматы и поддерживаемые системы различаются у разных провайдеров. См. [настройку Hopper и Ghidra](docs/installation.md#hopper), [руководство по IDA](docs/ida-provider.md) и [экспериментальную поддержку Ghidra в Windows](docs/windows-ghidra-p0.md). Ghidra также поддерживает [анализ 16-битного DOS](docs/ghidra-dos.md). Выбор провайдера описан в [руководстве по CLI](docs/cli.md#choose-a-provider). Для функций, добавленных после последней версии в npm, проверьте [доступность в релизах](docs/installation.md#released-package-and-main).
## Примеры
### DX-Ball: восстановление расчёта звуковой панорамы
Проследите звуковой вызов до функции, преобразующей координату в значение панорамы, изучите инструкции и превратите неполный псевдокод в C. Восстановленная реализация проходит 3 205 тестов с исходным x86-кодом и воспроизводит все 63 байта скомпилированной функции.
[Прочитать исследование](https://rea.tools/showcase/dx-ball/) · [Репозиторий реконструкции](https://github.com/N0zoM1z0/dx-ball)
### Notion: исследование моста буфера обмена Electron
Найдите API буфера обмена в процессе рендеринга, проследите его через preload и IPC до основного процесса и изучите формат буфера обмена с форматированием.
[Прочитать исследование](https://rea.tools/showcase/notion/)
### TH04: восстановление расчёта кольца пуль в DOS
Изучите 16-битные инструкции оригинальной игры для PC-98, восстановите расчёты фиксированных и прицельных углов и сравните восстановленный C++ с выводом компилятора того времени.
[Прочитать исследование](https://rea.tools/showcase/th04/) · [Репозиторий реконструкции](https://github.com/N0zoM1z0/th04)
Если вы исследовали с REA что-то интересное, поделитесь результатом. Создайте [issue](https://github.com/morluto/rea/issues) или [pull request](https://github.com/morluto/rea/pulls) и укажите цель, ваш вопрос, как помог REA и что удалось узнать.
## Частые вопросы
<details>
<summary><strong>Какие агенты могут использовать REA?</strong></summary>
Любой агент с поддержкой локальных MCP-серверов. Настройка конфигурирует [поддерживаемых агентов](docs/installation.md#supported-agents); другие клиенты могут использовать [ручную регистрацию MCP](docs/installation.md#mcp-registry).
</details>
<details>
<summary><strong>Нужны ли мне Hopper, Ghidra или IDA?</strong></summary>
Углублённый нативный анализ использует один из этих инструментов. Статическое исследование JavaScript и .NET работает без движка нативного анализа. Настройка может установить Hopper после подтверждения; Ghidra и IDA используют существующие установки. См. [настройку провайдеров](docs/installation.md#hopper).
</details>
<details>
<summary><strong>Нужно ли сначала запускать Hopper?</strong></summary>
REA запускает Hopper, когда он нужен для операции. В macOS при первом запуске может появиться диалог выбора деморежима или активации лицензии. См. [запуск Hopper и устранение неполадок](docs/installation.md#launcher-paths-and-troubleshooting).
</details>
<details>
<summary><strong>Что даёт установка skill с skills.sh?</strong></summary>
Skill предоставляет агенту инструкции по исследованию. Используйте `rea setup`, чтобы зарегистрировать MCP-сервер REA и установить соответствующие инструкции, затем перезапустите агента. См. [установку только skill](docs/installation.md#skill-only-installation).
</details>
<details>
<summary><strong>Какой код возвращает REA?</strong></summary>
Нативный анализ возвращает псевдокод и ассемблер. Анализ JavaScript/Electron восстанавливает модули и их связи. Агент использует эти результаты, чтобы написать и протестировать реализацию; в [примерах](#примеры) показаны практические исследования.
</details>
<details>
<summary><strong>REA загружает моё приложение на сервер?</strong></summary>
REA анализирует цели локально. Ваш агент получает результаты инструментов, а у его поставщика моделей есть собственная политика обработки данных.
</details>
<details>
<summary><strong>Что делать, если я столкнулся с ошибкой?</strong></summary>
Сначала обновитесь: недавний релиз мог уже исправить ошибку.
Для CLI, установленного через npm:
```bash
rea update
```
Для настройки агента через `npx`:
```bash
npx rea-agents@latest setup
```
Если используете агента, завершите [обновление настройки](#обновите-rea) и перезапустите его. Повторите ту же задачу. Если проблема сохраняется, [откройте issue](https://github.com/morluto/rea/issues), указав версию REA, тип цели, шаги воспроизведения и вывод ошибки.
</details>
## Документация
Начните с [практических руководств](https://rea.tools/guides/) на сайте. Точные параметры, требования и контракты результатов описаны здесь:
- [Установка и настройка](docs/installation.md): регистрация агентов, настройка провайдеров, обновление и удаление.
- [Готовность и устранение неполадок](docs/installation.md#check-readiness-for-your-task): диагностика конкретного агента или движка анализа.
- [CLI и Evidence](docs/cli.md): команды, выбор провайдера, снимки, импорт/экспорт и коды завершения.
- [Контракты MCP](docs/mcp-contracts.md) и [промпты агентов](docs/mcp-prompts.md): результаты инструментов, сеансы и исследования с подсказками.
- [Каталог инструментов](docs/mcp-contracts.md#generated-catalog): создаваемый при сборке перечень инструментов, провайдеров и команд CLI.
- [План развития](docs/roadmap.md): запланированные работы и отслеживание возможностей.
Сообщайте об уязвимостях согласно [SECURITY.md](SECURITY.md).
## Участие в проекте
Будем рады вашей помощи с REA! [Откройте issue](https://github.com/morluto/rea/issues), чтобы сообщить об ошибке или предложить функцию, либо [отправьте pull request](https://github.com/morluto/rea/pulls) с улучшениями кода или документации.
Настройка среды разработки и проверки описаны в [CONTRIBUTING.md](CONTRIBUTING.md), направления проверки — в [руководстве по тестированию](docs/testing.md), а структура проекта — на [схеме архитектуры](docs/architecture.mermaid).
## Ссылки проекта
[Сайт](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [Безопасность](SECURITY.md)
## История звёзд
🎉 **30 000 звёзд на GitHub — спасибо!**
Спасибо всем, кто использует REA, сообщает об ошибках, предлагает новые функции, тестирует сборки и помогает с исправлениями.
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="История звёзд REA на GitHub" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
</picture>
</a>
## Отказ от ответственности
REA предоставляет инструменты для законных исследований, анализа и реконструкции с помощью реверс-инжиниринга. Вы отвечаете за получение необходимых разрешений и соблюдение применимых законов. Проект не поддерживает незаконное или несанкционированное использование.
## Лицензия
[MIT](LICENSE)
+285
View File
@@ -0,0 +1,285 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · **ไทย** · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA: Reverse Engineer Anything
### MCP เดียวสำหรับวิศวกรรมย้อนกลับ ทั้งไบนารี แอปพลิเคชัน และพฤติกรรมขณะทำงาน
**เห็นฟีเจอร์ที่ชอบแล้วอยากรู้ว่าทำงานอย่างไร? เข้าใจได้ลึกถึงระดับไบนารี**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[เว็บไซต์](https://rea.tools/) · [คู่มือ](https://rea.tools/guides/) · [ตัวอย่างการใช้งาน](https://rea.tools/showcase/)**
[เริ่มต้นอย่างรวดเร็ว](#เริ่มต้นอย่างรวดเร็ว) · [REA ทำงานอย่างไร](#rea-ทำงานอย่างไร) · [สิ่งที่วิเคราะห์ได้](#สิ่งที่วิเคราะห์ได้) · [ตัวอย่างการใช้งาน](#ตัวอย่างการใช้งาน) · [คำถามที่พบบ่อย](#คำถามที่พบบ่อย) · [เอกสาร](#เอกสาร)
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA เริ่มบริดจ์การวิเคราะห์ภายใน Hopper ขณะตรวจสอบไบนารีเนทีฟ" width="1200" />
<br />
<table aria-label="ชุมชน REA">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
<img src="docs/assets/discord.svg" height="42" alt="Discord" /><br />
<strong>เข้าร่วมชุมชนวิศวกรรมย้อนกลับ</strong>
</a><br />
<sub>Discord · ถามตอบ · แบ่งปันผลงาน</sub>
</td>
</tr>
</table>
<br />
</div>
---
เห็นฟีเจอร์ในแอปที่อยากนำมาใช้ในผลิตภัณฑ์ของคุณเองหรือไม่? ขอให้เอเจนต์ของคุณตรวจสอบด้วย REA เอเจนต์สามารถวิเคราะห์แอปโดยไม่ต้องมีซอร์สโค้ด อธิบายการทำงานของฟีเจอร์ แสดงหลักฐาน และสร้างเวอร์ชันสำหรับโปรเจกต์ของคุณได้
REA เชื่อมเอเจนต์ของคุณกับเครื่องมือสำหรับตรวจสอบไบนารีเนทีฟ แอป JavaScript และ Electron แอสเซมบลี .NET และเว็บไซต์ คุณใช้เครื่องมือเดียวกันจากเทอร์มินัลได้ การวิเคราะห์ทำงานบนเครื่องของคุณ และผลลัพธ์มีทั้งหลักฐานและข้อจำกัดที่ประกอบแต่ละข้อสรุป
ขั้นตอนตั้งค่าจะลงทะเบียน REA กับเอเจนต์และติดตั้งคำแนะนำการทำงานที่ตรงกัน การวิเคราะห์เนทีฟใช้ Hopper หรือ Ghidra ที่ติดตั้งอยู่แล้วได้ และขั้นตอนตั้งค่าสามารถติดตั้ง Hopper เพิ่มเติมเมื่อได้รับอนุมัติ การวิเคราะห์ JavaScript แบบสถิตไม่ต้องใช้เอนจินทั้งสองนี้
> **[เยี่ยมชมเว็บไซต์ REA](https://rea.tools/)** เพื่อดูคำแนะนำการตั้งค่า คู่มือพร้อมภาพประกอบ และกรณีศึกษาจริง
## เริ่มต้นอย่างรวดเร็ว
### ตั้งค่าเอเจนต์
เมื่อติดตั้ง Node.js และ npm แล้ว ให้เรียกใช้:
```bash
npx rea-agents setup
```
เลือกเอเจนต์ ตรวจสอบการเปลี่ยนแปลงที่เสนอ แล้วอนุมัติ ขั้นตอนตั้งค่าจะเพิ่มเซิร์ฟเวอร์ MCP ของ REA และคำแนะนำการทำงานที่ตรงกัน พร้อมสำรองการตั้งค่าที่มีอยู่ จากนั้นเริ่มเอเจนต์ใหม่
ขั้นตอนตั้งค่ารองรับ Claude Code, Codex, Cursor, Gemini CLI, Grok Build และ[เอเจนต์อื่น ๆ](docs/installation.md#supported-agents) ดู[การติดตั้งและตั้งค่า](docs/installation.md) สำหรับการตั้งค่าผู้ให้บริการวิเคราะห์และการลงทะเบียน MCP ด้วยตนเอง
### ถามเอเจนต์
```text
ตรวจสอบว่าการค้นหาในแอป Notes ทำงานอย่างไร แสดงหลักฐานให้ฉันดู และสร้างฟีเจอร์ที่คล้ายกันสำหรับโปรเจกต์ของฉัน
```
แทนที่ Notes ด้วยแอปเป้าหมายและฟีเจอร์ที่คุณต้องการเข้าใจ
### ใช้เทอร์มินัล
ตรวจสอบไดเรกทอรีแอป JavaScript/Electron ที่แตกไฟล์แล้ว หรือไฟล์ ASAR:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
ผลลัพธ์ประกอบด้วยโมดูล การนำเข้า ขอบเขตของ Electron และหลักฐาน แทนที่พาธด้วยเป้าหมายของคุณ เช่น `"D:/apps/example"` บน Windows
หากต้องการติดตั้งคำสั่ง `rea` สำหรับใช้งานเป็นประจำ:
```bash
npm install --global rea-agents
rea --help
```
สำหรับการวิเคราะห์เนทีฟ ให้ตั้งค่าผู้ให้บริการวิเคราะห์ก่อน ดู[คู่มือ CLI และ Evidence](docs/cli.md) สำหรับคำสั่งเนทีฟ การเลือกผู้ให้บริการ สแนปช็อต และการใช้งานในสคริปต์
### อัปเดต REA
REA เปลี่ยนแปลงอย่างรวดเร็ว และรุ่นใหม่มีการแก้ไขบั๊กอยู่บ่อยครั้ง โปรดอัปเดตการติดตั้งของคุณให้เป็นปัจจุบัน
สำหรับ CLI ที่ติดตั้งผ่าน npm:
```bash
rea update
```
หากต้องการปรับปรุงการลงทะเบียนเอเจนต์และสกิลให้เป็นปัจจุบัน ให้เรียกใช้คำสั่งตั้งค่าที่แสดงหลังการอัปเดต
หากใช้ `npx` ให้อัปเดตการตั้งค่าเอเจนต์ด้วย:
```bash
npx rea-agents@latest setup
```
ตรวจสอบการเปลี่ยนแปลงการตั้งค่าและเริ่มเอเจนต์ใหม่ สำหรับคำสั่ง CLI ที่ใช้ครั้งเดียว ให้ใช้ `npx rea-agents@latest` ตามด้วยคำสั่งที่ต้องการ
## REA ทำงานอย่างไร
เอเจนต์เรียก REA ผ่าน MCP เพื่อตรวจสอบเป้าหมายและติดตามโค้ดที่เกี่ยวข้อง REA ส่งข้อค้นพบกลับมาพร้อมหลักฐาน เอเจนต์ใช้ข้อมูลนี้เพื่อถามคำถามต่อ อธิบายพฤติกรรม หรือเขียนและทดสอบการนำไปใช้งาน คำสั่ง CLI ใช้กระบวนการเดียวกัน
![กระบวนการตรวจสอบด้วย REA: เอเจนต์ถามเกี่ยวกับเป้าหมายบนเครื่อง REA ตรวจสอบและติดตามด้วยเครื่องมือวิเคราะห์ แล้วเอเจนต์ใช้โค้ด การอ้างอิง และสิ่งที่ยังไม่ทราบที่ได้รับกลับมาเพื่ออธิบาย สร้าง และทดสอบ](website/public/assets/figures/rea-investigation-flow.svg)
[เปิดภาพขนาดเต็ม](website/public/assets/figures/rea-investigation-flow.svg)
<a id="current-status"></a>
## สิ่งที่วิเคราะห์ได้
REA ต้องใช้ Node.js 22.x (>=22.19), 24.x (>=24.11) หรือ 26+ และ npm เครื่องมือเพิ่มเติมและแพลตฟอร์มที่รองรับขึ้นอยู่กับเป้าหมาย:
| เป้าหมาย | สิ่งที่ REA ส่งกลับ | ข้อกำหนดและคู่มือ |
| ---------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| ไบนารีเนทีฟ | โค้ดเทียม แอสเซมบลี สตริง สัญลักษณ์ การเรียก และการอ้างอิง | Hopper, Ghidra หรือ IDA; [การวิเคราะห์เนทีฟ](https://rea.tools/guides/native/) |
| โครงสร้าง ELF แบบออฟไลน์ | เซกชัน เซกเมนต์ สัญลักษณ์/รีโลเคชันต้นฉบับ และตัวเลือกกลไกป้องกันที่พบจากการวิเคราะห์แบบสถิต | pwntools ที่ผู้ใช้จัดเตรียมบน Linux x64; [การวินิจฉัยไบนารี](docs/binary-diagnostics.md) |
| ไบต์โค้ด EVM | ตัวเลือกฟังก์ชันสำหรับการเรียก ออฟเซ็ตไบต์ อาร์กิวเมนต์และความสามารถในการเปลี่ยนสถานะที่อนุมานได้ | ไฟล์บนเครื่องที่มีไบต์ดิบหรือข้อมูลเลขฐานสิบหก; [คู่มือ EVM แบบออฟไลน์](docs/evm-bytecode.md) |
| ข้อมูลการแครชของ Linux ที่บันทึกไว้ | ระเบียน note ดิบ รีจิสเตอร์/สัญญาณของทุกเธรดที่บันทึกไว้ และตัวเลือกการแมปเพิ่มเติม | pwntools ที่ผู้ใช้จัดเตรียม; เลือกใช้ GDB/pwndbg เพิ่มเติมได้; [การแครชที่บันทึกไว้](docs/recorded-crashes.md) |
| JavaScript / Electron | โมดูล การนำเข้า ซอร์สแมป เส้นทาง IPC และความสัมพันธ์กับส่วนเสริมเนทีฟ | Node.js และ npm; [การวิเคราะห์แอปพลิเคชัน](https://rea.tools/guides/javascript/) |
| เว็บไซต์ | โครงสร้างหน้า สคริปต์ ข้อมูลเครือข่ายที่สังเกตได้ และภาพหน้าจอที่ร้องขอ | เบราว์เซอร์ในตระกูล Chrome; [การวิเคราะห์ผ่านเบราว์เซอร์](https://rea.tools/guides/browser/) |
| ข้อมูลเครือข่ายที่บันทึกไว้ | คำขอ คำตอบ เพย์โหลดที่ปรากฏ และตำแหน่งต้นทาง | HAR; mitmdump บน Linux สำหรับข้อมูลที่บันทึกในรูปแบบเนทีฟของ mitmproxy; [คู่มือการบันทึกข้อมูล](docs/web-network-captures.md) |
| แอสเซมบลี .NET | เมทาดาทา คำสั่ง CIL การพึ่งพาเนทีฟที่ประกาศไว้ และการเปรียบเทียบผลการบิลด์ | การตรวจสอบแบบสถิต; [คู่มือโค้ดแบบจัดการ](docs/managed-code-analysis.md) |
| APK ของ Android | การประกาศใน manifest คลาส เมธอดที่ดีคอมไพล์ และการอ้างอิง | JADX แบบไม่มี GUI และ JDK แบบเต็มบน Linux/macOS; [คู่มือ Android](docs/android-analysis.md) |
| เฟิร์มแวร์ | พื้นที่ข้อมูล ผลการแตกไฟล์ และการส่งต่อไปวิเคราะห์เนทีฟ | Binwalk / Unblob บน Linux; [คู่มือเฟิร์มแวร์](docs/firmware-analysis.md) |
| แพ็กเกจและทรัพยากร | รายการไฟล์ ค่าแฮช plist โครงสร้างบันเดิลของ Apple และทรัพยากรที่แตกออกมา | [คู่มืออาร์ติแฟกต์และ JavaScript](docs/javascript-artifact-reconstruction.md), [แอปพลิเคชัน Apple](docs/apple-application-analysis.md) |
| พฤติกรรมของโปรเซส | เอาต์พุตเทอร์มินัล การโต้ตอบ ข้อมูลการสิ้นสุดและระบบไฟล์ที่สังเกตได้ และการเปรียบเทียบการรัน | Linux/macOS ที่มี PTY เนทีฟ; [การบันทึกโปรเซส](docs/process-capture.md) |
การตรวจสอบ JavaScript และ .NET แบบสถิตอ่านไฟล์ที่ให้มาโดยไม่รันแอปพลิเคชัน การบันทึกขณะทำงานจะรันหรือโต้ตอบกับเป้าหมายที่เลือกโดยใช้สิทธิ์ของผู้ใช้คุณ คู่มือการทำงานแต่ละฉบับอธิบายผลที่เกิดขึ้นไว้
<a id="choosing-a-deep-analysis-provider"></a>
รูปแบบไบนารีเนทีฟและแพลตฟอร์มที่รองรับแตกต่างกันตามผู้ให้บริการวิเคราะห์ ดู[การตั้งค่า Hopper และ Ghidra](docs/installation.md#hopper), [คู่มือ IDA](docs/ida-provider.md) และ[การรองรับ Ghidra บน Windows แบบทดลอง](docs/windows-ghidra-p0.md) Ghidra ยังรองรับ[การวิเคราะห์ DOS แบบ 16 บิต](docs/ghidra-dos.md) สำหรับการเลือกผู้ให้บริการ ดู[คู่มือ CLI](docs/cli.md#choose-a-provider) ตรวจสอบ[ความพร้อมใช้งานในแต่ละรุ่น](docs/installation.md#released-package-and-main) สำหรับฟีเจอร์ที่เพิ่มหลังรุ่นล่าสุดบน npm
## ตัวอย่างการใช้งาน
### DX-Ball: สร้างการคำนวณแพนเสียงขึ้นใหม่
ติดตามการเรียกเสียงไปยังฟังก์ชันช่วยแปลงตำแหน่งเป็นค่าแพน ตรวจสอบคำสั่ง และแปลงโค้ดเทียมที่ยังไม่สมบูรณ์เป็น C การสร้างขึ้นใหม่นี้ผ่านการทดสอบ 3,205 กรณีเทียบกับ x86 ต้นฉบับ และสร้างไบต์ทั้ง 63 ไบต์ของฟังก์ชันที่คอมไพล์แล้วได้ตรงกัน
[อ่านกรณีศึกษา](https://rea.tools/showcase/dx-ball/) · [รีโพซิทอรีการสร้างขึ้นใหม่](https://github.com/N0zoM1z0/dx-ball)
### Notion: ติดตามบริดจ์คลิปบอร์ดของ Electron
ค้นหา API คลิปบอร์ดของ renderer ติดตามผ่าน preload และ IPC ไปยังโปรเซสหลัก แล้วตรวจสอบรูปแบบคลิปบอร์ดที่มีข้อมูลการจัดรูปแบบ
[อ่านกรณีศึกษา](https://rea.tools/showcase/notion/)
### TH04: กู้คืนการคำนวณวงแหวนกระสุนของ DOS
ตรวจสอบคำสั่ง 16 บิตของเกมต้นฉบับบน PC-98 กู้คืนการคำนวณมุมคงที่และมุมเล็งเป้าหมาย แล้วเปรียบเทียบ C++ ที่สร้างขึ้นใหม่กับผลลัพธ์จากคอมไพเลอร์ในยุคนั้น
[อ่านกรณีศึกษา](https://rea.tools/showcase/th04/) · [รีโพซิทอรีการสร้างขึ้นใหม่](https://github.com/N0zoM1z0/th04)
หากคุณใช้ REA กับสิ่งที่น่าสนใจ เราอยากเห็นผลงานของคุณ แบ่งปันกรณีของคุณผ่าน [issue](https://github.com/morluto/rea/issues) หรือ [pull request](https://github.com/morluto/rea/pulls) พร้อมระบุเป้าหมาย คำถามของคุณ REA ช่วยอย่างไร และสิ่งที่คุณค้นพบ
## คำถามที่พบบ่อย
<details>
<summary><strong>เอเจนต์ใดใช้ REA ได้บ้าง?</strong></summary>
เอเจนต์ใดก็ตามที่รองรับเซิร์ฟเวอร์ MCP บนเครื่อง ขั้นตอนตั้งค่าจะกำหนดค่าให้กับ[เอเจนต์ที่รองรับ](docs/installation.md#supported-agents) ส่วนไคลเอนต์อื่นใช้[การลงทะเบียน MCP ด้วยตนเอง](docs/installation.md#mcp-registry) ได้
</details>
<details>
<summary><strong>จำเป็นต้องมี Hopper, Ghidra หรือ IDA หรือไม่?</strong></summary>
การวิเคราะห์เนทีฟเชิงลึกต้องใช้หนึ่งในเครื่องมือเหล่านี้ การตรวจสอบ JavaScript และ .NET แบบสถิตทำงานได้โดยไม่มีเอนจินวิเคราะห์เนทีฟ ขั้นตอนตั้งค่าสามารถติดตั้ง Hopper หลังได้รับอนุมัติ ส่วน Ghidra และ IDA ใช้การติดตั้งที่คุณมีอยู่แล้ว ดู[การตั้งค่าผู้ให้บริการวิเคราะห์](docs/installation.md#hopper)
</details>
<details>
<summary><strong>ต้องเริ่ม Hopper ก่อนหรือไม่?</strong></summary>
REA เริ่ม Hopper เมื่อการดำเนินการต้องใช้ บน macOS อาจมีหน้าต่างเมื่อรันครั้งแรกให้เลือกโหมดทดลองหรือเปิดใช้ใบอนุญาต ดู[การเริ่ม Hopper และการแก้ไขปัญหา](docs/installation.md#launcher-paths-and-troubleshooting)
</details>
<details>
<summary><strong>การติดตั้งสกิลจาก skills.sh ทำอะไร?</strong></summary>
สกิลให้คำแนะนำการตรวจสอบแก่เอเจนต์ ใช้ `rea setup` เพื่อลงทะเบียนเซิร์ฟเวอร์ MCP ของ REA และติดตั้งคำแนะนำที่ตรงกัน แล้วเริ่มเอเจนต์ใหม่ ดู[การติดตั้งเฉพาะสกิล](docs/installation.md#skill-only-installation)
</details>
<details>
<summary><strong>REA ส่งกลับโค้ดแบบใด?</strong></summary>
การวิเคราะห์เนทีฟส่งกลับโค้ดเทียมและแอสเซมบลี การวิเคราะห์ JavaScript/Electron กู้คืนโมดูลและความสัมพันธ์ระหว่างโมดูล เอเจนต์ใช้ข้อค้นพบเหล่านี้เพื่อเขียนและทดสอบการนำไปใช้งาน ดูตัวอย่างที่ทำจริงใน[ตัวอย่างการใช้งาน](#ตัวอย่างการใช้งาน)
</details>
<details>
<summary><strong>REA อัปโหลดแอปของฉันหรือไม่?</strong></summary>
REA วิเคราะห์เป้าหมายบนเครื่องของคุณ เอเจนต์ได้รับผลลัพธ์ของเครื่องมือ และผู้ให้บริการโมเดลของเอเจนต์มีนโยบายข้อมูลของตนเอง
</details>
<details>
<summary><strong>ควรทำอย่างไรเมื่อพบข้อผิดพลาด?</strong></summary>
อัปเดตก่อน รุ่นล่าสุดอาจแก้ไขปัญหานี้แล้ว
สำหรับ CLI ที่ติดตั้งผ่าน npm:
```bash
rea update
```
สำหรับการตั้งค่าเอเจนต์ผ่าน `npx`:
```bash
npx rea-agents@latest setup
```
หากใช้เอเจนต์ ให้ทำ[การปรับปรุงการตั้งค่า](#อัปเดต-rea) ให้เสร็จแล้วเริ่มเอเจนต์ใหม่ ลองงานเดิมอีกครั้ง หากยังพบปัญหา ให้[เปิด issue](https://github.com/morluto/rea/issues) พร้อมระบุรุ่น REA ประเภทเป้าหมาย ขั้นตอนทำให้เกิดปัญหา และข้อความข้อผิดพลาด
</details>
## เอกสาร
เริ่มจาก[คู่มือพร้อมตัวอย่าง](https://rea.tools/guides/) บนเว็บไซต์ สำหรับตัวเลือก ข้อกำหนดเบื้องต้น และสัญญาผลลัพธ์ที่แน่นอน:
- [การติดตั้งและตั้งค่า](docs/installation.md): การลงทะเบียนเอเจนต์ การตั้งค่าผู้ให้บริการ การอัปเดต และการถอนการติดตั้ง
- [ความพร้อมใช้งานและการแก้ไขปัญหา](docs/installation.md#check-readiness-for-your-task): ตรวจวินิจฉัยเอเจนต์หรือเอนจินวิเคราะห์หนึ่งตัว
- [CLI และ Evidence](docs/cli.md): คำสั่ง การเลือกผู้ให้บริการ สแนปช็อต การนำเข้า/ส่งออก และรหัสการสิ้นสุด
- [สัญญา MCP](docs/mcp-contracts.md) และ[พรอมป์ต์สำหรับเอเจนต์](docs/mcp-prompts.md): ผลลัพธ์เครื่องมือ เซสชัน และการตรวจสอบตามคำแนะนำ
- [แค็ตตาล็อกเครื่องมือ](docs/mcp-contracts.md#generated-catalog): รายการเครื่องมือ ผู้ให้บริการ และคำสั่ง CLI ที่สร้างระหว่างการบิลด์
- [แผนพัฒนา](docs/roadmap.md): งานที่วางแผนไว้และรายการติดตามความสามารถ
รายงานช่องโหว่ผ่าน [SECURITY.md](SECURITY.md)
## การมีส่วนร่วม
เรายินดีรับความช่วยเหลือในการพัฒนา REA! [เปิด issue](https://github.com/morluto/rea/issues) เพื่อรายงานบั๊กหรือเสนอฟีเจอร์ หรือ[ส่ง pull request](https://github.com/morluto/rea/pulls) เพื่อปรับปรุงโค้ดหรือเอกสาร
ดู [CONTRIBUTING.md](CONTRIBUTING.md) สำหรับการตั้งค่าสภาพแวดล้อมพัฒนาและการตรวจสอบ ดู[การทดสอบ](docs/testing.md) สำหรับแนวทางการตรวจยืนยัน และ[แผนผังสถาปัตยกรรม](docs/architecture.mermaid) สำหรับโครงสร้างโปรเจกต์
## ลิงก์ของโปรเจกต์
[เว็บไซต์](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [ความปลอดภัย](SECURITY.md)
## ประวัติดาว
🎉 **30,000 ดาวบน GitHub — ขอบคุณทุกคน!**
ขอบคุณทุกคนที่ใช้ REA รายงานบั๊ก เสนอฟีเจอร์ ทดสอบบิลด์ และช่วยแก้ไข
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="ประวัติดาว GitHub ของ REA" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
</picture>
</a>
## ข้อสงวนสิทธิ์
REA มีเครื่องมือสำหรับการวิจัยวิศวกรรมย้อนกลับ การวิเคราะห์ และการสร้างขึ้นใหม่อย่างถูกกฎหมาย คุณมีหน้าที่ขออนุญาตตามที่จำเป็นและปฏิบัติตามกฎหมายที่เกี่ยวข้อง โปรเจกต์นี้ไม่สนับสนุนการใช้งานที่ผิดกฎหมายหรือไม่ได้รับอนุญาต
## สัญญาอนุญาต
[MIT](LICENSE)
+285
View File
@@ -0,0 +1,285 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · **Türkçe** · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA: Her şeyi tersine mühendislikle inceleyin
### İkili dosyaların, uygulamaların ve çalışma zamanı davranışının tersine mühendisliği için tek bir MCP.
**Beğendiğiniz bir özellik mi gördünüz? İkili kod düzeyine kadar nasıl çalıştığını anlayın.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[Web sitesi](https://rea.tools/) · [Rehberler](https://rea.tools/guides/) · [Örnek çalışmalar](https://rea.tools/showcase/)**
[Hızlı başlangıç](#hızlı-başlangıç) · [REA nasıl çalışır](#rea-nasıl-çalışır) · [Neleri analiz edebilirsiniz](#neleri-analiz-edebilirsiniz) · [Örnek çalışmalar](#örnek-çalışmalar) · [Sık sorulan sorular](#sık-sorulan-sorular) · [Belgeler](#belgeler)
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA, yerel bir ikili dosyayı incelerken Hopper içinde analiz köprüsünü başlatıyor" width="1200" />
<br />
<table aria-label="REA topluluğu">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
<img src="docs/assets/discord.svg" height="42" alt="Discord" /><br />
<strong>Tersine mühendislik topluluğuna katılın</strong>
</a><br />
<sub>Discord · Soru-cevap · Sonuç paylaşımı</sub>
</td>
</tr>
</table>
<br />
</div>
---
Bir uygulamada kendi ürününüze eklemek istediğiniz bir özellik mi gördünüz? Ajanınızdan REA ile araştırmasını isteyin. Kaynak kodu olmadan uygulamayı inceleyebilir, özelliğin nasıl çalıştığını açıklayabilir, kanıtları gösterebilir ve projenize uygun bir sürümünü geliştirebilir.
REA, ajanınızı yerel makine kodu içeren ikili dosyaları, JavaScript ve Electron uygulamalarını, .NET derlemelerini ve web sitelerini inceleyen araçlara bağlar. Aynı araçları terminalden de kullanabilirsiniz. Analiz yerel olarak çalışır; sonuçlar her çıkarımın dayandığı kanıtları ve sınırlamaları içerir.
Kurulum, REA'yı ajanınıza kaydeder ve uyumlu iş akışı talimatlarını yükler. Yerel makine kodu analizi mevcut bir Hopper veya Ghidra kurulumunu kullanabilir; kurulum işlemi, onayınızla isteğe bağlı olarak Hopper da yükleyebilir. Statik JavaScript analizi bu iki motora da ihtiyaç duymaz.
> Kurulum talimatları, görselli rehberler ve gerçek örnek çalışmalar için **[REA web sitesini ziyaret edin](https://rea.tools/)**.
## Hızlı başlangıç
### Ajanınızı yapılandırın
Node.js ve npm yüklüyken şu komutu çalıştırın:
```bash
npx rea-agents setup
```
Ajanlarınızı seçin, önerilen değişiklikleri inceleyin ve onaylayın. Kurulum, mevcut yapılandırmaları yedekleyerek REA'nın MCP sunucusunu ve uyumlu iş akışı talimatlarını ekler. Ardından ajanınızı yeniden başlatın.
Kurulum Claude Code, Codex, Cursor, Gemini CLI, Grok Build ve [diğer ajanları](docs/installation.md#supported-agents) destekler. Sağlayıcı yapılandırması ve elle MCP kaydı için [yükleme ve kurulum](docs/installation.md) belgesine bakın.
### Ajanınıza sorun
```text
Notes uygulamasında aramanın nasıl çalıştığını araştır, kanıtları göster ve projem için benzer bir özellik geliştir.
```
Notes yerine hedef uygulamanızı ve anlamak istediğiniz özelliği belirtin.
### Terminali kullanın
Çıkarılmış bir JavaScript/Electron uygulama dizinini veya ASAR dosyasını inceleyin:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
Sonuç; modülleri, içe aktarmaları, Electron sınırlarını ve bunlara ilişkin kanıtları içerir. Yolu hedefinizle değiştirin; Windows'ta örneğin `"D:/apps/example"` kullanabilirsiniz.
Düzenli kullanım için `rea` komutunu yüklemek üzere:
```bash
npm install --global rea-agents
rea --help
```
Yerel makine kodu analizi için önce bir sağlayıcı yapılandırın. Yerel analiz komutları, sağlayıcı seçimi, anlık görüntüler ve betik kullanımı için [CLI ve Evidence rehberine](docs/cli.md) bakın.
### REA'yı güncelleyin
REA hızla değişir ve yeni sürümler sık sık hata düzeltmeleri içerir. Kurulumunuzu güncel tutun.
npm ile yüklenen CLI için:
```bash
rea update
```
Ajan kayıtlarını ve skill'i güncellemek için güncellemenin çıktısında gösterilen kurulum komutunu çalıştırın.
`npx` kullanıyorsanız ajan kurulumunu şu komutla güncelleyin:
```bash
npx rea-agents@latest setup
```
Kurulum değişikliklerini inceleyin ve ajanınızı yeniden başlatın. Tek seferlik CLI işlemleri için `npx rea-agents@latest` komutunun ardından istediğiniz komutu yazın.
## REA nasıl çalışır
Ajanınız, hedefi incelemek ve ilgili kodu izlemek için MCP üzerinden REA'yı çağırır. REA bulguları kanıtlarıyla birlikte döndürür. Ajan bunları takip soruları sormak, davranışı açıklamak veya bir uygulama yazıp test etmek için kullanır. CLI komutları aynı iş akışlarını kullanır.
![REA araştırma akışı: ajanınız yerel bir hedef hakkında soru sorar, REA analiz araçlarıyla hedefi inceler ve izler; ajan döndürülen kodu, referansları ve bilinmeyenleri açıklama, geliştirme ve test için kullanır.](website/public/assets/figures/rea-investigation-flow.svg)
[Şekli tam boyutuyla açın](website/public/assets/figures/rea-investigation-flow.svg).
<a id="current-status"></a>
## Neleri analiz edebilirsiniz
REA; Node.js 22.x (>=22.19), 24.x (>=24.11) veya 26+ ve npm gerektirir. Ek araçlar ve desteklenen ana bilgisayar sistemleri hedefe bağlıdır:
| Hedef | REA'nın döndürdükleri | Gereksinimler ve rehber |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Yerel makine kodu içeren ikili dosyalar | Sözde kod, assembly kodu, dizeler, semboller, çağrılar ve referanslar | Hopper, Ghidra veya IDA; [yerel kod analizi](https://rea.tools/guides/native/) |
| Çevrimdışı ELF yapısı | Bölümler, segmentler, özgün semboller/yeniden konumlandırmalar ve statik analizden olası güvenlik önlemleri | Linux x64 üzerinde çağıranın sağladığı pwntools; [ikili dosya tanılama](docs/binary-diagnostics.md) |
| EVM bayt kodu | Yönlendirme seçicileri, bayt ofsetleri, çıkarımla belirlenen argümanlar ve durum değiştirilebilirliği | Yerel ham bayt/onaltılık girdi; [çevrimdışı EVM rehberi](docs/evm-bytecode.md) |
| Kaydedilmiş Linux çökmeleri | Ham note kayıtları, kaydedilen her iş parçacığının yazmaçları/sinyalleri ve isteğe bağlı eşleme adayları | Çağıranın sağladığı pwntools; isteğe bağlı GDB/pwndbg; [kaydedilmiş çökmeler](docs/recorded-crashes.md) |
| JavaScript / Electron | Modüller, içe aktarmalar, kaynak haritaları, rotalar, IPC ve yerel eklenti ilişkileri | Node.js ve npm; [uygulama analizi](https://rea.tools/guides/javascript/) |
| Web siteleri | Sayfa yapısı, betikler, ağ gözlemleri ve istenen ekran görüntüleri | Chrome ailesinden bir tarayıcı; [tarayıcı analizi](https://rea.tools/guides/browser/) |
| Kaydedilmiş ağ yakalamaları | İstekler, yanıtlar, erişilebilir yük içerikleri ve kaynak konumları | HAR; mitmproxy'nin kendi biçimindeki yakalamalar için Linux üzerinde mitmdump; [ağ yakalama rehberi](docs/web-network-captures.md) |
| .NET derlemeleri | Meta veriler, CIL talimatları, bildirilmiş yerel bağımlılıklar ve derleme karşılaştırmaları | Statik inceleme; [yönetilen kod rehberi](docs/managed-code-analysis.md) |
| Android APK'leri | Manifest bildirimleri, sınıflar, tersine derlenmiş metotlar ve referanslar | Linux/macOS üzerinde arayüzsüz JADX ve tam bir JDK; [Android rehberi](docs/android-analysis.md) |
| Donanım yazılımı | Bölgeler, çıkarma sonuçları ve yerel kod analizine devirler | Linux üzerinde Binwalk / Unblob; [donanım yazılımı rehberi](docs/firmware-analysis.md) |
| Paketler ve kaynaklar | Dosya envanterleri, özet değerleri, plist'ler, Apple paket yapısı ve çıkarılmış kaynaklar | [Artefakt ve JavaScript rehberi](docs/javascript-artifact-reconstruction.md), [Apple uygulamaları](docs/apple-application-analysis.md) |
| Süreç davranışı | Terminal çıktısı, etkileşimler, sonlanma ve dosya sistemi gözlemleri, çalıştırma karşılaştırmaları | Yerel PTY ile Linux/macOS; [süreç yakalama](docs/process-capture.md) |
Statik JavaScript ve .NET incelemesi, uygulamayı çalıştırmadan sağlanan dosyaları okur. Çalışma zamanı yakalaması, seçilen hedefi kullanıcı izinlerinizle çalıştırır veya onunla etkileşime girer; her çalışma zamanı rehberi etkilerini açıklar.
<a id="choosing-a-deep-analysis-provider"></a>
Yerel makine kodu içeren ikili dosya biçimleri ve ana bilgisayar desteği sağlayıcıya göre değişir. [Hopper ve Ghidra kurulumu](docs/installation.md#hopper), [IDA rehberi](docs/ida-provider.md) ve [Windows için deneysel Ghidra desteği](docs/windows-ghidra-p0.md) belgelerine bakın. Ghidra ayrıca [16 bit DOS analizini](docs/ghidra-dos.md) destekler. Sağlayıcı seçimi için [CLI rehberine](docs/cli.md#choose-a-provider) bakın. En son npm sürümünden sonra eklenen özellikler için [sürümlerde kullanılabilirliği](docs/installation.md#released-package-and-main) kontrol edin.
## Örnek çalışmalar
### DX-Ball: Sesin stereo konumlandırma hesabını yeniden oluşturma
Bir ses çağrısından konumu stereo konumlandırmaya dönüştüren yardımcı fonksiyona ilerleyin, talimatları inceleyin ve eksik sözde kodu C'ye dönüştürün. Yeniden oluşturulan kod, özgün x86 üzerinde 3.205 test durumunu geçer ve derlenmiş fonksiyonun 63 baytının tamamını yeniden üretir.
[Örnek çalışmayı okuyun](https://rea.tools/showcase/dx-ball/) · [Yeniden oluşturma deposu](https://github.com/N0zoM1z0/dx-ball)
### Notion: Electron pano köprüsünü izleme
İşleyici sürecin pano API'sini bulun, preload ve IPC üzerinden ana sürece kadar izleyin ve zengin pano biçimini inceleyin.
[Örnek çalışmayı okuyun](https://rea.tools/showcase/notion/)
### TH04: DOS mermi halkası hesabını geri çıkarma
Özgün PC-98 oyununun 16 bit talimatlarını inceleyin, sabit ve hedefe yönelik açı hesaplarını geri çıkarın ve yeniden oluşturulan C++ kodunu dönemin derleyici çıktısıyla karşılaştırın.
[Örnek çalışmayı okuyun](https://rea.tools/showcase/th04/) · [Yeniden oluşturma deposu](https://github.com/N0zoM1z0/th04)
REA'yı ilginç bir konuda kullandıysanız görmek isteriz. Hedefi, sorunuzu, REA'nın nasıl yardımcı olduğunu ve bulgularınızı bir [issue](https://github.com/morluto/rea/issues) veya [pull request](https://github.com/morluto/rea/pulls) ile paylaşın.
## Sık sorulan sorular
<details>
<summary><strong>Hangi ajanlar REA'yı kullanabilir?</strong></summary>
Yerel MCP sunucularını destekleyen her ajan REA'yı kullanabilir. Kurulum, [desteklenen ajanları](docs/installation.md#supported-agents) yapılandırır; diğer istemciler [elle MCP kaydını](docs/installation.md#mcp-registry) kullanabilir.
</details>
<details>
<summary><strong>Hopper, Ghidra veya IDA gerekiyor mu?</strong></summary>
Derin yerel kod analizi bunlardan birini kullanır. Statik JavaScript ve .NET incelemesi yerel analiz motoru olmadan çalışır. Kurulum onaydan sonra Hopper yükleyebilir; Ghidra ve IDA mevcut kurulumlarınızı kullanır. [Sağlayıcı kurulumuna](docs/installation.md#hopper) bakın.
</details>
<details>
<summary><strong>Önce Hopper'ı başlatmam gerekiyor mu?</strong></summary>
REA, bir işlem gerektirdiğinde Hopper'ı başlatır. macOS'ta ilk çalıştırmada demo modunu seçmenizi veya lisansınızı etkinleştirmenizi isteyen bir iletişim kutusu açılabilir. [Hopper başlatma ve sorun giderme](docs/installation.md#launcher-paths-and-troubleshooting) belgesine bakın.
</details>
<details>
<summary><strong>skills.sh üzerinden skill yüklemek ne yapar?</strong></summary>
Skill, ajanınıza araştırma talimatları sağlar. REA'nın MCP sunucusunu kaydetmek ve uyumlu talimatları yüklemek için `rea setup` kullanın, ardından ajanınızı yeniden başlatın. [Yalnızca skill yükleme](docs/installation.md#skill-only-installation) belgesine bakın.
</details>
<details>
<summary><strong>REA hangi kodu döndürür?</strong></summary>
Yerel kod analizi sözde kod ve assembly kodu döndürür. JavaScript/Electron analizi modülleri ve aralarındaki ilişkileri geri çıkarır. Ajanınız bu bulgularla bir uygulama yazar ve test eder; [örnek çalışmalar](#örnek-çalışmalar) uygulamalı örnekler sunar.
</details>
<details>
<summary><strong>REA uygulamamı yükler mi?</strong></summary>
REA hedefleri yerel olarak analiz eder. Ajanınız araç sonuçlarını alır ve model sağlayıcısının kendi veri politikası vardır.
</details>
<details>
<summary><strong>Bir hatayla karşılaşırsam ne yapmalıyım?</strong></summary>
Önce güncelleyin; yakın tarihli bir sürüm hatayı düzeltmiş olabilir.
npm ile yüklenen CLI için:
```bash
rea update
```
`npx` üzerinden ajan kurulumu için:
```bash
npx rea-agents@latest setup
```
Ajan kullanıyorsanız [kurulum yenilemesini](#reayı-güncelleyin) tamamlayın ve ajanı yeniden başlatın. Aynı görevi tekrar deneyin. Sorun sürerse REA sürümünüzü, hedef türünü, yeniden üretme adımlarını ve hata çıktısını içeren bir [issue açın](https://github.com/morluto/rea/issues).
</details>
## Belgeler
Web sitesindeki [uygulamalı rehberlerden](https://rea.tools/guides/) başlayın. Kesin seçenekler, ön koşullar ve sonuç sözleşmeleri için:
- [Yükleme ve kurulum](docs/installation.md): ajan kaydı, sağlayıcı yapılandırması, güncellemeler ve kaldırma.
- [Hazırlık ve sorun giderme](docs/installation.md#check-readiness-for-your-task): belirli bir ajanı veya analiz motorunu tanılama.
- [CLI ve Evidence](docs/cli.md): komutlar, sağlayıcı seçimi, anlık görüntüler, içe/dışa aktarma ve çıkış durumları.
- [MCP sözleşmeleri](docs/mcp-contracts.md) ve [ajan istemleri](docs/mcp-prompts.md): araç sonuçları, oturumlar ve yönlendirmeli araştırmalar.
- [Araç kataloğu](docs/mcp-contracts.md#generated-catalog): derleme sırasında oluşturulan araç, sağlayıcı ve CLI komutu envanteri.
- [Yol haritası](docs/roadmap.md): planlanan çalışmalar ve yetenek takibi.
Güvenlik açıklarını [SECURITY.md](SECURITY.md) üzerinden bildirin.
## Katkıda bulunma
REA'ya katkınızı bekliyoruz! Hata bildirmek veya özellik önermek için bir [issue açın](https://github.com/morluto/rea/issues); kodu veya belgeleri geliştirmek için bir [pull request gönderin](https://github.com/morluto/rea/pulls).
Geliştirme ortamı ve kontroller için [CONTRIBUTING.md](CONTRIBUTING.md), doğrulama süreçleri için [test rehberi](docs/testing.md) ve proje yapısı için [mimari harita](docs/architecture.mermaid) belgelerine bakın.
## Proje bağlantıları
[Web sitesi](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [Güvenlik](SECURITY.md)
## Yıldız geçmişi
🎉 **GitHub'da 30.000 yıldız — teşekkürler!**
REA'yı kullanan, hata bildiren, özellik talep eden, derlemeleri test eden ve düzeltmelere katkıda bulunan herkese teşekkürler.
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="REA GitHub yıldız geçmişi" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
</picture>
</a>
## Sorumluluk reddi
REA, yasal tersine mühendislik araştırması, analizi ve yeniden oluşturma için araçlar sağlar. Gerekli izinleri almak ve geçerli yasalara uymak sizin sorumluluğunuzdadır. Proje yasa dışı veya yetkisiz kullanımı desteklemez.
## Lisans
[MIT](LICENSE)
+285
View File
@@ -0,0 +1,285 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · **Українська** · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA: Реверс-інжиніринг будь-чого
### Один MCP для реверс-інжинірингу бінарних файлів, застосунків і поведінки під час виконання.
**Побачили цікаву функцію? Зрозумійте, як вона працює, аж до рівня двійкового коду.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[Сайт](https://rea.tools/) · [Посібники](https://rea.tools/guides/) · [Приклади](https://rea.tools/showcase/)**
[Швидкий старт](#швидкий-старт) · [Як працює REA](#як-працює-rea) · [Що можна аналізувати](#що-можна-аналізувати) · [Приклади](#приклади) · [Поширені запитання](#поширені-запитання) · [Документація](#документація)
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA запускає міст аналізу всередині Hopper під час дослідження нативного бінарного файла" width="1200" />
<br />
<table aria-label="Спільнота REA">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
<img src="docs/assets/discord.svg" height="42" alt="Discord" /><br />
<strong>Приєднуйтеся до спільноти реверс-інжинірингу</strong>
</a><br />
<sub>Discord · Запитання й відповіді · Обмін результатами</sub>
</td>
</tr>
</table>
<br />
</div>
---
Побачили в застосунку функцію, яку хочете додати до свого продукту? Попросіть агента дослідити її за допомогою REA. Він може перевірити застосунок без вихідного коду, пояснити роботу функції, показати підтвердження та створити реалізацію для вашого проєкту.
REA підключає вашого агента до інструментів дослідження нативних бінарних файлів, застосунків JavaScript і Electron, збірок .NET та сайтів. Ті самі інструменти можна використовувати з термінала. Аналіз виконується локально, а результати містять підтвердження й обмеження, на яких ґрунтується кожен висновок.
Налаштування реєструє REA у вашому агенті та встановлює відповідні інструкції робочих процесів. Для нативного аналізу можна використати вже встановлений Hopper або Ghidra; налаштування також може встановити Hopper за вашою згодою. Статичному аналізу JavaScript не потрібен жоден із цих рушіїв.
> **[Відвідайте сайт REA](https://rea.tools/)**, щоб знайти інструкції налаштування, ілюстровані посібники та реальні дослідження.
## Швидкий старт
### Налаштуйте агента
Якщо Node.js і npm встановлено, виконайте:
```bash
npx rea-agents setup
```
Виберіть агентів, перевірте запропоновані зміни та схваліть їх. Налаштування додає MCP-сервер REA та відповідні інструкції робочих процесів, створюючи резервні копії наявної конфігурації. Після цього перезапустіть агента.
Налаштування підтримує Claude Code, Codex, Cursor, Gemini CLI, Grok Build та [інших агентів](docs/installation.md#supported-agents). Конфігурацію провайдерів і ручну реєстрацію MCP описано в розділі [встановлення й налаштування](docs/installation.md).
### Запитайте агента
```text
Досліди, як працює пошук у застосунку Notes, покажи підтвердження й створи подібну функцію для мого проєкту.
```
Замініть Notes цільовим застосунком і вкажіть функцію, яку хочете зрозуміти.
### Використовуйте термінал
Перевірте розпакований каталог застосунку JavaScript/Electron або файл ASAR:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
Результат містить модулі, імпорти, межі Electron і підтвердження для них. Замініть шлях своїм, наприклад `"D:/apps/example"` у Windows.
Для регулярного використання встановіть команду `rea`:
```bash
npm install --global rea-agents
rea --help
```
Для нативного аналізу спочатку налаштуйте провайдера. Нативні команди, вибір провайдера, знімки та використання у скриптах описує [посібник CLI та Evidence](docs/cli.md).
### Оновіть REA
REA швидко розвивається, а нові випуски часто містять виправлення помилок. Підтримуйте встановлену версію актуальною.
Для CLI, встановленого через npm:
```bash
rea update
```
Щоб оновити реєстрації агентів і skill, виконайте команду налаштування, яку виведе оновлення.
Якщо використовуєте `npx`, оновіть налаштування агента командою:
```bash
npx rea-agents@latest setup
```
Перевірте зміни налаштувань і перезапустіть агента. Для разових команд CLI використовуйте `npx rea-agents@latest`, а потім потрібну команду.
## Як працює REA
Агент викликає REA через MCP, щоб перевірити ціль і простежити пов'язаний код. REA повертає знахідки разом із підтвердженнями. Агент використовує їх для уточнювальних запитань, пояснення поведінки або написання й тестування реалізації. Команди CLI використовують ті самі робочі процеси.
![Процес дослідження з REA: агент запитує про локальну ціль, REA перевіряє й простежує її інструментами аналізу, а агент використовує повернений код, посилання та невідомі дані для пояснення, реалізації й тестування.](website/public/assets/figures/rea-investigation-flow.svg)
[Відкрити зображення в повному розмірі](website/public/assets/figures/rea-investigation-flow.svg).
<a id="current-status"></a>
## Що можна аналізувати
REA потребує Node.js 22.x (>=22.19), 24.x (>=24.11) або 26+, а також npm. Додаткові інструменти й підтримувані системи залежать від цілі:
| Ціль | Що повертає REA | Вимоги й посібник |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Нативні бінарні файли | Псевдокод, асемблерний код, рядки, символи, виклики й посилання | Hopper, Ghidra або IDA; [нативний аналіз](https://rea.tools/guides/native/) |
| Структура ELF без запуску | Секції, сегменти, початкові символи/релокації та можливі захисні механізми за даними статичного аналізу | pwntools, наданий стороною, що викликає інструмент, у Linux x64; [діагностика бінарних файлів](docs/binary-diagnostics.md) |
| Байт-код EVM | Селектори диспетчеризації, зміщення байтів, виведені припущення про аргументи та змінюваність стану | Локальні необроблені байти або шістнадцяткове представлення; [посібник EVM без запуску](docs/evm-bytecode.md) |
| Записані аварійні завершення в Linux | Необроблені записи note, регістри/сигнали кожного записаного потоку й необов'язкові кандидати зіставлення пам'яті | pwntools, наданий стороною, що викликає інструмент; необов'язкові GDB/pwndbg; [записані аварійні завершення](docs/recorded-crashes.md) |
| JavaScript / Electron | Модулі, імпорти, карти вихідного коду, маршрути, IPC і зв'язки з нативними доповненнями | Node.js і npm; [аналіз застосунків](https://rea.tools/guides/javascript/) |
| Сайти | Структура сторінки, скрипти, мережеві спостереження й запитані знімки екрана | Браузер сімейства Chrome; [аналіз браузера](https://rea.tools/guides/browser/) |
| Збережені мережеві захоплення | Запити, відповіді, доступні корисні дані та місця в джерелі | HAR; mitmdump у Linux для захоплень у власному форматі mitmproxy; [посібник мережевих захоплень](docs/web-network-captures.md) |
| Збірки .NET | Метадані, інструкції CIL, оголошені нативні залежності й порівняння збірок | Статична перевірка; [посібник керованого коду](docs/managed-code-analysis.md) |
| Android APK | Оголошення маніфесту, класи, декомпільовані методи й посилання | JADX без графічного інтерфейсу й повний JDK у Linux/macOS; [посібник Android](docs/android-analysis.md) |
| Прошивки | Області, результати видобування й передавання до нативного аналізу | Binwalk / Unblob у Linux; [посібник прошивок](docs/firmware-analysis.md) |
| Пакети й ресурси | Переліки файлів, хеші, plist, структура пакетів Apple та видобуті ресурси | [Посібник артефактів і JavaScript](docs/javascript-artifact-reconstruction.md), [застосунки Apple](docs/apple-application-analysis.md) |
| Поведінка процесів | Вивід термінала, взаємодії, спостереження за завершенням і файловою системою та порівняння запусків | Linux/macOS із нативним PTY; [захоплення процесів](docs/process-capture.md) |
Статична перевірка JavaScript і .NET читає надані файли, не запускаючи застосунок. Захоплення під час виконання запускає вибрану ціль або взаємодіє з нею з правами вашого користувача; відповідні посібники описують його вплив.
<a id="choosing-a-deep-analysis-provider"></a>
Нативні формати й підтримка систем відрізняються залежно від провайдера. Дивіться [налаштування Hopper і Ghidra](docs/installation.md#hopper), [посібник IDA](docs/ida-provider.md) та [експериментальну підтримку Ghidra у Windows](docs/windows-ghidra-p0.md). Ghidra також підтримує [аналіз 16-бітного DOS](docs/ghidra-dos.md). Для вибору провайдера дивіться [посібник CLI](docs/cli.md#choose-a-provider). Для функцій, доданих після найновішої версії в npm, перевірте [доступність у випусках](docs/installation.md#released-package-and-main).
## Приклади
### DX-Ball: відновлення розрахунку звукової панорами
Простежте звуковий виклик до допоміжної функції, що перетворює координату на панорамування, перевірте інструкції та перетворіть неповний псевдокод на C. Відновлена реалізація проходить 3 205 тестів з оригінальним x86-кодом і відтворює всі 63 байти скомпільованої функції.
[Прочитати дослідження](https://rea.tools/showcase/dx-ball/) · [Репозиторій реконструкції](https://github.com/N0zoM1z0/dx-ball)
### Notion: відстеження мосту буфера обміну Electron
Знайдіть API буфера обміну в процесі рендерингу, простежте його через preload та IPC до головного процесу й перевірте формат буфера обміну з даними форматування.
[Прочитати дослідження](https://rea.tools/showcase/notion/)
### TH04: відновлення розрахунку кільця куль у DOS
Перевірте 16-бітні інструкції оригінальної гри для PC-98, відновіть розрахунки фіксованих і прицільних кутів та порівняйте відновлений C++ із виводом тогочасного компілятора.
[Прочитати дослідження](https://rea.tools/showcase/th04/) · [Репозиторій реконструкції](https://github.com/N0zoM1z0/th04)
Якщо ви дослідили з REA щось цікаве, поділіться результатом. Створіть [issue](https://github.com/morluto/rea/issues) або [pull request](https://github.com/morluto/rea/pulls) і вкажіть ціль, ваше запитання, як допоміг REA та що ви з'ясували.
## Поширені запитання
<details>
<summary><strong>Які агенти можуть використовувати REA?</strong></summary>
Будь-який агент, що підтримує локальні MCP-сервери. Налаштування конфігурує [підтримуваних агентів](docs/installation.md#supported-agents); інші клієнти можуть використати [ручну реєстрацію MCP](docs/installation.md#mcp-registry).
</details>
<details>
<summary><strong>Чи потрібні мені Hopper, Ghidra або IDA?</strong></summary>
Поглиблений нативний аналіз використовує один із цих інструментів. Статична перевірка JavaScript і .NET працює без рушія нативного аналізу. Налаштування може встановити Hopper після схвалення; Ghidra й IDA використовують наявні встановлення. Дивіться [налаштування провайдерів](docs/installation.md#hopper).
</details>
<details>
<summary><strong>Чи потрібно спочатку запускати Hopper?</strong></summary>
REA запускає Hopper, коли він потрібен для операції. У macOS під час першого запуску може з'явитися діалог вибору деморежиму або активації ліцензії. Дивіться [запуск Hopper та усунення неполадок](docs/installation.md#launcher-paths-and-troubleshooting).
</details>
<details>
<summary><strong>Що дає встановлення skill із skills.sh?</strong></summary>
Skill надає агенту інструкції дослідження. Використовуйте `rea setup`, щоб зареєструвати MCP-сервер REA та встановити відповідні інструкції, а потім перезапустіть агента. Дивіться [встановлення лише skill](docs/installation.md#skill-only-installation).
</details>
<details>
<summary><strong>Який код повертає REA?</strong></summary>
Нативний аналіз повертає псевдокод і асемблерний код. Аналіз JavaScript/Electron відновлює модулі та їхні зв'язки. Агент використовує ці результати, щоб написати й протестувати реалізацію; [приклади](#приклади) містять практичні дослідження.
</details>
<details>
<summary><strong>Чи завантажує REA мій застосунок на сервер?</strong></summary>
REA аналізує цілі локально. Ваш агент отримує результати інструментів, а постачальник його моделі має власну політику даних.
</details>
<details>
<summary><strong>Що робити, якщо я зіткнувся з помилкою?</strong></summary>
Спочатку оновіться: нещодавній випуск уже міг виправити помилку.
Для CLI, встановленого через npm:
```bash
rea update
```
Для налаштування агента через `npx`:
```bash
npx rea-agents@latest setup
```
Якщо використовуєте агента, завершіть [оновлення налаштувань](#оновіть-rea) і перезапустіть його. Повторіть те саме завдання. Якщо проблема залишається, [відкрийте issue](https://github.com/morluto/rea/issues) із версією REA, типом цілі, кроками відтворення та виводом помилки.
</details>
## Документація
Почніть із [практичних посібників](https://rea.tools/guides/) на сайті. Точні параметри, передумови й контракти результатів описано тут:
- [Встановлення й налаштування](docs/installation.md): реєстрація агентів, конфігурація провайдерів, оновлення та видалення.
- [Готовність та усунення неполадок](docs/installation.md#check-readiness-for-your-task): діагностика конкретного агента або рушія аналізу.
- [CLI та Evidence](docs/cli.md): команди, вибір провайдера, знімки, імпорт/експорт і статуси завершення.
- [Контракти MCP](docs/mcp-contracts.md) і [промпти агентів](docs/mcp-prompts.md): результати інструментів, сеанси й керовані дослідження.
- [Каталог інструментів](docs/mcp-contracts.md#generated-catalog): створюваний під час збірки перелік інструментів, провайдерів і команд CLI.
- [План розвитку](docs/roadmap.md): заплановані роботи й відстеження можливостей.
Повідомляйте про вразливості згідно з [SECURITY.md](SECURITY.md).
## Участь у проєкті
Будемо раді вашій допомозі з REA! [Відкрийте issue](https://github.com/morluto/rea/issues), щоб повідомити про помилку чи запропонувати функцію, або [надішліть pull request](https://github.com/morluto/rea/pulls) з поліпшеннями коду чи документації.
Налаштування середовища розробки й перевірки описано в [CONTRIBUTING.md](CONTRIBUTING.md), шляхи верифікації — у [посібнику тестування](docs/testing.md), а структуру проєкту — на [схемі архітектури](docs/architecture.mermaid).
## Посилання проєкту
[Сайт](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [Безпека](SECURITY.md)
## Історія зірок
🎉 **30 000 зірок на GitHub — дякуємо!**
Дякуємо всім, хто використовує REA, повідомляє про помилки, пропонує нові функції, тестує збірки й допомагає з виправленнями.
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="Історія зірок REA на GitHub" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
</picture>
</a>
## Відмова від відповідальності
REA надає інструменти для законних досліджень, аналізу й реконструкції за допомогою реверс-інжинірингу. Ви відповідаєте за отримання необхідних дозволів і дотримання застосовних законів. Проєкт не підтримує незаконне або несанкціоноване використання.
## Ліцензія
[MIT](LICENSE)
+285
View File
@@ -0,0 +1,285 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · **Tiếng Việt** · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA: Kỹ thuật dịch ngược cho mọi thứ
### Một MCP để dịch ngược tệp nhị phân, ứng dụng và hành vi khi chạy.
**Thấy một tính năng bạn thích? Hiểu cách nó hoạt động, đến tận mức mã nhị phân.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[Trang web](https://rea.tools/) · [Hướng dẫn](https://rea.tools/guides/) · [Các nghiên cứu điển hình](https://rea.tools/showcase/)**
[Bắt đầu nhanh](#bắt-đầu-nhanh) · [Cách REA hoạt động](#cách-rea-hoạt-động) · [Những gì bạn có thể phân tích](#những-gì-bạn-có-thể-phân-tích) · [Các nghiên cứu điển hình](#các-nghiên-cứu-điển-hình) · [Câu hỏi thường gặp](#câu-hỏi-thường-gặp) · [Tài liệu](#tài-liệu)
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA khởi chạy cầu nối phân tích bên trong Hopper khi kiểm tra tệp nhị phân mã máy" width="1200" />
<br />
<table aria-label="Cộng đồng REA">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
<img src="docs/assets/discord.svg" height="42" alt="Discord" /><br />
<strong>Tham gia cộng đồng kỹ thuật dịch ngược</strong>
</a><br />
<sub>Discord · Hỏi đáp · Chia sẻ kết quả</sub>
</td>
</tr>
</table>
<br />
</div>
---
Bạn thấy một tính năng trong ứng dụng và muốn đưa nó vào sản phẩm của mình? Hãy nhờ tác nhân của bạn điều tra bằng REA. Tác nhân có thể kiểm tra ứng dụng mà không cần mã nguồn, giải thích cách tính năng hoạt động, đưa ra bằng chứng và xây dựng một phiên bản cho dự án của bạn.
REA kết nối tác nhân của bạn với các công cụ kiểm tra tệp nhị phân mã máy, ứng dụng JavaScript và Electron, assembly .NET và trang web. Bạn cũng có thể dùng các công cụ đó từ terminal. Việc phân tích chạy cục bộ; kết quả bao gồm bằng chứng và giới hạn làm cơ sở cho từng kết luận.
Quá trình thiết lập đăng ký REA với tác nhân và cài đặt các hướng dẫn quy trình tương ứng. Phân tích mã máy có thể sử dụng Hopper hoặc Ghidra đã cài sẵn; quá trình thiết lập cũng có thể cài Hopper nếu bạn chấp thuận. Phân tích tĩnh JavaScript không cần công cụ nào trong hai công cụ này.
> **[Truy cập trang web REA](https://rea.tools/)** để xem cách thiết lập, hướng dẫn có hình minh họa và các nghiên cứu điển hình thực tế.
## Bắt đầu nhanh
### Thiết lập tác nhân
Sau khi cài Node.js và npm, chạy:
```bash
npx rea-agents setup
```
Chọn các tác nhân, xem lại những thay đổi được đề xuất và chấp thuận. Quá trình thiết lập thêm máy chủ MCP của REA cùng hướng dẫn quy trình tương ứng, đồng thời sao lưu cấu hình hiện có. Sau đó khởi động lại tác nhân.
Quá trình thiết lập hỗ trợ Claude Code, Codex, Cursor, Gemini CLI, Grok Build và [các tác nhân khác](docs/installation.md#supported-agents). Xem [cài đặt và thiết lập](docs/installation.md) để cấu hình trình phân tích và đăng ký MCP thủ công.
### Hỏi tác nhân
```text
Tìm hiểu cách tính năng tìm kiếm trong ứng dụng Notes hoạt động, cho tôi xem bằng chứng và xây dựng tính năng tương tự cho dự án của tôi.
```
Thay Notes bằng ứng dụng mục tiêu và chỉ rõ tính năng bạn muốn hiểu.
### Sử dụng terminal
Kiểm tra thư mục ứng dụng JavaScript/Electron đã giải nén hoặc tệp ASAR:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
Kết quả bao gồm mô-đun, các khai báo import, ranh giới Electron và bằng chứng tương ứng. Thay đường dẫn bằng mục tiêu của bạn, chẳng hạn `"D:/apps/example"` trên Windows.
Để cài lệnh `rea` cho việc sử dụng thường xuyên:
```bash
npm install --global rea-agents
rea --help
```
Để phân tích mã máy, trước tiên hãy cấu hình trình phân tích. Xem [hướng dẫn CLI và Evidence](docs/cli.md) về các lệnh phân tích mã máy, lựa chọn trình phân tích, ảnh chụp trạng thái và sử dụng trong script.
### Cập nhật REA
REA thay đổi nhanh và các bản phát hành mới thường xuyên có bản sửa lỗi. Hãy giữ bản cài đặt của bạn được cập nhật.
Với CLI được cài bằng npm:
```bash
rea update
```
Để cập nhật đăng ký tác nhân và skill, chạy lệnh thiết lập được in ra sau khi cập nhật.
Nếu dùng `npx`, cập nhật thiết lập tác nhân bằng:
```bash
npx rea-agents@latest setup
```
Xem lại thay đổi cấu hình và khởi động lại tác nhân. Với lệnh CLI dùng một lần, dùng `npx rea-agents@latest` rồi thêm lệnh cần chạy.
## Cách REA hoạt động
Tác nhân gọi REA qua MCP để kiểm tra mục tiêu và lần theo mã liên quan. REA trả về các phát hiện kèm bằng chứng. Tác nhân dùng chúng để đặt câu hỏi tiếp theo, giải thích hành vi hoặc viết và kiểm thử một cách triển khai. Các lệnh CLI sử dụng cùng quy trình.
![Quy trình điều tra của REA: tác nhân hỏi về mục tiêu cục bộ, REA kiểm tra và lần theo mục tiêu bằng công cụ phân tích, rồi tác nhân dùng mã, tham chiếu và những điểm chưa biết được trả về để giải thích, triển khai và kiểm thử.](website/public/assets/figures/rea-investigation-flow.svg)
[Mở hình ở kích thước đầy đủ](website/public/assets/figures/rea-investigation-flow.svg).
<a id="current-status"></a>
## Những gì bạn có thể phân tích
REA yêu cầu Node.js 22.x (>=22.19), 24.x (>=24.11) hoặc 26+, cùng với npm. Công cụ bổ sung và nền tảng chạy REA được hỗ trợ tùy thuộc vào mục tiêu:
| Mục tiêu | REA trả về gì | Yêu cầu và hướng dẫn |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Tệp nhị phân mã máy | Mã giả, hợp ngữ, chuỗi, ký hiệu, lời gọi và tham chiếu | Hopper, Ghidra hoặc IDA; [phân tích mã máy](https://rea.tools/guides/native/) |
| Bố cục ELF ngoại tuyến | Các section, segment, ký hiệu/thông tin tái định vị gốc và các biện pháp giảm thiểu có thể có từ phân tích tĩnh | pwntools do bên gọi cung cấp trên Linux x64; [chẩn đoán tệp nhị phân](docs/binary-diagnostics.md) |
| Bytecode EVM | Bộ chọn điều phối, độ lệch byte, tham số suy luận và khả năng thay đổi trạng thái | Đầu vào cục bộ chứa byte thô hoặc dạng thập lục phân; [hướng dẫn EVM ngoại tuyến](docs/evm-bytecode.md) |
| Sự cố Linux đã ghi lại | Bản ghi note thô, thanh ghi/tín hiệu của mọi luồng được ghi lại và các ứng viên ánh xạ tùy chọn | pwntools do bên gọi cung cấp; GDB/pwndbg tùy chọn; [sự cố đã ghi lại](docs/recorded-crashes.md) |
| JavaScript / Electron | Mô-đun, các khai báo import, source map, tuyến, IPC và quan hệ với tiện ích mở rộng mã máy | Node.js và npm; [phân tích ứng dụng](https://rea.tools/guides/javascript/) |
| Trang web | Cấu trúc trang, script, quan sát mạng và ảnh chụp màn hình được yêu cầu | Trình duyệt thuộc họ Chrome; [phân tích trình duyệt](https://rea.tools/guides/browser/) |
| Bản ghi lưu lượng mạng đã lưu | Yêu cầu, phản hồi, payload có thể truy cập và vị trí nguồn | HAR; mitmdump trên Linux cho bản ghi định dạng gốc của mitmproxy; [hướng dẫn bản ghi mạng](docs/web-network-captures.md) |
| Assembly .NET | Siêu dữ liệu, lệnh CIL, các phụ thuộc mã máy được khai báo và so sánh bản dựng | Kiểm tra tĩnh; [hướng dẫn mã được quản lý](docs/managed-code-analysis.md) |
| APK Android | Khai báo manifest, lớp, phương thức được dịch ngược và tham chiếu | JADX không giao diện đồ họa và JDK đầy đủ trên Linux/macOS; [hướng dẫn Android](docs/android-analysis.md) |
| Phần sụn | Các vùng, kết quả trích xuất và dữ liệu chuyển sang phân tích mã máy | Binwalk / Unblob trên Linux; [hướng dẫn phần sụn](docs/firmware-analysis.md) |
| Gói và tài nguyên | Danh sách tệp, giá trị băm, plist, cấu trúc bundle Apple và tài nguyên được trích xuất | [Hướng dẫn hiện vật phần mềm và JavaScript](docs/javascript-artifact-reconstruction.md), [ứng dụng Apple](docs/apple-application-analysis.md) |
| Hành vi tiến trình | Đầu ra terminal, tương tác, quan sát khi thoát và hệ thống tệp, cùng so sánh các lần chạy | Linux/macOS có PTY gốc; [ghi lại tiến trình](docs/process-capture.md) |
Kiểm tra tĩnh JavaScript và .NET đọc các tệp được cung cấp mà không chạy ứng dụng. Ghi lại khi chạy sẽ khởi chạy hoặc tương tác với mục tiêu đã chọn bằng quyền người dùng của bạn; từng hướng dẫn mô tả tác động của nó.
<a id="choosing-a-deep-analysis-provider"></a>
Định dạng mã máy và nền tảng chạy REA được hỗ trợ khác nhau tùy trình phân tích. Xem [thiết lập Hopper và Ghidra](docs/installation.md#hopper), [hướng dẫn IDA](docs/ida-provider.md) và [hỗ trợ Ghidra thử nghiệm trên Windows](docs/windows-ghidra-p0.md). Ghidra cũng hỗ trợ [phân tích DOS 16 bit](docs/ghidra-dos.md). Để chọn trình phân tích, xem [hướng dẫn CLI](docs/cli.md#choose-a-provider). Kiểm tra [khả năng sử dụng theo bản phát hành](docs/installation.md#released-package-and-main) đối với tính năng được bổ sung sau phiên bản npm mới nhất.
## Các nghiên cứu điển hình
### DX-Ball: tái tạo phép tính phân bố âm thanh trái phải
Lần theo lời gọi âm thanh đến hàm hỗ trợ chuyển vị trí thành giá trị phân bố âm thanh, kiểm tra các lệnh và chuyển mã giả chưa đầy đủ sang C. Bản tái tạo vượt qua 3.205 trường hợp kiểm thử với x86 gốc và tái tạo đủ 63 byte của hàm đã biên dịch.
[Đọc nghiên cứu điển hình](https://rea.tools/showcase/dx-ball/) · [Kho mã tái tạo](https://github.com/N0zoM1z0/dx-ball)
### Notion: lần theo cầu nối clipboard của Electron
Tìm API clipboard trong tiến trình kết xuất, lần theo qua preload và IPC đến tiến trình chính, rồi kiểm tra định dạng clipboard có dữ liệu định dạng phong phú.
[Đọc nghiên cứu điển hình](https://rea.tools/showcase/notion/)
### TH04: khôi phục phép tính vòng đạn trong DOS
Kiểm tra các lệnh 16 bit của trò chơi PC-98 gốc, khôi phục phép tính góc cố định và góc nhắm mục tiêu, rồi so sánh C++ được tái tạo với đầu ra của trình biên dịch thời đó.
[Đọc nghiên cứu điển hình](https://rea.tools/showcase/th04/) · [Kho mã tái tạo](https://github.com/N0zoM1z0/th04)
Nếu bạn dùng REA cho một mục tiêu thú vị, chúng tôi muốn biết. Chia sẻ trường hợp của bạn qua [issue](https://github.com/morluto/rea/issues) hoặc [pull request](https://github.com/morluto/rea/pulls), nêu mục tiêu, câu hỏi, cách REA giúp bạn và những gì tìm được.
## Câu hỏi thường gặp
<details>
<summary><strong>Tác nhân nào có thể dùng REA?</strong></summary>
Bất kỳ tác nhân nào hỗ trợ máy chủ MCP cục bộ. Quá trình thiết lập cấu hình các [tác nhân được hỗ trợ](docs/installation.md#supported-agents); ứng dụng khách khác có thể dùng [đăng ký MCP thủ công](docs/installation.md#mcp-registry).
</details>
<details>
<summary><strong>Tôi có cần Hopper, Ghidra hoặc IDA không?</strong></summary>
Phân tích mã máy chuyên sâu sử dụng một trong những công cụ đó. Kiểm tra tĩnh JavaScript và .NET hoạt động mà không cần bộ máy phân tích mã máy. Quá trình thiết lập có thể cài Hopper sau khi được chấp thuận; Ghidra và IDA dùng bản cài đặt sẵn có. Xem [thiết lập trình phân tích](docs/installation.md#hopper).
</details>
<details>
<summary><strong>Tôi có cần khởi chạy Hopper trước không?</strong></summary>
REA khởi chạy Hopper khi một thao tác cần đến nó. Trên macOS, hộp thoại lần chạy đầu có thể yêu cầu chọn chế độ dùng thử hoặc kích hoạt giấy phép. Xem [khởi động Hopper và khắc phục sự cố](docs/installation.md#launcher-paths-and-troubleshooting).
</details>
<details>
<summary><strong>Cài skill từ skills.sh có tác dụng gì?</strong></summary>
Skill cung cấp hướng dẫn điều tra cho tác nhân. Dùng `rea setup` để đăng ký máy chủ MCP của REA và cài hướng dẫn tương ứng, sau đó khởi động lại tác nhân. Xem [chỉ cài skill](docs/installation.md#skill-only-installation).
</details>
<details>
<summary><strong>REA trả về mã gì?</strong></summary>
Phân tích mã máy trả về mã giả và hợp ngữ. Phân tích JavaScript/Electron khôi phục mô-đun cùng quan hệ giữa chúng. Tác nhân dùng các phát hiện này để viết và kiểm thử cách triển khai; các [nghiên cứu điển hình](#các-nghiên-cứu-điển-hình) cung cấp ví dụ thực hành.
</details>
<details>
<summary><strong>REA có tải ứng dụng của tôi lên không?</strong></summary>
REA phân tích mục tiêu cục bộ. Tác nhân nhận kết quả công cụ, và nhà cung cấp mô hình của tác nhân có chính sách dữ liệu riêng.
</details>
<details>
<summary><strong>Tôi nên làm gì khi gặp lỗi?</strong></summary>
Hãy cập nhật trước; một bản phát hành gần đây có thể đã sửa lỗi.
Với CLI được cài bằng npm:
```bash
rea update
```
Với thiết lập tác nhân qua `npx`:
```bash
npx rea-agents@latest setup
```
Nếu dùng tác nhân, hãy hoàn tất [làm mới thiết lập](#cập-nhật-rea) và khởi động lại nó. Thử lại cùng tác vụ. Nếu vấn đề vẫn còn, [mở issue](https://github.com/morluto/rea/issues) với phiên bản REA, loại mục tiêu, các bước tái hiện và đầu ra lỗi.
</details>
## Tài liệu
Bắt đầu với [hướng dẫn thực hành](https://rea.tools/guides/) trên trang web. Để biết tùy chọn cụ thể, điều kiện tiên quyết và hợp đồng kết quả:
- [Cài đặt và thiết lập](docs/installation.md): đăng ký tác nhân, cấu hình trình phân tích, cập nhật và gỡ cài đặt.
- [Kiểm tra mức sẵn sàng và khắc phục sự cố](docs/installation.md#check-readiness-for-your-task): chẩn đoán một tác nhân hoặc bộ máy phân tích cụ thể.
- [CLI và Evidence](docs/cli.md): lệnh, lựa chọn trình phân tích, ảnh chụp trạng thái, nhập/xuất và trạng thái thoát.
- [Hợp đồng MCP](docs/mcp-contracts.md) và [prompt cho tác nhân](docs/mcp-prompts.md): kết quả công cụ, phiên làm việc và điều tra có hướng dẫn.
- [Danh mục công cụ](docs/mcp-contracts.md#generated-catalog): danh sách công cụ, trình phân tích và lệnh CLI được tạo khi dựng dự án.
- [Lộ trình](docs/roadmap.md): công việc dự kiến và theo dõi các khả năng.
Báo cáo lỗ hổng theo [SECURITY.md](SECURITY.md).
## Đóng góp
Chúng tôi hoan nghênh bạn giúp phát triển REA! [Mở issue](https://github.com/morluto/rea/issues) để báo lỗi hoặc đề xuất tính năng, hoặc [gửi pull request](https://github.com/morluto/rea/pulls) để cải thiện mã hay tài liệu.
Xem [CONTRIBUTING.md](CONTRIBUTING.md) về thiết lập môi trường phát triển và các bước kiểm tra, [hướng dẫn kiểm thử](docs/testing.md) về các luồng xác minh và [sơ đồ kiến trúc](docs/architecture.mermaid) về cấu trúc dự án.
## Liên kết dự án
[Trang web](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [Bảo mật](SECURITY.md)
## Lịch sử sao
🎉 **30.000 sao trên GitHub — xin cảm ơn!**
Cảm ơn mọi người đã sử dụng REA, báo lỗi, đề xuất tính năng, kiểm thử bản dựng và đóng góp bản sửa lỗi.
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="Lịch sử sao GitHub của REA" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
</picture>
</a>
## Miễn trừ trách nhiệm
REA cung cấp công cụ cho nghiên cứu dịch ngược, phân tích và tái tạo hợp pháp. Bạn có trách nhiệm có được các quyền cho phép cần thiết và tuân thủ luật áp dụng. Dự án không ủng hộ việc sử dụng trái pháp luật hoặc không được cho phép.
## Giấy phép
[MIT](LICENSE)
+287
View File
@@ -0,0 +1,287 @@
<div align="center">
[English](README.md) · [简体中文](README_zh.md) · **繁體中文** · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA:逆向分析一切
### 通過一個 MCP 服務,逆向分析二進位檔案、應用程式和執行階段行為。
**看到喜歡的功能,弄清它的工作原理,深入到二進位層面。**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[網站](https://rea.tools/) · [指南](https://rea.tools/guides/) · [案例](https://rea.tools/showcase/)**
[快速開始](#快速開始) · [REA 的工作原理](#rea-的工作原理) · [可以分析什麼](#可以分析什麼) · [案例](#案例) · [常見問題](#常見問題) · [文件](#文件)
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA 在 Hopper 中啟動分析橋,檢查原生二進位檔案" width="1200" />
<br />
<table aria-label="REA 社群">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
<img src="docs/assets/discord.svg" height="42" alt="Discord" /><br />
<strong>加入逆向工程社群</strong>
</a><br />
<sub>Discord · 問答 · 成果分享</sub>
</td>
</tr>
</table>
<br />
</div>
---
在應用中看到一個想加入自己產品的功能?讓 AI 代理用 REA 調查它。即使沒有原始碼,AI 代理也可以檢查應用、解釋功能的工作方式、展示證據,並為你的專案實現類似功能。
REA 將 AI 代理連接到分析工具,用於檢查原生二進位檔案、JavaScript 和 Electron 應用、.NET 程式集以及網站。你也可以在終端中使用同樣的工具。分析在本機執行,結果包含支撐各項結論的證據和相關限制。
設定流程會向 AI 代理註冊 REA,並安裝對應的工作流程指引。原生分析可以使用已有的 Hopper 或 Ghidra;設定流程也可以在你核准後安裝 Hopper。靜態 JavaScript 分析不需要這兩個引擎。
> **[訪問 REA 網站](https://rea.tools/)**,查看設定說明、圖解指南和真實案例。
## 快速開始
### 設定 AI 代理
安裝 Node.js 和 npm 後,執行:
```bash
npx rea-agents setup
```
選擇 AI 代理,檢查計劃中的變更並核准。設定流程會添加 REA 的 MCP 服務和對應的工作流程指引,並備份已有組態。完成後重新啟動 AI 代理。
設定流程支援 Claude Code、Codex、Cursor、Gemini CLI、Grok Build 和[其他 AI 代理](docs/installation.md#supported-agents)。提供者組態和手動註冊 MCP 的方法見[安裝與設定](docs/installation.md)。
### 詢問 AI 代理
```text
弄清 Notes 應用的搜尋功能如何工作,展示證據,並為我的專案實現類似功能。
```
把 Notes 替換為目標應用,並描述你想瞭解的功能。
### 使用終端
檢查已解包的 JavaScript/Electron 應用目錄或 ASAR 檔案:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
結果包含模組、匯入、Electron 邊界及相關證據。將路徑替換為你的目標路徑,例如 Windows 上的 `"D:/apps/example"`。
如果需要經常使用,可以安裝 `rea` 命令:
```bash
npm install --global rea-agents
rea --help
```
原生分析需要先設定分析提供者。原生命令、提供者選擇、快照和腳本用法見 [CLI 與 Evidence 指南](docs/cli.md)。
### 更新 REA
REA 更新較快,新版本經常包含錯誤修復。請將安裝的版本保持最新。
通過 npm 安裝的 CLI 使用:
```bash
rea update
```
要更新 AI 代理註冊和 skill,請執行更新命令輸出的設定命令。
如果使用 `npx`,通過以下命令更新 AI 代理設定:
```bash
npx rea-agents@latest setup
```
檢查設定變更並重新啟動 AI 代理。單次 CLI 操作可使用 `npx rea-agents@latest`,後接所需命令。
## REA 的工作原理
AI 代理通過 MCP 呼叫 REA,檢查目標並追蹤相關程式碼。REA 返回發現及對應證據。AI 代理據此提出後續問題、解釋行為,或編寫並測試實現。CLI 命令使用同樣的工作流程。
![REA 調查流程:AI 代理詢問本地目標的行為,REA 使用分析工具檢查和追蹤目標,AI 代理再利用返回的程式碼、引用和未知項進行解釋、實現與測試。](website/public/assets/figures/rea-investigation-flow.svg)
[打開完整尺寸的圖示](website/public/assets/figures/rea-investigation-flow.svg)。
<a id="current-status"></a>
## 可以分析什麼
REA 需要 Node.js 22.x(>=22.19)、24.x(>=24.11)或 26+,以及 npm。額外工具和主機支援取決於分析目標:
| 目標 | REA 返回的內容 | 要求與指南 |
| --------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| 原生二進位檔案 | 偽程式碼、組合語言、字串、符號、呼叫和引用 | Hopper、Ghidra 或 IDA;[原生分析](https://rea.tools/guides/native/) |
| 離線 ELF 佈局 | 節、段、原始符號/重定位資訊及靜態防護機制候選項 | Linux x64 上由呼叫方提供的 pwntools;[二進位診斷](docs/binary-diagnostics.md) |
| EVM 位元組碼 | 分派選擇器、位元組偏移、推斷的參數和狀態可變性 | 本地原始位元組/十六進位輸入載體;[離線 EVM 指南](docs/evm-bytecode.md) |
| 已記錄的 Linux 崩潰 | 原始 note 記錄、每個已記錄執行緒的暫存器/訊號及可選的對映候選項 | 呼叫方提供的 pwntools;可選的 GDB/pwndbg;[已記錄崩潰](docs/recorded-crashes.md) |
| JavaScript / Electron | 模組、匯入、source map、路由、IPC 和原生附加元件關係 | Node.js 和 npm;[應用分析](https://rea.tools/guides/javascript/) |
| 網站 | 頁面結構、腳本、網路觀察結果和按請求獲取的螢幕擷取畫面 | Chrome 系瀏覽器;[瀏覽器分析](https://rea.tools/guides/browser/) |
| 已保存的網路擷取 | 請求、回應、可訪問的酬載和來源位置 | HAR;原生 mitmproxy 擷取需要 Linux 上的 mitmdump;[擷取指南](docs/web-network-captures.md) |
| .NET 程式集 | 中繼資料、CIL 指令、聲明的原生依賴和建置對比 | 靜態檢查;[托管程式碼指南](docs/managed-code-analysis.md) |
| Android APK | 清單聲明、類別、反編譯的方法和引用 | Linux/macOS 上的無界面 JADX 和完整 JDK;[Android 指南](docs/android-analysis.md) |
| 韌體 | 區域、提取結果和轉交原生分析的內容 | Linux 上的 Binwalk / Unblob;[韌體指南](docs/firmware-analysis.md) |
| 軟體包與資源 | 檔案清單、摘要、plist、Apple bundle 結構和提取的資源 | [製品與 JavaScript 指南](docs/javascript-artifact-reconstruction.md)、[Apple 應用](docs/apple-application-analysis.md) |
| 處理程序行為 | 終端輸出、互動、退出和檔案系統觀察結果,以及執行對比 | 支援原生 PTY 的 Linux/macOS;[處理程序擷取](docs/process-capture.md) |
靜態 JavaScript 和 .NET 檢查讀取提供的檔案,不執行應用。執行階段擷取會以你的使用者權限執行目標或與之互動;各執行階段指南說明瞭具體影響。
<a id="choosing-a-deep-analysis-provider"></a>
不同提供者支援的原生格式和主機系統有所不同。請參閱 [Hopper 與 Ghidra 設定](docs/installation.md#hopper)、[IDA 指南](docs/ida-provider.md)和[實驗性 Windows Ghidra 支援](docs/windows-ghidra-p0.md)。Ghidra 也支援 [16 位 DOS 分析](docs/ghidra-dos.md)。提供者選擇見 [CLI 指南](docs/cli.md#choose-a-provider)。對於最新 npm 版本發佈後新增的功能,請查看[發佈可用性](docs/installation.md#released-package-and-main)。
## 案例
### DX-Ball:重建聲像計算
沿聲音呼叫追蹤到根據位置計算聲像的輔助函數,檢查指令,將不完整的偽程式碼轉為 C。重建結果通過了 3,205 個原始 x86 測試用例,並復現了編譯後函數的全部 63 個位元組。
[閱讀案例](https://rea.tools/showcase/dx-ball/) ·
[重建專案儲存庫](https://github.com/N0zoM1z0/dx-ball)
### Notion:追蹤 Electron 剪貼簿橋
找到渲染處理程序的剪貼簿 API,沿 preload 和 IPC 追蹤到主處理程序,並檢查富格式剪貼簿資料。
[閱讀案例](https://rea.tools/showcase/notion/)
### TH04:恢復 DOS 環形子彈的計算
檢查原始 PC-98 遊戲的 16 位指令,恢復固定角度和瞄準角度的計算,並將重建的 C++ 與當年的編譯器輸出進行對比。
[閱讀案例](https://rea.tools/showcase/th04/) ·
[重建專案儲存庫](https://github.com/N0zoM1z0/th04)
如果你用 REA 分析了有趣的目標,我們很想瞭解。歡迎通過 [issue](https://github.com/morluto/rea/issues) 或 [pull request](https://github.com/morluto/rea/pulls) 分享案例,說明目標、你的問題、REA 如何提供幫助,以及你的發現。
## 常見問題
<details>
<summary><strong>哪些 AI 代理可以使用 REA?</strong></summary>
任何支援本地 MCP 服務的 AI 代理都可以使用。設定流程會設定[受支援的 AI 代理](docs/installation.md#supported-agents);其他客戶端可通過[手動註冊 MCP](docs/installation.md#mcp-registry) 使用。
</details>
<details>
<summary><strong>需要 Hopper、Ghidra 或 IDA 嗎?</strong></summary>
深入的原生分析需要其中一個引擎。靜態 JavaScript 和 .NET 檢查不需要原生分析引擎。設定流程可以在你核准後安裝 Hopper;Ghidra 和 IDA 則使用已有安裝。見[提供者設定](docs/installation.md#hopper)。
</details>
<details>
<summary><strong>需要先啟動 Hopper 嗎?</strong></summary>
REA 會在操作需要時啟動 Hopper。在 macOS 上,首次執行時可能會出現對話框,讓你選擇演示模式或啟用授權。見 [Hopper 啟動與故障排查](docs/installation.md#launcher-paths-and-troubleshooting)。
</details>
<details>
<summary><strong>從 skills.sh 安裝 skill 有什麼作用?</strong></summary>
skill 為 AI 代理提供調查指引。使用 `rea setup` 註冊 REA 的 MCP 服務並安裝對應的指引,然後重新啟動 AI 代理。見[僅安裝 skill](docs/installation.md#skill-only-installation)。
</details>
<details>
<summary><strong>REA 返回什麼程式碼?</strong></summary>
原生分析返回偽程式碼和組合語言。JavaScript/Electron 分析恢復模組及其關係。AI 代理利用這些發現編寫並測試實現;[案例](#案例)提供了完整示例。
</details>
<details>
<summary><strong>REA 會上傳我的應用嗎?</strong></summary>
REA 在本機分析目標。AI 代理會接收工具結果,其模型提供商有自己的資料政策。
</details>
<details>
<summary><strong>遇到錯誤時該怎麼辦?</strong></summary>
先更新;最近的版本可能已經修復了問題。
通過 npm 安裝的 CLI 使用:
```bash
rea update
```
通過 `npx` 設定 AI 代理時使用:
```bash
npx rea-agents@latest setup
```
如果正在使用 AI 代理,請完成[設定更新](#更新-rea)並重新啟動 AI 代理。重試同一任務。如果問題仍然存在,請[提交 issue](https://github.com/morluto/rea/issues),附上 REA 版本、目標類型、復現步驟和錯誤輸出。
</details>
## 文件
可以先閱讀網站上的[實操指南](https://rea.tools/guides/)。具體選項、前提條件和結果契約見:
- [安裝與設定](docs/installation.md):AI 代理註冊、提供者組態、更新和卸載。
- [就緒檢查與故障排查](docs/installation.md#check-readiness-for-your-task):診斷特定 AI 代理或分析引擎。
- [CLI 與 Evidence](docs/cli.md):命令、提供者選擇、快照、匯入/匯出和退出狀態。
- [MCP 契約](docs/mcp-contracts.md)和[AI 代理提示詞](docs/mcp-prompts.md):工具結果、會話和引導式調查。
- [工具目錄](docs/mcp-contracts.md#generated-catalog):建置時生成的工具、提供者和 CLI 命令清單。
- [路線圖](docs/roadmap.md):計劃中的工作和能力跟蹤。
請按照 [SECURITY.md](SECURITY.md) 報告漏洞。
## 參與貢獻
歡迎幫助改進 REA!你可以[提交 issue](https://github.com/morluto/rea/issues) 報告錯誤或建議功能,也可以[提交 pull request](https://github.com/morluto/rea/pulls) 改進程式碼或文件。
開發設定和檢查見 [CONTRIBUTING.md](CONTRIBUTING.md),驗證流程見[測試指南](docs/testing.md),專案結構見[架構圖](docs/architecture.mermaid)。
## 專案連結
[網站](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [安全](SECURITY.md)
## Star 歷史
🎉 **GitHub Star 達到 30,000 個,感謝大家!**
感謝每一位使用 REA、報告錯誤、提出功能需求、測試建置和貢獻修復的朋友。
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="REA 的 GitHub Star 歷史" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
</picture>
</a>
## 免責聲明
REA 為合法的逆向工程研究、分析和重建提供工具。你有責任取得所需授權並遵守適用法律。本專案不認同非法或未經授權的使用。
## 授權條款
[MIT](LICENSE)
+183 -231
View File
@@ -1,27 +1,36 @@
<div align="center">
[English](README.md) · **简体中文** · [日本語](README_ja.md) · [한국어](README_ko.md) · [العربية](README_ar.md)
[English](README.md) · **简体中文** · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md)
# REA:逆向工程一切
# REA:逆向分析一切
### 使用智能体逆向工程任何程序,从应用行为到原生二进制文件。
### 通过一个 MCP 服务,逆向分析二进制文件、应用程序和运行时行为。
**看到喜欢的功能。理解它的原理。按照你的方式实现。**
**看到喜欢的功能,弄清它的工作原理,深入到二进制层面。**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](#调查工具目录)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
[![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE)
[![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM)
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
**[网站(英文)](https://morluto.github.io/rea/) · [使用指南](https://morluto.github.io/rea/guides/) · [DX-Ball 案例](https://morluto.github.io/rea/showcase/dx-ball/)**
**[网站](https://rea.tools/) · [指南](https://rea.tools/guides/) · [案例](https://rea.tools/showcase/)**
[快速开始](#快速开始) · [当前状态](#当前状态) · [从二进制到行为](#从二进制到行为) · [调查工具目录](#调查工具目录) · [路线图](#路线图) · [工作原理](#工作原理)
[快速开始](#快速开始) · [REA 的工作原理](#rea-的工作原理) · [可以分析什么](#可以分析什么) · [案例](#案例) · [常见问题](#常见问题) · [文档](#文档)
<table aria-label="REA community">
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA 在 Hopper 中启动分析桥,检查原生二进制文件" width="1200" />
<br />
<table aria-label="REA 社区">
<tr>
<td align="center" width="360">
<a href="https://discord.gg/GkcryMnJDM">
@@ -35,301 +44,244 @@
<br />
<code>npx rea-agents setup</code>
<br />
<img src="docs/assets/rea-hopper-analysis.png" alt="REA 在 Hopper 中启动分析桥并检查原生二进制文件" width="1200" />
</div>
---
看到某个应用中想加入自己产品的功能?让智能体用 REA 调查它。即使没有源代码,智能体也可以检查应用、解释功能的工作方式、展示证据,并为你的项目实现类似功能。
在应用中看到一个想加入自己产品的功能?让智能体用 REA 调查它。即使没有源代码,智能体也可以检查应用、解释功能的工作方式、展示证据,并为你的项目实现类似功能。
REA 提供原生二进制、JavaScript 与 Electron 应用、.NET 程序集和网站的分析工具。你可以从智能体或终端使用它们。分析在本机运行,结果会说明证据和限制。
REA 将智能体连接到分析工具,用于检查原生二进制文件、JavaScript 和 Electron 应用、.NET 程序集以及网站。你也可以在终端中使用同样的工具。分析在本机运行,结果包含支撑各项结论的证据和相关限制。
Setup 可以配置智能体、连接已安装的 Hopper 或 Ghidra,也可以在你批准后安装 Hopper。
设置流程会向智能体注册 REA,并安装匹配的工作流指引。原生分析可以使用已有的 Hopper 或 Ghidra;设置流程也可以在你批准后安装 Hopper。静态 JavaScript 分析不需要这两个引擎。
## 直接询问智能体
完成[设置](#快速开始)后,重启智能体并提出问题:
```text
解释“备忘录”应用的搜索功能,展示证据,然后为我的项目实现类似功能。
```
把“备忘录”替换成你想了解的应用,也可以先请求概览。
## 从二进制到行为
| 反编译 | 理解 | 重建 |
| ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| 打开原生应用或可执行文件,恢复过程、伪代码、汇编、字符串、符号、段和元数据。 | 沿调用者、被调用者、交叉引用和调用图追踪,直到智能体能够解释功能或算法的实际工作方式。 | 将智能体学到的内容变成适合你的技术栈、界面和需求的产品功能。 |
REA 让调查始终以二进制证据为依据。它不会声称能恢复原始源代码,也不会自动克隆整个应用。
## 为什么选择 REA
| | |
| ---------------- | -------------------------------------------------------------------- |
| **为智能体设计** | 直接询问应用的行为,让智能体去检查,而不是猜测。 |
| **CLI 与 MCP** | 在终端或智能体中使用同一套逆向工程能力。 |
| **引导式设置** | 配置智能体、连接已有分析工具,或在你批准后安装 Hopper。 |
| **从洞察到代码** | 理解功能后,在同一次编程会话中构建属于你自己的版本。 |
| **本地运行** | 分析在受支持的本机系统上运行;REA 不会把二进制上传到托管式分析服务。 |
| **保留上下文** | 连续调查多个应用,无需为每个问题重新开始整个分析过程。 |
> **[访问 REA 网站](https://rea.tools/)**,查看设置说明、图解指南和真实案例。
## 快速开始
### 运行设置(推荐)
### 设置智能体
配置 REA 与智能体的连接:
安装 Node.js 和 npm 后,运行:
```bash
npx rea-agents setup
```
Setup 首先让你多选要连接的智能体。已有 REA 注册默认选中;仅被检测到的客户端不会自动选中,也可以手动选择尚未配置的客户端。查看具体路径和变更后再批准。所选智能体默认安装 REA 工作流;Hopper 是单独的可选操作,需要单独批准。Setup 也可以记录现有 Ghidra 的路径。
选择智能体,检查计划中的变更并批准。设置流程会添加 REA 的 MCP 服务和匹配的工作流指引,并备份已有配置。完成后重启智能体。
Setup 会先展示变更并备份已有配置。要求与更多选项见[安装与设置](docs/installation.md)。
设置流程支持 Claude Code、Codex、Cursor、Gemini CLI、Grok Build 和[其他智能体](docs/installation.md#supported-agents)。提供方配置和手动注册 MCP 的方法见[安装与设置](docs/installation.md)。
### 使用智能体
### 询问智能体
设置完成后,重启智能体并描述你想了解的应用或功能。REA 支持 Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、Command Code 和 VS Code。已有 REA 注册默认选中;其他检测到的客户端需手动选择。其他智能体可以使用下方的 MCP 配置。
Hopper 支持演示模式。如果首次启动时出现提示,选择演示模式或输入已有许可证。
### AI 编程助手技能(可选)
为你的 AI 编程助手添加 skill,以获得更丰富的上下文:
```bash
npx skills add morluto/rea --skill reverse-engineer-anything
```text
弄清 Notes 应用的搜索功能如何工作,展示证据,并为我的项目实现类似功能。
```
该 skill 提供 REA 的调查工作流。先运行上文的 setup,把 REA 连接到你的智能体并配置分析工具。Setup 默认会安装版本匹配的 skill;这条命令安装的是仓库版本。
把 Notes 替换为目标应用,并描述你想了解的功能。
对于已解包的 JavaScript/Electron 应用目录或 ASAR,无需 MCP 配置,也无需原生引擎:
### 使用终端
检查已解包的 JavaScript/Electron 应用目录或 ASAR 文件:
```bash
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
```
把路径换成你的目标(例如 Windows 上的 `"D:/apps/example"`)。这条静态流程支持此类路径。原生分析仍需要先配置对应的分析引擎。
结果包含模块、导入、Electron 边界及相关证据。将路径替换为你的目标路径,例如 Windows 上的 `"D:/apps/example"`。
### 安装 rea 命令
安装命令行工具:
```bash
curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh | bash
```
需要预先安装 Node.js 和 npm。在终端中运行时,安装程序会安装 `rea` 并启动设置。
也可以用 npm 安装,再运行设置:
```bash
npm install --global rea-agents
rea setup
```
### 系统要求
- macOS 12 或更高版本
- Ubuntu 24.04+、Fedora 41+、64 位 Arch Linux 或 CachyOS
- Node.js 22.x (>=22.19)、24.x (>=24.11) 或 26+,以及 npm
原生二进制分析需要 Hopper 或 Ghidra。Hopper 是独立软件;演示模式有厂商规定的限制,不要求购买许可证。
Ghidra 支持 Linux x64 和 macOS x64/arm64。单独安装 Ghidra 12.1.x,以及该安装在 `application.java.min` 到 `application.java.max` 中声明的完整 64 位 JDK,然后配置 REA 使用它们。当前 12.1 发行版要求 JDK 21 或更新版本,并且不设置上限。桥接在 Ghidra 12.1.4 和 JDK 21 上完成验证。macOS 还需要对应架构的原生反编译器。
Setup 可以验证安装并保存路径,不会安装或升级 Ghidra、Java、Node.js、npm 或 Homebrew。
仓库 main 分支与 npm 4.1.0 已包含实验性的 Windows x64 Ghidra P0 支持,适用于本地 NTFS 上的原生 x86-64 PE 应用(非托管、非 DLL),并附带 Job Object、私有 DACL 与路径准入控制。请先确认[发布边界](docs/installation.md#released-package-and-main),再假定旧版 npm 包具备该能力。详见 [Windows Ghidra P0](docs/windows-ghidra-p0.md) 的前置条件与已验证范围。
### 故障排除
运行 `npx -y rea-agents@latest doctor`,检查主机、依赖、分析工具和智能体配置。该命令不会修改文件。添加 `--json` 可获取结构化诊断。
Linux 上 REA 会优先使用可执行的 `/opt/hopper/bin/Hopper`;如果不可用,则自动检查 `~/.local/share/rea/hopper/bin/Hopper`。其他路径可通过 `HOPPER_LAUNCHER_PATH` 指定。如果文件存在但仍报告缺少分析引擎,请对实际 Hopper 路径运行 `ldd /absolute/path/to/Hopper | grep 'not found'` 检查缺少的系统库。安装详情见 [Hopper 指南](docs/installation.md#hopper)。
### 更新与卸载
- `rea update` 更新当前 REA 安装。
- `rea uninstall` 移除 REA 管理的智能体配置项和工作流文件,保留 Hopper。
- `rea uninstall --purge-data` 还会删除 REA 的缓存和状态。仅在需要移除这些数据时使用。
## 当前状态
仓库当前能力与平台要求见[英文支持指南](README.md#current-status)。main 分支可能领先于 [npm 发布版](docs/installation.md#released-package-and-main)。
- Ghidra 在 Linux x64、macOS x64/arm64 以及实验性的 Windows x64 P0 边界上提供 25 个只读操作。Linux/macOS 额外支持原子化的会话级函数注释。Windows P0 为只读;Ghidra 不提供 GUI 控制。
- 静态 Android 检查需要自备 JADX/Java,前置条件独立;真机验证覆盖 Linux。详见 [Android 分析](docs/android-analysis.md)。
- 浏览器、Electron 与进程请求需直接声明目标、动作与生命周期,并依赖宿主的实际访问权限,无需 REA 另行授权。Setup 的配置写入与 Hopper 安装仍需逐项批准确切计划。
- `rea capabilities` 描述的是二进制会话操作,而非全部应用/运行时工具。请以已连接 MCP 工具列表与相关指南为准。
## 一个提示词,完成一次完整调查
```text
逆向工程“备忘录”应用,找到离线搜索功能的工作方式,解释其控制流,
并使用 TypeScript 和 SQLite 为我的项目构建一个版本。
```
| 步骤 | 智能体的操作 | REA 工具 |
| ---: | ---------------------- | ---------------------------------------------------------------- |
| 1 | 打开并识别二进制文件 | `open_binary`, `binary_overview` |
| 2 | 搜索可能的离线搜索线索 | `search_strings`, `search_procedures`, `list_names` |
| 3 | 将线索连接到可执行代码 | `find_xrefs_to_name`, `xrefs`, `procedure_callers` |
| 4 | 重建相关控制流 | `get_call_graph`, `procedure_callees`, `procedure_info` |
| 5 | 反编译相关程序 | `procedure_pseudo_code`, `procedure_assembly`, `batch_decompile` |
| 6 | 在你的项目中构建该功能 | 适合你的技术栈、产品和需求的代码 |
REA 负责第 1–5 步中的二进制分析。第 6 步由智能体使用其常规文件编辑与测试工具完成。
## 智能体可以完成什么
- 在没有源代码时解释某项功能的实现方式。
- 重建应用的身份验证、存储、更新或网络流程。
- 恢复足够的结构,以记录未公开的格式或接口。
- 从字符串或符号追踪到实现可疑行为的代码。
- 在一个会话中切换两个应用版本并比较实现路径。
- 调查你喜欢的功能,并为自己的产品构建量身定制的版本。
- 将恢复的行为转换为产品功能、测试、迁移说明、移植代码或互操作替代品。
- 分析 Swift 和 Objective-C 元数据。
- 在 Hopper 中留下名称、注释与书签,使人与智能体的分析互相增强。
## 调查工具目录
| 工具类别 | 数量 | 用途 |
| ----------------- | ---: | -------------------------------------------------------------------------------------------------------------------- |
| 原生检查 | 41 | 函数、伪代码、汇编、字符串、符号、调用、引用、注释、字节读取与文件偏移 |
| 调查工作流 | 14 | 应用概览、函数档案、原生 API 与分发、批量反编译、功能追踪、调用路径与调用图、Swift 与 Objective-C 发现 |
| 原生二进制工具 | 10 | macOS 的 Mach-O 元数据、签名、plist、架构、Swift 符号与 LLDB 调用观察;Linux 的 ELF 布局、静态保护证据与历史崩溃记录 |
| 产物图 | 7 | 目录与软件包清单、编译后的 Interface Builder 文件、Apple 资源目录、Mach-O dylib 加载解析、提取与离线 EVM 接口推断 |
| 托管 PE/CLI | 7 | .NET 程序集身份、元数据、CIL 指令、原生依赖声明、重建导入与构建比较 |
| 固件 | 2 | Linux 固件区域检查与显式提取 |
| Android APK | 5 | 包与 manifest 声明、类搜索、成员清单、方法反编译与静态引用 |
| 浏览器观察 | 12 | 页面结构、网络元数据、脚本、来源映射、WebMCP 发现、截图与捕获比较 |
| Electron 分析 | 5 | 渲染进程观察、静态应用映射、静态/运行时结果关联 |
| JavaScript 运行时 | 2 | Node/Electron Inspector 目标发现、脚本位置与执行上下文事件 |
| 应用工作流 | 13 | 捕获的网站脚本导出、跨层功能追踪、构建比较、历史源码映射、静态返回结构比较与重建验证 |
| 工作区与观察 | 21 | 会话、证据包、导航上下文、进程/产物/函数比较与待解决问题跟踪 |
## 路线图
接下来的工作包括更多原生目标验证、JavaScript 与 .NET 跨版本比较,以及运行时观察和重建验证。当前设置已支持选择智能体集成和 Hopper 安装;安装其他分析工具仍是后续工作。详见[安装路线图](docs/roadmap.md)和[分析工具评估](docs/provider-evaluation.md)。
## 与其他编程智能体一起使用
Setup 支持 Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、Command Code 和 VS Code。已有 REA 注册默认选中;其他检测到的客户端需手动选择。任何支持本地 MCP 服务器的智能体都可以使用以下配置连接 REA。
<!-- x-release-please-start-version -->
```json
{
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents@6.0.0", "mcp"]
}
}
}
```
<!-- x-release-please-end -->
## 工作原理
```mermaid
flowchart LR
Agent["编程智能体"] --> REA["REA<br/>CLI + MCP"]
Terminal["终端"] --> REA
REA --> Hopper["Hopper 分析操作"]
Hopper --> App["你的应用"]
REA --> Ghidra["Ghidra 只读分析<br/>清单 + 函数分析"]
Ghidra --> App
```
CLI 与 MCP 服务器使用相同的分析流程。终端命令完成后释放自己创建的桥接会话;智能体会话可以在调查期间保持连接。关闭 REA 会话不会退出用户正在使用的 Hopper 应用。
## CLI
上面的智能体工作流是使用 REA 最简单的方式。如果只想在终端中快速了解一个应用:
```bash
npx -y rea-agents@latest analyze /Applications/Notes.app
```
运行 `npx -y rea-agents@latest --help` 查看直接反编译和其他选项。
也可以全局安装 `rea` 命令:
如果需要经常使用,可以安装 `rea` 命令:
```bash
npm install --global rea-agents
rea --help
rea mcp
```
REA 可以直接打开 Mac 的 `.app` 文件夹。如果智能体找不到应用,请告诉它应用安装在哪里。
原生分析需要先配置分析提供方。原生命令、提供方选择、快照和脚本用法见 [CLI 与 Evidence 指南](docs/cli.md)。
## Hopper 应用行为
### 更新 REA
REA 会在需要时启动 Hopper,无需预先运行。Hopper 启动器内部会激活应用,因此打开目标时 Hopper 可能出现在其他窗口前。REA 会请求 macOS 在后台启动 Hopper,但无法保证窗口始终位于后台。
REA 更新较快,新版本经常包含错误修复。请保持安装的版本为最新。
REA 会推导明确的格式和架构参数,以避免常见的 FAT 与 ARM 选择对话框。其他 Hopper 或 macOS 对话框仍可能需要人工响应。关闭 REA 会话会终止桥并删除私有套接字目录,但不会退出用户正在使用的 Hopper 应用。
通过 npm 安装的 CLI 使用:
## 安全模型
```bash
rea update
```
每个桥会话都使用随机能力令牌和仅限当前用户的 Unix 套接字。诊断保留本机路径、摘要和错误位置等排查信息,同时移除凭据和认证令牌。Ghidra 会话还使用隔离的临时项目,不会打开或修改用户自己的 Ghidra 项目。
要更新智能体注册和 skill,请运行更新命令输出的设置命令。
这不是沙箱,也无法防御以同一操作系统用户身份运行的恶意进程。打开不可信二进制文件会让所选本地提供商以当前用户权限进行解析和分析。请按照 [SECURITY.md](SECURITY.md) 中的私密流程报告漏洞。
如果使用 `npx`,通过以下命令更新智能体设置:
```bash
npx rea-agents@latest setup
```
检查设置变更并重启智能体。单次 CLI 操作可使用 `npx rea-agents@latest`,后接所需命令。
## REA 的工作原理
智能体通过 MCP 调用 REA,检查目标并追踪相关代码。REA 返回发现及对应证据。智能体据此提出后续问题、解释行为,或编写并测试实现。CLI 命令使用同样的工作流。
![REA 调查流程:智能体询问本地目标的行为,REA 使用分析工具检查和追踪目标,智能体再利用返回的代码、引用和未知项进行解释、实现与测试。](website/public/assets/figures/rea-investigation-flow.svg)
[打开完整尺寸的图示](website/public/assets/figures/rea-investigation-flow.svg)。
<a id="current-status"></a>
## 可以分析什么
REA 需要 Node.js 22.x(>=22.19)、24.x(>=24.11)或 26+,以及 npm。额外工具和主机支持取决于分析目标:
| 目标 | REA 返回的内容 | 要求与指南 |
| --------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| 原生二进制文件 | 伪代码、汇编、字符串、符号、调用和引用 | Hopper、Ghidra 或 IDA;[原生分析](https://rea.tools/guides/native/) |
| 离线 ELF 布局 | 节、段、原始符号/重定位信息及静态防护机制候选项 | Linux x64 上由调用方提供的 pwntools;[二进制诊断](docs/binary-diagnostics.md) |
| EVM 字节码 | 分派选择器、字节偏移、推断的参数和状态可变性 | 本地原始字节/十六进制输入载体;[离线 EVM 指南](docs/evm-bytecode.md) |
| 已记录的 Linux 崩溃 | 原始 note 记录、每个已记录线程的寄存器/信号及可选的映射候选项 | 调用方提供的 pwntools;可选的 GDB/pwndbg;[已记录崩溃](docs/recorded-crashes.md) |
| JavaScript / Electron | 模块、导入、source map、路由、IPC 和原生扩展关系 | Node.js 和 npm;[应用分析](https://rea.tools/guides/javascript/) |
| 网站 | 页面结构、脚本、网络观察结果和按请求获取的截图 | Chrome 系浏览器;[浏览器分析](https://rea.tools/guides/browser/) |
| 已保存的网络捕获 | 请求、响应、可访问的载荷和来源位置 | HAR;原生 mitmproxy 捕获需要 Linux 上的 mitmdump;[捕获指南](docs/web-network-captures.md) |
| .NET 程序集 | 元数据、CIL 指令、声明的原生依赖和构建对比 | 静态检查;[托管代码指南](docs/managed-code-analysis.md) |
| Android APK | 清单声明、类、反编译的方法和引用 | Linux/macOS 上的无界面 JADX 和完整 JDK;[Android 指南](docs/android-analysis.md) |
| 固件 | 区域、提取结果和转交原生分析的内容 | Linux 上的 Binwalk / Unblob;[固件指南](docs/firmware-analysis.md) |
| 软件包与资源 | 文件清单、摘要、plist、Apple bundle 结构和提取的资源 | [制品与 JavaScript 指南](docs/javascript-artifact-reconstruction.md)、[Apple 应用](docs/apple-application-analysis.md) |
| 进程行为 | 终端输出、交互、退出和文件系统观察结果,以及运行对比 | 支持原生 PTY 的 Linux/macOS;[进程捕获](docs/process-capture.md) |
静态 JavaScript 和 .NET 检查读取提供的文件,不运行应用。运行时捕获会以你的用户权限运行目标或与之交互;各运行时指南说明了具体影响。
<a id="choosing-a-deep-analysis-provider"></a>
不同提供方支持的原生格式和主机系统有所不同。请参阅 [Hopper 与 Ghidra 设置](docs/installation.md#hopper)、[IDA 指南](docs/ida-provider.md)和[实验性 Windows Ghidra 支持](docs/windows-ghidra-p0.md)。Ghidra 也支持 [16 位 DOS 分析](docs/ghidra-dos.md)。提供方选择见 [CLI 指南](docs/cli.md#choose-a-provider)。对于最新 npm 版本发布后新增的功能,请查看[发布可用性](docs/installation.md#released-package-and-main)。
## 案例
### DX-Ball:重建声像计算
沿声音调用追踪到根据位置计算声像的辅助函数,检查指令,将不完整的伪代码转为 C。重建结果通过了 3,205 个原始 x86 测试用例,并复现了编译后函数的全部 63 个字节。
[阅读案例](https://rea.tools/showcase/dx-ball/) ·
[重建项目仓库](https://github.com/N0zoM1z0/dx-ball)
### Notion:追踪 Electron 剪贴板桥
找到渲染进程的剪贴板 API,沿 preload 和 IPC 追踪到主进程,并检查富格式剪贴板数据。
[阅读案例](https://rea.tools/showcase/notion/)
### TH04:恢复 DOS 环形子弹的计算
检查原始 PC-98 游戏的 16 位指令,恢复固定角度和瞄准角度的计算,并将重建的 C++ 与当年的编译器输出进行对比。
[阅读案例](https://rea.tools/showcase/th04/) ·
[重建项目仓库](https://github.com/N0zoM1z0/th04)
如果你用 REA 分析了有趣的目标,我们很想了解。欢迎通过 [issue](https://github.com/morluto/rea/issues) 或 [pull request](https://github.com/morluto/rea/pulls) 分享案例,说明目标、你的问题、REA 如何提供帮助,以及你的发现。
## 常见问题
<details><summary><strong>Hopper 是否需要提前运行?</strong></summary>
<details>
<summary><strong>哪些智能体可以使用 REA?</strong></summary>
不需要。REA 会在操作需要时启动 Hopper。Hopper 已经运行时也可以使用,但 REA 会打开新的分析文档,不会连接并复用现有 GUI 文档。
任何支持本地 MCP 服务的智能体都可以使用。设置流程会配置[受支持的智能体](docs/installation.md#supported-agents);其他客户端可通过[手动注册 MCP](docs/installation.md#mcp-registry) 使用。
</details>
<details><summary><strong>REA 是否包含 Hopper?</strong></summary>
<details>
<summary><strong>需要 Hopper、Ghidra 或 IDA 吗?</strong></summary>
不包含。Setup 可以为你安装 Hopper,但 Hopper 仍是需要单独授权的软件。REA 提供 CLI、MCP 服务器和面向智能体的工作流。
深入的原生分析需要其中一个引擎。静态 JavaScript 和 .NET 检查不需要原生分析引擎。设置流程可以在你批准后安装 Hopper;Ghidra 和 IDA 则使用已有安装。见[提供方设置](docs/installation.md#hopper)。
</details>
<details><summary><strong>REA 会上传我的二进制文件吗?</strong></summary>
<details>
<summary><strong>需要先启动 Hopper 吗?</strong></summary>
REA 不提供托管分析服务,而是通过本地 Unix 套接字把操作交给 Hopper。你的智能体或模型服务商可能有自己的数据政策,请单独核查。
REA 会在操作需要时启动 Hopper。在 macOS 上,首次运行时可能会出现对话框,让你选择演示模式或激活许可证。见 [Hopper 启动与故障排查](docs/installation.md#launcher-paths-and-troubleshooting)。
</details>
<details><summary><strong>REA 能恢复原始源代码吗?</strong></summary>
<details>
<summary><strong>从 skills.sh 安装 skill 有什么作用?</strong></summary>
不能保证。REA 提供伪代码、汇编、符号、字符串、元数据和关系,智能体可据此解释或兼容地重建观察到的行为。
skill 为智能体提供调查指引。使用 `rea setup` 注册 REA 的 MCP 服务并安装匹配的指引,然后重启智能体。见[仅安装 skill](docs/installation.md#skill-only-installation)。
</details>
## 开发
<details>
<summary><strong>REA 返回什么代码?</strong></summary>
开发环境、架构、测试和发布说明请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。
原生分析返回伪代码和汇编。JavaScript/Electron 分析恢复模块及其关系。智能体利用这些发现编写并测试实现;[案例](#案例)提供了完整示例。
</details>
<details>
<summary><strong>REA 会上传我的应用吗?</strong></summary>
REA 在本机分析目标。智能体会接收工具结果,其模型提供商有自己的数据政策。
</details>
<details>
<summary><strong>遇到错误时该怎么办?</strong></summary>
先更新;最近的版本可能已经修复了问题。
通过 npm 安装的 CLI 使用:
```bash
rea update
```
通过 `npx` 设置智能体时使用:
```bash
npx rea-agents@latest setup
```
如果正在使用智能体,请完成[设置更新](#更新-rea)并重启智能体。重试同一任务。如果问题仍然存在,请[提交 issue](https://github.com/morluto/rea/issues),附上 REA 版本、目标类型、复现步骤和错误输出。
</details>
## 文档
可以先阅读网站上的[实操指南](https://rea.tools/guides/)。具体选项、前提条件和结果契约见:
- [安装与设置](docs/installation.md):智能体注册、提供方配置、更新和卸载。
- [就绪检查与故障排查](docs/installation.md#check-readiness-for-your-task):诊断特定智能体或分析引擎。
- [CLI 与 Evidence](docs/cli.md):命令、提供方选择、快照、导入/导出和退出状态。
- [MCP 契约](docs/mcp-contracts.md)和[智能体提示词](docs/mcp-prompts.md):工具结果、会话和引导式调查。
- [工具目录](docs/mcp-contracts.md#generated-catalog):构建时生成的工具、提供方和 CLI 命令清单。
- [路线图](docs/roadmap.md):计划中的工作和能力跟踪。
请按照 [SECURITY.md](SECURITY.md) 报告漏洞。
## 参与贡献
欢迎帮助改进 REA!你可以[提交 issue](https://github.com/morluto/rea/issues) 报告错误或建议功能,也可以[提交 pull request](https://github.com/morluto/rea/pulls) 改进代码或文档。
开发设置和检查见 [CONTRIBUTING.md](CONTRIBUTING.md),验证流程见[测试指南](docs/testing.md),项目结构见[架构图](docs/architecture.mermaid)。
## 项目链接
[网站](https://rea.tools/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [安全](SECURITY.md)
## Star 历史
🎉 **GitHub Star 突破 20,000,感谢大家!**
🎉 **GitHub Star 达到 30,000 个,感谢大家!**
感谢每一位使用 REA、反馈问题、测试构建和贡献修复的朋友。
感谢每一位使用 REA、报告错误、提出功能需求、测试构建和贡献修复的朋友。
<a href="https://www.star-history.com/?repos=morluto%2Frea&amp;type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date&amp;theme=dark&amp;legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="REA GitHub Star 历史" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
<img alt="REA 的 GitHub Star 历史" src="https://api.star-history.com/chart?repos=morluto/rea&amp;type=date" />
</picture>
</a>
## 免责声明
REA 为合法的逆向工程研究、分析和重建提供工具。你有责任取得所需授权并遵守适用法律。项目不支持非法或未经授权的使用。
## 许可证
[MIT](LICENSE)
+14 -5
View File
@@ -25,6 +25,11 @@ class ReaJadxBridge {
private record MethodSnapshot(String name, String shortId, 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;
InputFailure(String field, String message) { super(message); this.field = field; }
}
private ReaJadxBridge(SessionHolder holder) { this.holder = holder; }
private static CallToolResult text(String value, boolean failed) {
@@ -100,8 +105,9 @@ class ReaJadxBridge {
}
String name = string(args, "class_name");
List<ClassSnapshot> candidates = classes.get(name);
if (candidates == null) throw new IllegalArgumentException("Exact class not found: " + name);
if (candidates.size() != 1) throw new IllegalArgumentException("Ambiguous metadata class identity: " + name
if (candidates == null) throw new InputFailure("class_name", "Exact class not found: " + name
+ "; use search_android_classes to discover fully qualified names.");
if (candidates.size() != 1) throw new InputFailure("class_name", "Ambiguous metadata class identity: " + name
+ "; raw identities: " + candidates.stream().map(candidate -> candidate.node().getRawName()).toList());
return candidates.get(0);
}
@@ -109,7 +115,8 @@ class ReaJadxBridge {
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();
if (result.isEmpty()) throw new IllegalArgumentException("Method not found: " + name);
if (result.isEmpty()) throw new InputFailure("method_name", "Method not found: " + name
+ "; use inspect_android_class to discover available methods.");
return result;
}
@@ -141,7 +148,7 @@ class ReaJadxBridge {
if (name.equals("get_method_body")) {
List<MethodSnapshot> overloads = methods(cls, args);
int index = integer(args, "overload_index", 0);
if (index >= overloads.size()) throw new IllegalArgumentException("overload_index out of range: " + index);
if (index >= overloads.size()) throw new InputFailure("overload_index", "overload_index out of range: " + index);
JavaMethod method = method(cls, overloads.get(index));
JadxSession.SmartCode code = current.getMethodBodySmart(method, current.getMaxSourceBytes(), true);
return json(Map.of("class_name", cls.summary().get("full_name"),
@@ -155,7 +162,7 @@ class ReaJadxBridge {
if (name.equals("get_xrefs_to_class")) uses = cls.node().getUseIn();
else {
List<MethodSnapshot> overloads = methods(cls, args);
if (overloads.size() != 1) throw new IllegalArgumentException("Method references require a unique overload");
if (overloads.size() != 1) throw new InputFailure("method_name", "Method references require a unique overload");
target += "." + overloads.get(0).name();
uses = method(cls, overloads.get(0)).getUseIn();
}
@@ -211,6 +218,8 @@ class ReaJadxBridge {
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); }
});
}
+202 -48
View File
@@ -52,6 +52,8 @@ import ghidra.app.decompiler.ClangCaseToken;
import ghidra.app.decompiler.ClangNode;
import ghidra.framework.Application;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressIterator;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressRangeIterator;
@@ -723,8 +725,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
if (!direction.equals("incoming") && !direction.equals("outgoing")) {
throw new RequestFailure("invalid_request", "direction must be incoming or outgoing");
}
InstructionScan scan = scanInstructions(function, Integer.MAX_VALUE);
List<Reference> references = collectReferences(scan.instructions, direction);
List<Reference> references = collectReferences(function, direction);
JsonArray edges = new JsonArray();
for (Reference reference : references) {
edges.add(referenceEdge(reference));
@@ -735,7 +736,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
result.add("references", edges);
result.addProperty("reference_kinds_available", true);
JsonArray unresolvedCalls = new JsonArray();
if (direction.equals("outgoing")) for (Instruction instruction : scan.instructions) {
if (direction.equals("outgoing")) for (Instruction instruction : scanInstructions(function, Integer.MAX_VALUE).instructions) {
if (!instruction.getFlowType().isCall()) continue;
boolean resolved = false;
for (Reference reference : instruction.getReferencesFrom()) if (reference.getReferenceType().isCall()) resolved = true;
@@ -865,6 +866,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
JsonArray operands = new JsonArray();
JsonArray references = new JsonArray();
JsonArray destinations = new JsonArray();
JsonArray flowEvidence = new JsonArray();
JsonObject flow = new JsonObject();
flow.addProperty("kind", "unavailable");
flow.addProperty("conditional", false);
@@ -880,7 +882,20 @@ public final class ReaGhidraBridge extends HeadlessScript {
result.addProperty("mnemonic", instruction.getMnemonicString());
result.addProperty("raw_disassembly", instruction.toString());
RefType type = instruction.getFlowType();
flow.addProperty("kind", type.isCall() ? "call" : type.isJump() ? "jump" : type.isTerminal() ? "terminal" : "fallthrough");
JsonObject listingFlow = new JsonObject();
listingFlow.addProperty("source", "ghidra-listing-flow-type");
listingFlow.addProperty("value", type.getName());
flowEvidence.add(listingFlow);
boolean decodedReturn = false;
for (PcodeOp operation : instruction.getPcode()) {
if (operation.getOpcode() != PcodeOp.RETURN) continue;
decodedReturn = true;
JsonObject returnEvidence = new JsonObject();
returnEvidence.addProperty("source", "ghidra-instruction-pcode");
returnEvidence.addProperty("value", operation.getMnemonic());
flowEvidence.add(returnEvidence);
}
flow.addProperty("kind", type.isCall() ? "call" : type.isJump() ? "jump" : decodedReturn ? "return" : type.isTerminal() ? "terminal" : "fallthrough");
flow.addProperty("conditional", type.isConditional());
flow.addProperty("computed", type.isComputed());
if (!type.isComputed()) for (Address target : instruction.getFlows()) destinations.add(canonicalAddress(target));
@@ -916,10 +931,13 @@ public final class ReaGhidraBridge extends HeadlessScript {
}
}
flow.add("direct_destinations", destinations);
flow.add("classification_evidence", flowEvidence);
result.add("operands", operands);
result.add("flow", flow);
result.add("references", references);
result.add("limitations", GSON.toJsonTree(List.of("Operand components are Ghidra Listing decoder tokens; effective memory base/index/displacement roles and per-instruction context mode are unavailable. The mode field is the program language variant.", "Terminal flow is not classified as a return without provider evidence.")));
List<String> limitations = new ArrayList<>(List.of("Operand components are Ghidra Listing decoder tokens; effective memory base/index/displacement roles and per-instruction context mode are unavailable. The mode field is the program language variant."));
if (flow.get("kind").getAsString().equals("terminal")) limitations.add("No RETURN operation was observed in the provider's instruction p-code; terminal classification remains generic.");
result.add("limitations", GSON.toJsonTree(limitations));
return result;
}
@@ -1007,6 +1025,11 @@ public final class ReaGhidraBridge extends HeadlessScript {
Address entry = function.getEntryPoint();
if (function.isExternal() || !currentProgram.getMemory().contains(entry))
throw new RequestFailure("invalid_request", "Annotations require a local function entry: " + canonicalAddress(entry));
for (String field : List.of("name", "comment", "inline_comment")) {
if (params.has(field)) validateAnnotationText(requireText(params, field), field);
}
String leafName = params.has("name")
? annotationLeafName(function, requireString(params, "name")) : null;
int transaction = currentProgram.startTransaction("REA function annotations");
boolean commit = false;
try {
@@ -1020,7 +1043,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
}
}
if (params.has("name"))
function.setName(requireString(params, "name"), SourceType.USER_DEFINED);
function.setName(leafName, SourceType.USER_DEFINED);
monitor.checkCancelled();
invalidateAnalysisCaches();
JsonObject readback = annotationReadback(function);
@@ -1028,7 +1051,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
if (!params.has(field)) continue;
String requested = requireText(params, field);
JsonElement measured = readback.get(field);
String expected = !field.equals("name") && requested.isEmpty() ? null : requested;
String expected = field.equals("name") ? leafName : requested.isEmpty() ? null : requested;
String observed = field.equals("name") ? function.getName() :
(measured.isJsonNull() ? null : measured.getAsString());
if (!java.util.Objects.equals(expected, observed))
@@ -1058,6 +1081,22 @@ public final class ReaGhidraBridge extends HeadlessScript {
}
}
private String annotationLeafName(Function function, String requested) {
// Fully qualified readback must be usable as an idempotent rename input.
if (requested.equals(procedureName(function))) return function.getName();
String namespace = function.getParentNamespace().isGlobal()
? "" : function.getParentNamespace().getName(true);
String prefix = namespace + "::";
if (!namespace.isEmpty() && requested.startsWith(prefix)) {
String leaf = requested.substring(prefix.length());
if (!leaf.isEmpty()) return leaf;
throw new RequestFailure("invalid_function_name",
"Function name has an empty leaf within namespace " + namespace + " at " +
canonicalAddress(function.getEntryPoint()) + ": " + requested);
}
return requested;
}
private static String requireText(JsonObject params, String field) {
JsonElement value = params.get(field);
if (value == null || !value.isJsonPrimitive() || !value.getAsJsonPrimitive().isString())
@@ -1065,6 +1104,21 @@ public final class ReaGhidraBridge extends HeadlessScript {
return value.getAsString();
}
private static void validateAnnotationText(String text, String field) {
for (int index = 0; index < text.length(); index++) {
char character = text.charAt(index);
if (character == 0)
throw new RequestFailure("invalid_request", "Annotation " + field + " contains NUL at UTF-16 index " + index + "; Ghidra's decompiler cannot marshal NUL text");
if (Character.isHighSurrogate(character) && index + 1 < text.length() &&
Character.isLowSurrogate(text.charAt(index + 1))) {
index++;
continue;
}
if (Character.isSurrogate(character))
throw new RequestFailure("invalid_request", "Annotation " + field + " contains an unpaired Unicode surrogate at UTF-16 index " + index + "; Ghidra cannot preserve it as annotation text");
}
}
private JsonObject analyzeFunction(JsonObject params) throws Exception {
requireKeys(params, Set.of("procedure"));
Function function = resolveProcedure(requireString(params, "procedure"));
@@ -1074,11 +1128,11 @@ public final class ReaGhidraBridge extends HeadlessScript {
if (pseudocode == null) {
pseudocode = "";
}
List<Reference> incomingReferences = collectReferences(scan.instructions, "incoming")
List<Reference> incomingReferences = collectReferences(function, "incoming")
.stream()
.filter(reference -> !function.getBody().contains(reference.getFromAddress()))
.toList();
List<Reference> outgoingReferences = collectReferences(scan.instructions, "outgoing");
List<Reference> outgoingReferences = collectReferences(function, "outgoing");
JsonArray incoming = referenceEdges(incomingReferences);
JsonArray outgoing = referenceEdges(outgoingReferences);
JsonArray comments = comments(scan.instructions);
@@ -1471,6 +1525,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
: regexMatcher(expression, caseSensitive);
JsonArray items = new JsonArray();
for (InventoryItem item : inventory) {
monitor.checkCancelled();
if (!matcher.matches(item.value)) {
continue;
}
@@ -1581,33 +1636,47 @@ public final class ReaGhidraBridge extends HeadlessScript {
}
private Function resolveProcedure(String value) throws Exception {
Address address = tryParseAddress(value);
if (address != null) {
Function function = currentProgram.getFunctionManager().getFunctionAt(address);
if (function == null && address.isMemoryAddress()) {
function = currentProgram.getFunctionManager().getFunctionContaining(address);
// Explicit address syntax wins; an unprefixed identifier is first an
// exact symbol name, even when every character is a hexadecimal digit.
boolean explicitAddress = isExplicitReaAddress(value);
if (!explicitAddress) {
List<Function> matches = new ArrayList<>();
for (FunctionEntry entry : procedures()) {
monitor.checkCancelled();
Function function = entry.function;
Symbol symbol = function.getSymbol();
String qualified = symbol == null ? function.getName() : symbol.getName(true);
boolean matchesName = function.getName().equals(value) || qualified.equals(value);
if (!matchesName) {
// Imported labels (for example Mach-O _main) can share a
// function entry without becoming its primary symbol. Only
// exact entries qualify; an interior label is not a selector.
for (Symbol entrySymbol : currentProgram.getSymbolTable().getSymbols(function.getEntryPoint())) {
monitor.checkCancelled();
if (entrySymbol.getName().equals(value) || entrySymbol.getName(true).equals(value)) {
matchesName = true;
break;
}
}
}
if (matchesName) matches.add(function);
}
if (function == null) {
throw new RequestFailure("not_found", "No procedure exists at the requested address");
}
return function;
if (matches.size() > 1)
throw new RequestFailure("ambiguous", "Ghidra procedure name is ambiguous: " + value +
"; select an exact entry address from " + matches.stream()
.map(function -> canonicalAddress(function.getEntryPoint())).toList());
if (matches.size() == 1) return matches.get(0);
}
List<Function> matches = new ArrayList<>();
for (FunctionEntry entry : procedures()) {
Function function = entry.function;
Symbol symbol = function.getSymbol();
String qualified = symbol == null ? function.getName() : symbol.getName(true);
if (function.getName().equals(value) || qualified.equals(value)) {
matches.add(function);
}
}
if (matches.isEmpty()) {
throw new RequestFailure("not_found", "Unknown Ghidra procedure name");
}
if (matches.size() != 1) {
throw new RequestFailure("ambiguous", "Ghidra procedure name is ambiguous");
}
return matches.get(0);
// Retain legacy bare-hex address input only when no symbol matches.
Address address = explicitAddress ? parseReaAddress(value) : tryParseAddress(value);
if (address == null)
throw new RequestFailure("not_found", "Unknown Ghidra procedure name or address: " + value);
Function function = currentProgram.getFunctionManager().getFunctionAt(address);
if (function == null && address.isMemoryAddress())
function = currentProgram.getFunctionManager().getFunctionContaining(address);
if (function == null)
throw new RequestFailure("not_found", "No procedure exists at the requested address: " + value);
return function;
}
private JsonObject nativeApiBoundary(
@@ -2530,21 +2599,32 @@ public final class ReaGhidraBridge extends HeadlessScript {
}
private List<Reference> collectReferences(
List<Instruction> instructions,
Function function,
String direction) throws Exception {
// References can target instruction interiors or originate in embedded
// data. Use actual body ownership, including an external's exact entry.
AddressSet scope = new AddressSet(function.getBody());
scope.add(function.getEntryPoint());
boolean outgoing = direction.equals("outgoing");
AddressIterator addresses = outgoing
? currentProgram.getReferenceManager().getReferenceSourceIterator(scope, true)
: currentProgram.getReferenceManager().getReferenceDestinationIterator(scope, true);
TreeMap<String, Reference> observed = new TreeMap<>();
for (Instruction instruction : instructions) {
while (addresses.hasNext()) {
monitor.checkCancelled();
if (direction.equals("outgoing")) {
Address address = addresses.next();
if (outgoing) {
for (Reference reference : currentProgram.getReferenceManager()
.getReferencesFrom(instruction.getAddress())) {
.getReferencesFrom(address)) {
monitor.checkCancelled();
addReference(observed, reference);
}
}
else {
ReferenceIterator iterator = currentProgram.getReferenceManager()
.getReferencesTo(instruction.getAddress());
.getReferencesTo(address);
while (iterator.hasNext()) {
monitor.checkCancelled();
addReference(observed, iterator.next());
}
}
@@ -2944,10 +3024,46 @@ public final class ReaGhidraBridge extends HeadlessScript {
return parseReaAddress(requireString(params, name));
}
private static boolean isExplicitReaAddress(String value) {
if (value.regionMatches(true, 0, "0x", 0, 2) &&
isHexadecimal(value, 2, value.length()))
return true;
int separator = value.toLowerCase(Locale.ROOT).lastIndexOf(":0x");
if (separator <= 0 || !isHexadecimal(value, separator + 3, value.length()))
return false;
// Classify the encoded space iteratively. A repeated regex alternation
// can exhaust Java's stack on a long ordinary symbol name.
for (int index = 0; index < separator; index++) {
char item = value.charAt(index);
if ((item >= 'A' && item <= 'Z') ||
(item >= 'a' && item <= 'z') ||
(item >= '0' && item <= '9') ||
item == '.' || item == '_' || item == '~' || item == '-')
continue;
if (item != '%' || index + 2 >= separator ||
!isHexadecimal(value, index + 1, index + 3))
return false;
index += 2;
}
return true;
}
private static boolean isHexadecimal(String value, int start, int end) {
if (start >= end) return false;
for (int index = start; index < end; index++) {
char item = value.charAt(index);
if (!((item >= '0' && item <= '9') ||
(item >= 'a' && item <= 'f') ||
(item >= 'A' && item <= 'F')))
return false;
}
return true;
}
private Address parseReaAddress(String value) {
Address address = tryParseAddress(value);
if (address == null) {
throw new RequestFailure("invalid_request", "Unknown or invalid Ghidra address");
throw new RequestFailure("invalid_request", "Unknown or invalid Ghidra address: " + value);
}
return address;
}
@@ -2956,22 +3072,31 @@ public final class ReaGhidraBridge extends HeadlessScript {
try {
String spaceName = null;
String offset = value;
int separator = value.lastIndexOf(":0x");
int separator = value.toLowerCase(Locale.ROOT).lastIndexOf(":0x");
if (separator >= 0) {
spaceName = decodeAddressSpace(value.substring(0, separator));
offset = value.substring(separator + 3);
}
else if (value.startsWith("0x")) {
else if (value.startsWith("0x") || value.startsWith("0X")) {
offset = value.substring(2);
}
if (!offset.matches("[0-9A-Fa-f]+")) {
if (!isHexadecimal(offset, 0, offset.length())) {
return null;
}
AddressSpace space = spaceName == null
? currentProgram.getAddressFactory().getDefaultAddressSpace()
: currentProgram.getAddressFactory().getAddressSpace(spaceName);
return space == null ? null : space.getAddress(offset);
if (space == null) return null;
Address address = space.getAddress(offset);
BigInteger requestedOffset = new BigInteger(offset, 16);
BigInteger parsedOffset = new BigInteger(Long.toUnsignedString(address.getOffset()));
if (!requestedOffset.equals(parsedOffset))
throw new RequestFailure("invalid_request",
"Ghidra address offset cannot be represented without truncation in space " +
space.getName() + ": requested " + value + ", parsed " + canonicalAddress(address));
return address;
}
catch (RequestFailure failure) { throw failure; }
catch (Exception exception) {
return null;
}
@@ -3055,9 +3180,29 @@ public final class ReaGhidraBridge extends HeadlessScript {
);
}
catch (PatternSyntaxException exception) {
throw new RequestFailure("invalid_request", "Invalid regex pattern");
// Java wraps compiler stack exhaustion in PatternSyntaxException
// without retaining its cause. This producer reason is not bad syntax.
if (exception.getDescription().equals("Stack overflow during pattern compilation"))
throw regexStackExhausted(expression, "compiling");
throw new RequestFailure("invalid_request", "Invalid regex pattern " + expression + ": " + exception.getDescription() + " at index " + exception.getIndex());
}
return value -> pattern.matcher(value).find();
catch (StackOverflowError exhausted) {
throw regexStackExhausted(expression, "compiling");
}
return value -> {
try {
return pattern.matcher(value).find();
}
catch (StackOverflowError exhausted) {
throw regexStackExhausted(expression, "matching");
}
};
}
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 + ". Use literal mode or simplify the regex; the session remains usable.");
}
private static SessionDescriptor readDescriptor(Path path) throws IOException {
@@ -3114,7 +3259,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
private void requireDocument(JsonObject params) {
String document = optionalString(params, "document");
if (document != null && !document.equals(currentProgram.getName())) {
throw new RequestFailure("not_found", "Unknown Ghidra Program identity");
throw new RequestFailure("not_found", "Unknown Ghidra Program identity: " + document + "; active program: " + currentProgram.getName());
}
}
@@ -3226,7 +3371,16 @@ public final class ReaGhidraBridge extends HeadlessScript {
private static void writeResponse(BufferedWriter writer, JsonObject response) throws IOException {
String encoded = GSON.toJson(response);
writer.write(encoded);
// Gson leaves UTF-16 surrogates literal. Escape them in JSON so even
// diagnostics quoting ill-formed caller text remain lossless UTF-8.
int start = 0;
for (int index = 0; index < encoded.length(); index++) {
if (!Character.isSurrogate(encoded.charAt(index))) continue;
writer.write(encoded, start, index - start);
writer.write("\\u" + Integer.toHexString(encoded.charAt(index)));
start = index + 1;
}
writer.write(encoded, start, encoded.length() - start);
writer.newLine();
writer.flush();
}
+315 -128
View File
@@ -8,12 +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)
_selected_document = None
# The native label setter truncates NSString names at this UTF-16 extent.
HOPPER_SYMBOL_NAME_UTF16_UNITS = 1024
_rea_session_document = None
_search_inventory_cache = {}
_pseudocode_cache = {}
@@ -89,9 +89,11 @@ def _session_document():
global _rea_session_document
documents = _api().documents()
if _rea_session_document is not None:
# Hopper recreates Python wrappers; equality compares native handles.
# Retain our bound wrapper so cache identity stays stable across calls.
return (
_rea_session_document
if any(document is _rea_session_document for document in documents)
if any(document == _rea_session_document for document in documents)
else None
)
return _bind_session_document(documents)
@@ -174,46 +176,89 @@ def _json_safe(value):
def _document(name=None):
"""Resolve an explicit or session-selected document without changing Hopper UI."""
global _selected_document
api = _api()
documents = api.documents()
if name is not None:
matches = [candidate for candidate in documents if candidate.getDocumentName() == name]
if len(matches) == 1:
return matches[0]
if len(matches) > 1:
current = api.current_document()
if current is not None and current.getDocumentName() == name:
return current
raise InvalidRequestError("Hopper document name is ambiguous")
raise InvalidRequestError("Unknown Hopper document")
if _selected_document is not None:
for candidate in documents:
if candidate.getDocumentName() == _selected_document:
return candidate
"""Keep observations and mutations bound to the active REA target."""
documents = _api().documents()
session_document = _session_document()
if session_document is not None:
return session_document
current = api.current_document()
if current is None:
raise CapabilityUnavailableError("No Hopper document is loaded")
return current
if session_document is None:
raise CapabilityUnavailableError("The active REA target's Hopper document is no longer open; reopen the target with open_binary")
if name is not None:
# A document name is display metadata; scope it to the bound native
# handle even when another open target happens to have the same name.
if session_document.getDocumentName() == name:
return session_document
if any(candidate.getDocumentName() == name for candidate in documents):
raise InvalidRequestError("Hopper document belongs to another target; use open_binary to select that target")
raise InvalidRequestError("Unknown Hopper document")
return session_document
def _api_text(value, field):
"""Validate text before passing it to Hopper's native UTF-8 string API."""
if not isinstance(value, str):
raise InvalidRequestError("%s must be a string" % field)
if "\0" in value:
raise InvalidRequestError("%s contains a NUL character, which Hopper's Python API cannot represent" % field)
try:
value.encode("utf-8")
except UnicodeEncodeError as error:
raise InvalidRequestError("%s contains an unpaired Unicode surrogate, which Hopper's Python API cannot represent" % field) from error
return value
def _validate_name_targets(document, names):
"""Keep Hopper's unique-label moves within the explicitly selected addresses."""
targets = {address for _, address, _ in names}
if len(targets) != len(names):
raise InvalidRequestError("Batch rename selects the same address more than once; use one canonical address per target")
labels = set()
for _, address, name in names:
_segment(document, address)
if not name:
continue
if name in labels:
raise InvalidRequestError("Batch rename assigns the same name more than once: %s" % name)
labels.add(name)
owner = document.getAddressForName(name)
if owner not in BAD_ADDRESSES and owner != address and owner not in targets:
raise InvalidRequestError("Name %s is already assigned to %s; include that address in the batch with a replacement name" % (name, _hex(owner)))
def _symbol_name(value, field):
"""Reject native label truncation without altering the caller's name."""
value = _api_text(value, field)
if len(value.encode("utf-16-le")) // 2 > HOPPER_SYMBOL_NAME_UTF16_UNITS:
raise InvalidRequestError("%s exceeds Hopper's %s UTF-16 code-unit symbol-name limit" % (field, HOPPER_SYMBOL_NAME_UTF16_UNITS))
return value
def _name_matches(document, address, name):
observed = _segment(document, address).getNameAtAddress(address)
return observed == name or (not name and observed is None)
def _address(document, value=None):
"""Resolve hexadecimal addresses first, then fall back to Hopper symbol names."""
"""Keep explicit hexadecimal coordinates distinct from symbol names."""
if value is None:
return document.getCurrentAddress()
if not isinstance(value, str):
raise InvalidRequestError("Address must be a string")
try:
return int(value, 16)
except ValueError:
result = document.getAddressForName(value)
if result in BAD_ADDRESSES:
raise InvalidRequestError("Unknown Hopper address or name")
return result
_api_text(value, "Address")
if value.lower().startswith("0x"):
try:
address = int(value, 16)
except ValueError as error:
raise InvalidRequestError("Invalid hexadecimal Hopper address: %s" % value) from error
else:
# Names such as `add` and `face` are valid hexadecimal strings too.
# Prefer an observed symbol, retaining bare-hex compatibility only
# when Hopper has no symbol with the supplied name.
address = document.getAddressForName(value)
if address in BAD_ADDRESSES:
try:
address = int(value, 16)
except ValueError as error:
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
def _segment(document, address):
@@ -249,14 +294,18 @@ def _procedure_identity(procedure):
def _procedure_locals(procedure):
"""Project opaque Hopper local-variable objects into an exact public shape."""
return [
{
"description": str(local),
"""Preserve public local-variable names and observed stack displacements."""
result = []
for local in procedure.getLocalVariableList():
name = local.name()
displacement = local.displacement()
result.append({
"description": "%s (stack displacement %s)" % (name if name is not None else "Unnamed local", displacement),
"name": name,
"stack_displacement": str(displacement) if displacement is not None else None,
"provenance": "hopper-public-python-api",
}
for local in procedure.getLocalVariableList()
]
})
return result
def _containing_procedure(document, address):
@@ -267,26 +316,75 @@ def _containing_procedure(document, address):
return (procedure, None) if procedure is not None else (None, "not_in_procedure")
def _basic_block_end(procedure, block):
"""Normalize native block endpoints to an exclusive byte address."""
owner_at = getattr(procedure, "getBasicBlockAtAddress", None)
if not callable(owner_at):
raise CapabilityUnavailableError(
"Instruction enumeration requires Hopper basic-block ownership"
)
end = block.getEndingAddress()
# Hopper 6.1 returns the final instruction address, despite the Python
# documentation describing an exclusive end. Membership distinguishes
# that representation from an actual exclusive endpoint in other builds.
if owner_at(end) == block:
instruction = procedure.getSegment().getInstructionAtAddress(end)
if instruction is not None and instruction.getInstructionLength() > 0:
return end + instruction.getInstructionLength()
return end
def _block_instruction_addresses(procedure, block):
"""Walk either endpoint convention using Hopper's block ownership API.
Some Hopper builds return the final instruction start, despite the public
API documenting an exclusive end. Equality compares fresh native wrappers;
probing ownership also keeps an exclusive endpoint in the next block out.
"""
segment = procedure.getSegment()
address = block.getStartingAddress()
end = _basic_block_end(procedure, block)
while address < end:
if procedure.getBasicBlockAtAddress(address) != block:
break
instruction = segment.getInstructionAtAddress(address)
if instruction is None:
break
length = instruction.getInstructionLength()
if length <= 0 or address + length > end:
break
yield address
address += length
def _instruction_addresses(procedure):
result = []
seen = set()
segment = procedure.getSegment()
for block in procedure.basicBlockIterator():
address = block.getStartingAddress()
end = block.getEndingAddress()
while address < end and address not in seen:
for address in _block_instruction_addresses(procedure, block):
if address in seen:
continue
seen.add(address)
instruction = segment.getInstructionAtAddress(address)
if instruction is None:
break
result.append(address)
length = instruction.getInstructionLength()
if length <= 0:
break
address += length
return result
def _native_calls(procedure, direction):
references = procedure.getAllCallees() if direction == "outgoing" else procedure.getAllCallers()
kinds = {0: "none", 1: "unknown", 2: "direct", 3: "objective_c"}
calls = {}
unresolved = []
for reference in references:
source, target, kind = reference.fromAddress(), reference.toAddress(), reference.type()
if source in BAD_ADDRESSES:
continue
if target in BAD_ADDRESSES:
unresolved.append({"address": _hex(source), "reason": "Native CallReference has no resolved target (type %s)" % kind})
continue
calls[(source, target)] = {"classification": kinds.get(kind, "unknown"), "provider_type": kind, "provenance": "hopper-public-python-api:CallReference"}
return calls, sorted(unresolved, key=lambda item: int(item["address"], 16))
def _procedure_references(document, params):
procedure = _procedure(document, params.get("procedure"))
direction = params.get("direction", "outgoing")
@@ -299,6 +397,8 @@ def _procedure_references(document, params):
references = segment.getReferencesFromAddress(address) if direction == "outgoing" else segment.getReferencesOfAddress(address)
for reference in references:
edges.add((address, reference) if direction == "outgoing" else (reference, address))
calls, unresolved = _native_calls(procedure, direction)
edges.update(calls)
ordered = sorted(edges)
items = []
for source, target in ordered:
@@ -309,16 +409,15 @@ def _procedure_references(document, params):
"target_address": _hex(target),
"source_procedure": _procedure_identity(source_procedure) if source_procedure is not None else None,
"target_procedure": _procedure_identity(target_procedure) if target_procedure is not None else None,
"kind": _unavailable("Hopper's public Python API does not classify reference kinds"),
"kind": _unavailable("Hopper exposes partial CallReference classification, but not detailed reference flags"),
**({"call": calls[(source, target)]} if (source, target) in calls else {}),
})
return {
"procedure": _procedure_identity(procedure), "direction": direction,
"reference_kinds_available": False,
# The public Hopper API returns observed references but does not expose
# enough flow metadata to enumerate calls with no resolved target.
# Keep the required list field explicit; the provider records this as
# unknown coverage rather than claiming the list is exhaustive.
"unresolved_calls": [],
# These are native CallReference objects, not proof that every unresolved
# dispatch site has been enumerated by Hopper.
"unresolved_calls": unresolved,
"references": items,
}
@@ -333,11 +432,61 @@ def _procedure_map(document):
def _strings(document):
result = {}
for segment in document.getSegmentsList():
for value, address in segment.getStringsList():
result[_hex(address)] = value
return result
return {address: record["value"] for address, record in _string_inventory(document).items()}
def _string_record(segment, address, display):
"""Read one native typed object, retaining its shortened display separately."""
length = segment.getObjectLength(address)
end = segment.getStartingAddress() + segment.getLength()
if isinstance(length, bool) or not isinstance(length, int) or length <= 0 or length > end - address:
raise CapabilityUnavailableError("Hopper reported an invalid string object extent at %s" % _hex(address))
raw = segment.readBytes(address, length)
if not isinstance(raw, (bytes, bytearray)) or len(raw) != length:
raise CapabilityUnavailableError("Hopper could not read the complete string object at %s" % _hex(address))
kind = segment.getTypeAtAddress(address)
encodings = ("utf-8", "latin-1") if kind == segment.TYPE_ASCII else ("utf-16-le", "utf-16-be") if kind == segment.TYPE_UNICODE else ()
# Native display escaping is not invertible (a literal backslash-n and a
# newline can render alike). Decode byte strings directly; for UTF-16,
# compare rendered candidates to establish an unambiguous byte order.
candidates = []
for encoding in encodings:
terminator = b"\x00\x00" if encoding.startswith("utf-16") else b"\x00"
terminated = raw.endswith(terminator)
payload = raw[:-len(terminator)] if terminated else raw
try:
value = payload.decode(encoding)
except UnicodeDecodeError:
continue
record = {"value": value, "provider_value": display, "string": {"encoding": encoding, "encoding_status": "inferred", "termination": "present_or_not_required" if terminated else "missing", "byte_length": length}}
if kind == segment.TYPE_ASCII:
return record
rendered = value.replace('"', '\\"').replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
if rendered == display or (display.endswith("\u2026") and display[:-1] and rendered.startswith(display[:-1])):
candidates.append(record)
if candidates and len({candidate["value"] for candidate in candidates}) == 1:
return candidates[0]
raise CapabilityUnavailableError("Hopper's string display cannot be reconciled with its typed bytes at %s; inspect the bytes and encoding explicitly" % _hex(address))
def _string_inventory(document, addresses=None):
key = (id(document), "string_objects")
objects = _search_inventory_cache.get(key)
if objects is None:
objects = {address: (segment, display) for segment in document.getSegmentsList() for display, address in segment.getStringsList()}
_search_inventory_cache[key] = objects
key = (id(document), "string_records")
records = _search_inventory_cache.setdefault(key, {})
selected = sorted(objects if addresses is None else objects.keys() & addresses)
for address in selected:
name = _hex(address)
if name not in records:
segment, display = objects[address]
try:
records[name] = _string_record(segment, address, display)
except CapabilityUnavailableError as error:
records[name] = {"value": display, "provider_value": display, "decoding": _unavailable(str(error))}
return {_hex(address): records[_hex(address)] for address in selected}
def _invalidate_search_inventory(document):
@@ -397,17 +546,18 @@ def _search_results(document, kind, params):
needle = pattern if case_sensitive else pattern.casefold()
matches = lambda value: needle in (value if case_sensitive else value.casefold())
else:
try:
expression = re.compile(pattern, 0 if case_sensitive else re.IGNORECASE)
except (re.error, OverflowError) as error:
raise InvalidRequestError("Invalid regex pattern") from error
matches = lambda value: expression.search(value) is not None
raise CapabilityUnavailableError("Regex matching requires REA's supervised worker; the native bridge only executes literal searches")
selected = []
for item in _search_inventory(document, kind):
if not matches(item[1]):
continue
selected.append(item)
if not selected:
return []
if kind == "string":
records = _string_inventory(document)
return [{"address": address, **records[address]} for address, _ in selected]
return [
{
"address": address,
@@ -450,25 +600,11 @@ def _render_instruction(segment, address):
def _assembly(procedure):
"""Render assembly while guarding against malformed instruction cycles."""
lines = []
segment = procedure.getSegment()
seen = set()
for block in procedure.basicBlockIterator():
address = block.getStartingAddress()
end = block.getEndingAddress()
while address < end and address not in seen:
seen.add(address)
instruction = segment.getInstructionAtAddress(address)
if instruction is None:
break
line = _render_instruction(segment, address)
if line is not None:
lines.append(line)
length = instruction.getInstructionLength()
if length <= 0:
break
address += length
return "\n".join(lines)
return "\n".join(
line for address in _instruction_addresses(procedure)
if (line := _render_instruction(segment, address)) is not None
)
def _read_function_instructions(document, params):
@@ -505,7 +641,7 @@ def _analyze_function(document, params):
successors.append(_hex(successor))
blocks.append({
"start": _hex(block.getStartingAddress()),
"end": _hex(block.getEndingAddress()),
"end": _hex(_basic_block_end(procedure, block)),
"successors": sorted(set(successors), key=lambda value: int(value, 16)),
})
pseudo = _pseudocode(document, procedure) or ""
@@ -529,6 +665,10 @@ def _analyze_function(document, params):
incoming = []
outgoing = []
procedure_addresses = set(addresses)
outgoing_calls, unresolved_calls = _native_calls(procedure, "outgoing")
incoming_calls, _ = _native_calls(procedure, "incoming")
calls = {**incoming_calls, **outgoing_calls}
edges.update(calls)
for source, target in sorted(edges):
source_procedure, _ = _containing_procedure(document, source)
target_procedure, _ = _containing_procedure(document, target)
@@ -537,16 +677,14 @@ def _analyze_function(document, params):
"target_address": _hex(target),
"source_procedure": _procedure_identity(source_procedure) if source_procedure is not None else None,
"target_procedure": _procedure_identity(target_procedure) if target_procedure is not None else None,
"kind": _unavailable("Hopper's public Python API does not classify reference kinds"),
"kind": _unavailable("Hopper exposes partial CallReference classification, but not detailed reference flags"),
**({"call": calls[(source, target)]} if (source, target) in calls else {}),
}
if target in procedure_addresses and source not in procedure_addresses:
incoming.append(item)
if source in procedure_addresses:
outgoing.append(item)
string_map = {
int(address, 16): value
for address, value in _search_inventory(document, "string")
}
string_map = {int(address, 16): record for address, record in _string_inventory(document, {int(edge["target_address"], 16) for edge in outgoing}).items()}
name_map = {
int(address, 16): value
for address, value in _search_inventory(document, "name")
@@ -556,7 +694,7 @@ def _analyze_function(document, params):
for edge in outgoing:
target = int(edge["target_address"], 16)
if target in string_map:
referenced_strings.append({"address": edge["target_address"], "value": string_map[target], "source_address": edge["source_address"]})
referenced_strings.append({"address": edge["target_address"], **string_map[target], "source_address": edge["source_address"]})
if target in name_map:
referenced_names.append({"address": edge["target_address"], "value": name_map[target], "source_address": edge["source_address"]})
comments.sort(key=lambda item: (int(item["address"], 16), item["kind"]))
@@ -574,13 +712,14 @@ def _analyze_function(document, params):
"callers": callers, "callees": callees,
"incoming_references": incoming,
"outgoing_references": outgoing,
"unresolved_calls": unresolved_calls,
"referenced_strings": referenced_strings,
"referenced_names": referenced_names,
"basic_blocks": blocks,
"limitations": [
"Hopper's public Python API does not classify reference kinds.",
"Native CallReference classifications are observed where available; detailed reference flags remain unknown.",
"Hopper's public Python API does not expose equivalent external or thunk classification in this dossier.",
"Unresolved indirect calls without reported target addresses are not represented as call edges.",
"Native calls without resolved target addresses are not represented as edges; enumeration beyond reported CallReference objects is unknown.",
"Pseudocode and assembly are provider-specific representations, not original source.",
],
}
@@ -588,7 +727,6 @@ def _analyze_function(document, params):
def _dispatch(method, params):
"""Dispatch only the closed operation set implemented by REA's public tools."""
global _selected_document
if method == "health":
return {"name": "REA Hopper bridge", "version": "1.0.0", "run_id": REA_RUN_ID}
if method in ("shutdown", "shutdown_document"):
@@ -622,14 +760,9 @@ def _dispatch(method, params):
"document_closed": document_closed,
}
if method == "list_documents":
return [document.getDocumentName() for document in Document.getAllDocuments()]
return [document.getDocumentName() for document in _api().documents()]
if method == "current_document":
return _document().getDocumentName()
if method == "set_current_document":
document = _document(params.get("document"))
_selected_document = document.getDocumentName()
return _selected_document
document = _document(params.get("document"))
if method in EXHAUSTIVE_ANALYSIS_METHODS:
_api().require_analysis_complete(document, method)
@@ -650,10 +783,17 @@ def _dispatch(method, params):
"Hopper's public Python API does not expose readBytes"
)
address = _address(document, params.get("address"))
value = reader(address, length)
segment = _segment(document, address)
# Document.readBytes delegates to one Segment and returns False when
# the requested range crosses its end. Preserve the readable prefix.
available = segment.getStartingAddress() + segment.getLength() - address
value = reader(address, min(length, available))
if value is False or value is None:
raise InvalidRequestError("Hopper could not read the requested byte range at %s" % _hex(address))
if not isinstance(value, (bytes, bytearray)):
raise CapabilityUnavailableError(
"Hopper's public Python API returned an unsupported byte representation"
"Hopper readBytes returned %s for the requested %s-byte range at %s"
% (type(value).__name__, length, _hex(address))
)
data = bytes(value)
return {
@@ -678,7 +818,26 @@ def _dispatch(method, params):
or offset < 0
):
raise InvalidRequestError("Address has no authoritative file-offset mapping")
return {"address": _hex(address), "file_offset": offset}
inverse = getattr(document, "getAddressFromFileOffset", None)
if not callable(inverse):
raise CapabilityUnavailableError(
"Hopper's public Python API cannot verify the file-offset mapping by reverse lookup"
)
# Synthetic memory can report offset zero, which belongs to the actual
# file header. An integer alone does not prove source-file provenance.
if inverse(offset) != address:
raise InvalidRequestError("Address has no authoritative file-offset mapping: reverse lookup disagrees at %s" % _hex(address))
if offset > 9007199254740991:
raise CapabilityUnavailableError("Provider file offset exceeds the exact JSON integer range")
header_address = inverse(0)
reader = getattr(document, "readBytes", None)
header = reader(header_address, 12) if header_address not in BAD_ADDRESSES and document.getSegmentAtAddress(header_address) is not None and callable(reader) else None
return {
"address": _hex(address),
"provider_file_offset": offset,
"provider_source_path": document.getExecutableFilePath(),
"provider_image_header_hex": bytes(header).hex() if isinstance(header, (bytes, bytearray)) and len(header) == 12 else None,
}
if method == "resolve_containing_procedure":
address = _address(document, params.get("address"))
procedure, reason = _containing_procedure(document, address)
@@ -694,8 +853,13 @@ def _dispatch(method, params):
return _procedure_name(_procedure(document))
if method == "goto_address":
address = _address(document, params.get("address"))
segment = _segment(document, address)
expected = segment.getInstructionStart(address)
document.moveCursorAtAddress(address)
return _hex(address)
observed = document.getCurrentAddress()
if observed != (address if expected in BAD_ADDRESSES else expected):
raise InvalidRequestError("Hopper cursor did not reach the requested object at %s; observed %s" % (_hex(address), _hex(observed)))
return _hex(observed)
if method in ("address_name", "comment", "inline_comment", "xrefs"):
target = _address(document, params.get("address"))
segment = _segment(document, target)
@@ -708,12 +872,22 @@ def _dispatch(method, params):
return [_hex(value) for value in segment.getReferencesOfAddress(target)]
if method in ("next_address", "prev_address"):
target = _address(document, params.get("address"))
segment = _segment(document, target)
if method == "next_address":
result = target + max(1, document.getObjectLength(target))
start = segment.getInstructionStart(target)
if start in BAD_ADDRESSES:
raise InvalidRequestError("No analyzed object exists at the requested address")
length = segment.getObjectLength(start)
if length in BAD_ADDRESSES or length <= 0:
raise InvalidRequestError("No next analyzed object exists at the requested address")
result = start + length
else:
result = document.getInstructionStart(max(0, target - 1))
if result in BAD_ADDRESSES:
raise InvalidRequestError("No adjacent address")
previous_segment = document.getSegmentAtAddress(target - 1) if target > 0 else None
if previous_segment is None:
raise InvalidRequestError("No previous address exists in mapped memory")
result = previous_segment.getInstructionStart(target - 1)
if result in BAD_ADDRESSES or document.getSegmentAtAddress(result) is None:
raise InvalidRequestError("No adjacent analyzed object exists in mapped memory")
return _hex(result)
if method == "list_segments":
result = []
@@ -747,12 +921,9 @@ def _dispatch(method, params):
if method == "list_procedures":
return [{"address": address, "value": value} for address, value in _search_inventory(document, "procedure")]
if method == "list_strings":
values = dict(_search_inventory(document, "string"))
requested = params.get("address")
if requested is not None:
key = _hex(_address(document, requested))
values = {key: values[key]} if key in values else {}
return [{"address": address, "value": value} for address, value in values.items()]
values = _string_inventory(document, None if requested is None else {_address(document, requested)})
return [{"address": address, **record} for address, record in values.items()]
if method == "list_names":
result = dict(_search_inventory(document, "name"))
requested = params.get("address")
@@ -777,7 +948,7 @@ def _dispatch(method, params):
return sorted((_hex(item.getEntryPoint()) for item in procedure.getAllCalleeProcedures()), key=lambda value: int(value, 16))
if method == "procedure_info":
blocks = list(procedure.basicBlockIterator())
length = sum(max(0, block.getEndingAddress() - block.getStartingAddress()) for block in blocks)
length = sum(max(0, _basic_block_end(procedure, block) - block.getStartingAddress()) for block in blocks)
return {
"name": _procedure_name(procedure),
"entrypoint": _hex(procedure.getEntryPoint()),
@@ -790,35 +961,51 @@ def _dispatch(method, params):
}
if method == "set_address_name":
address = _address(document, params.get("address"))
name = _symbol_name(params["name"], "Name")
_validate_name_targets(document, [(params.get("address"), address, name)])
try:
result = document.setNameAtAddress(address, params["name"])
result = document.setNameAtAddress(address, name)
finally:
_invalidate_search_inventory(document)
_invalidate_pseudocode(document)
return result
return bool(result) and _name_matches(document, address, name)
if method == "set_addresses_names":
# Validate all destinations before applying any annotation. A malformed
# later address must not discard the result of an earlier mutation.
names = [(key, _address(document, key), _symbol_name(value, "Name for %s" % key)) for key, value in params["names"].items()]
_validate_name_targets(document, names)
try:
result = {key: document.setNameAtAddress(_address(document, key), value) for key, value in params["names"].items()}
result = {key: document.setNameAtAddress(address, value) for key, address, value in names}
finally:
_invalidate_search_inventory(document)
_invalidate_pseudocode(document)
return result
return {key: bool(result[key]) and _name_matches(document, address, value) for key, address, value in names}
if method in ("set_comment", "set_inline_comment"):
address = _address(document, params.get("address"))
comment = _api_text(params["comment"], "Comment")
segment = _segment(document, address)
setter = segment.setCommentAtAddress if method == "set_comment" else segment.setInlineCommentAtAddress
getter = segment.getCommentAtAddress if method == "set_comment" else segment.getInlineCommentAtAddress
setter(address, params["comment"])
setter(address, comment)
_invalidate_pseudocode(document)
return getter(address) == params["comment"]
observed = getter(address)
return observed == comment or (comment == "" and observed is None)
if method == "list_bookmarks":
return [{"address": _hex(item), "name": document.getBookmarkName(item)} for item in document.getBookmarks()]
if method == "set_bookmark":
address = _address(document, params.get("address"))
document.setBookmarkAtAddress(address, params.get("name"))
_segment(document, address)
name = params.get("name")
if name is not None:
_api_text(name, "Bookmark name")
document.setBookmarkAtAddress(address, name)
return document.hasBookmarkAtAddress(address)
if method == "unset_bookmark":
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.
if not document.hasBookmarkAtAddress(address):
_segment(document, address)
document.removeBookmarkAtAddress(address)
return not document.hasBookmarkAtAddress(address)
raise InvalidRequestError("Unknown bridge method")
@@ -913,14 +1100,14 @@ def _diagnostic_type(error):
def _safe_diagnostic(error):
"""Project one exception without retaining provider or credential text."""
"""Preserve bridge validation reasons while excluding transport credentials."""
diagnostic_type = _diagnostic_type(error)
if diagnostic_type == "capability_unavailable":
message = str(error)
elif diagnostic_type == "authorization":
message = "Invalid bridge capability"
elif diagnostic_type == "invalid_request":
message = "Invalid Hopper bridge request"
message = str(error)
else:
message = "%s: Hopper bridge operation failed" % type(error).__name__
return {"code": -32000, "message": message, "type": diagnostic_type}
+630 -94
View File
@@ -3,12 +3,18 @@
REA imports this module into `lldb --batch` and runs `rea_trace <config.json>`.
It launches one owned process, stops at the requested functions and
Objective-C methods, records each entry from registers and memory reads only,
continues, and kills the process once it exits, the event limit is reached or
the duration elapses. No expression is evaluated and no target code runs on
the tracer's behalf. The result is written as JSON to the configured path.
continues, and kills the process once it exits, the event or aggregate resource
limit is reached, or the duration elapses. Target output is drained through
bounded FIFOs. No expression is evaluated and no target code runs on the
tracer's behalf. The result is written as JSON to the configured path.
"""
import errno
import hashlib
import json
import os
import select
import threading
import time
import lldb
@@ -22,9 +28,218 @@ ARGUMENT_REGISTERS = {
MAX_SELECTOR_BYTES = 1024
MAX_REPORTED_LOCATIONS = 64
MAX_OTHER_STOPS = 256
MAX_TRACE_BYTES = 8 * 1024 * 1024
MAX_TRACE_FRAMES = 65_536
MAX_OBSERVATION_JOURNAL_BYTES = MAX_TRACE_BYTES
MAX_BREAKPOINT_LOCATION_BYTES = 8 * 1024 * 1024
MAX_TARGET_OUTPUT_BYTES = 1024 * 1024
OUTPUT_DRAIN_BYTES_PER_PASS = 1024 * 1024
OUTPUT_EOF_WAIT_SECONDS = 1.0
OUTPUT_READER_JOIN_SECONDS = 0.5
LISTENER_NAME = "rea-native-call-observation"
def _can_retain_trace(current_bytes, current_frames, event_bytes, event_frames):
"""Whether a complete event fits both aggregate trace budgets."""
return (
current_bytes + event_bytes <= MAX_TRACE_BYTES
and current_frames + event_frames <= MAX_TRACE_FRAMES
)
def _estimated_json_size(value):
"""Estimate JSON size without encoding potentially oversized values."""
if isinstance(value, str):
# json.dumps defaults to ensure_ascii=True; supplementary characters
# can become two six-byte Unicode escapes.
return len(value) * 12 + 2
if value is None or isinstance(value, (bool, int, float)):
return 32
if isinstance(value, dict):
return 2 + sum(
len(key) * 6 + 4 + _estimated_json_size(item)
for key, item in value.items()
)
if isinstance(value, (list, tuple)):
return 2 + sum(_estimated_json_size(item) + 1 for item in value)
return 64
def _fits_trace_budget(value, remaining_bytes):
return _estimated_json_size(value) <= remaining_bytes
class _ObservationJournal:
"""Flush a bounded JSONL prefix so observations survive tracer failure."""
def __init__(self, handle, max_bytes=MAX_OBSERVATION_JOURNAL_BYTES):
self.handle = handle
self.max_bytes = max_bytes
self.bytes = 0
def append(self, record):
encoded = (json.dumps(record) + "\n").encode("utf-8")
if self.bytes + len(encoded) > self.max_bytes:
return False
self.handle.write(encoded)
self.handle.flush()
self.bytes += len(encoded)
return True
def _set_launch_flags(launch):
launch.SetLaunchFlags(
launch.GetLaunchFlags()
| lldb.eLaunchFlagDebug
| lldb.eLaunchFlagStopAtEntry
)
def _hash_file(path):
digest = hashlib.sha256()
with open(path, "rb") as target_file:
while True:
chunk = target_file.read(1024 * 1024)
if not chunk:
break
digest.update(chunk)
return digest.hexdigest()
def _file_identity(path):
info = os.stat(path)
return str(info.st_dev), str(info.st_ino)
def _module_identity(target):
module = target.FindModule(target.GetExecutable())
if not module.IsValid():
return None, None, None
spec = module.GetFileSpec()
path = str(spec) if spec.IsValid() else None
identity = None
if path:
try:
identity = _file_identity(path)
except OSError:
pass
return path, identity, module.GetUUIDString() or None
def _wait_for_identity_ack(config):
"""Hold the target at entry while the host adapter captures its start identity."""
path = config.get("identity_ack_path")
if not path:
return
while True:
try:
with open(path, "r") as handle:
return handle.read(32).strip()
except FileNotFoundError:
time.sleep(0.025)
class _OutputPipe:
"""Drain a target FIFO while retaining only its bounded output prefix."""
def __init__(self, fifo_path, capture_path, max_bytes):
os.mkfifo(fifo_path, 0o600)
self.path = fifo_path
try:
self.fd = os.open(fifo_path, os.O_RDONLY | os.O_NONBLOCK)
except Exception:
os.unlink(fifo_path)
raise
try:
self.capture = open(capture_path, "wb")
except Exception:
os.close(self.fd)
os.unlink(fifo_path)
raise
self.max_bytes = max_bytes
self.bytes = 0
self.truncated = False
self.closed = False
self.stop_reader = threading.Event()
self.reader_thread = None
self.reader_error = None
self.launched = False
self.eof = threading.Event()
self.complete = False
def start(self):
if self.reader_thread is None:
self.reader_thread = threading.Thread(target=self._reader_loop, daemon=True)
self.reader_thread.start()
def _reader_loop(self):
try:
while not self.stop_reader.is_set():
readable, _, _ = select.select([self.fd], [], [], 0.05)
if not readable:
continue
if self.drain(OUTPUT_DRAIN_BYTES_PER_PASS) == 0:
# A FIFO read end is readable at EOF before the target opens it.
time.sleep(0.01)
except Exception as error:
self.reader_error = error
self.stop_reader.set()
def mark_launched(self):
self.launched = True
def drain(self, max_bytes=OUTPUT_DRAIN_BYTES_PER_PASS):
consumed = 0
while consumed < max_bytes:
try:
chunk = os.read(self.fd, min(65_536, max_bytes - consumed))
except OSError as error:
if error.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
return consumed
raise
if not chunk:
if self.launched:
self.eof.set()
return consumed
consumed += len(chunk)
self.bytes += len(chunk)
remaining = max(0, self.max_bytes - self.capture.tell())
if remaining:
self.capture.write(chunk[:remaining])
if len(chunk) > remaining:
self.truncated = True
def close(self, expected_exit=False):
if self.closed:
return
if expected_exit and self.launched:
self.eof.wait(OUTPUT_EOF_WAIT_SECONDS)
self.stop_reader.set()
if self.reader_thread is not None:
self.reader_thread.join(OUTPUT_READER_JOIN_SECONDS)
stopped = self.reader_thread is None or not self.reader_thread.is_alive()
if stopped:
try:
self.drain(OUTPUT_DRAIN_BYTES_PER_PASS)
finally:
try:
self.capture.close()
finally:
os.close(self.fd)
self.closed = True
else:
# A daemon reader may still be unwinding a blocked filesystem write.
# Leave its handles alone rather than racing that thread at shutdown.
self.closed = True
self.complete = bool(
stopped
and (not self.launched or (expected_exit and self.eof.is_set()))
and self.reader_error is None
)
if not self.complete:
self.truncated = True
def _hex(value):
return "0x%x" % value
@@ -34,7 +249,7 @@ def _location(address, target):
module = address.GetModule()
symbol = address.GetSymbol()
file_address = address.GetFileAddress()
return {
location = {
"load_address": _hex(address.GetLoadAddress(target)),
"file_address": None
if file_address == lldb.LLDB_INVALID_ADDRESS
@@ -43,6 +258,7 @@ def _location(address, target):
"module_path": str(module.GetFileSpec()) if module.IsValid() else None,
"symbol": symbol.GetName() if symbol.IsValid() else None,
}
return location
def _no_files():
@@ -107,33 +323,54 @@ def _wanted(spec, symbol):
return symbol.startswith("-[" if method_type == "instance" else "+[")
def _record(context, thread, index, sequence):
def _read_register(frame, name):
error = lldb.SBError()
value = frame.FindRegister(name)
if not value.IsValid():
raise RuntimeError("LLDB breakpoint register %s is unavailable" % name)
number = value.GetValueAsUnsigned(error)
if error.Fail():
raise RuntimeError(
"LLDB breakpoint register %s could not be read: %s" % (name, error)
)
return number
def _record(context, thread, frame, index, sequence, location, remaining_bytes):
target, process, config = context["target"], context["process"], context["config"]
frame = thread.GetFrameAtIndex(0)
location = _location(frame.GetPCAddress(), target)
symbol = location["symbol"] or ""
if not _wanted(config["breakpoints"][index], symbol):
return None
names = ARGUMENT_REGISTERS.get(config["architecture"], [])
objc_method = symbol.startswith("-[") or symbol.startswith("+[")
values = {
name: _read_register(frame, name)
for name in names[: max(config["argument_registers"], 2 if objc_method else 0)]
}
registers = [
{"name": name, "value": _hex(frame.FindRegister(name).GetValueAsUnsigned())}
{"name": name, "value": _hex(values[name])}
for name in names[: config["argument_registers"]]
]
receiver_class = None
selector = None
if symbol.startswith("-[") or symbol.startswith("+["):
if objc_method:
error = lldb.SBError()
text = process.ReadCStringFromMemory(
frame.FindRegister(names[1]).GetValueAsUnsigned(),
values[names[1]],
MAX_SELECTOR_BYTES,
error,
)
selector = text if error.Success() and text else None
if symbol.startswith("-["):
receiver_class = _dynamic_class(
target, frame.FindRegister(names[0]).GetValueAsUnsigned()
)
frames = min(thread.GetNumFrames(), 1 + config["backtrace_frames"])
receiver_class = _dynamic_class(target, values[names[0]])
estimated_base = (
_estimated_json_size(location)
+ _estimated_json_size(receiver_class)
+ _estimated_json_size(selector)
+ 2048
)
if estimated_base > remaining_bytes:
raise ValueError("trace event exceeds aggregate byte budget")
record = {
"sequence": sequence,
"elapsed_ms": round((time.monotonic() - context["started"]) * 1000, 3),
@@ -142,55 +379,154 @@ def _record(context, thread, index, sequence):
"receiver_class": receiver_class,
"selector": selector,
"registers": registers,
"backtrace": [
_location(thread.GetFrameAtIndex(depth).GetPCAddress(), target)
for depth in range(1, frames)
],
"backtrace": [],
}
record.update(location)
return record
def _handle_stop(context, events, other_stops):
"""Record breakpoint hits of one stop; return an outcome when tracing must end."""
process = context["process"]
for thread in process:
reason = thread.GetStopReason()
if reason == lldb.eStopReasonBreakpoint:
index = context["breakpoint_index"].get(thread.GetStopReasonDataAtIndex(0))
if index is None:
continue
record = _record(context, thread, index, len(events))
if record is not None:
events.append(record)
if len(events) >= context["config"]["max_events"]:
return "event-limit"
elif reason in (lldb.eStopReasonSignal, lldb.eStopReasonException):
other_stops.append(thread.GetStopDescription(256))
# One trace runs in each owned LLDB subprocess. Its callbacks execute at the
# breakpoint before LLDB resumes, unlike snapshots of later public stop events.
_TRACE_CONTEXT = None
def _handle_breakpoint(context, frame, index):
"""Read one hit while LLDB's breakpoint callback owns the stopped frame."""
if not frame.IsValid() or frame.GetPC() == lldb.LLDB_INVALID_ADDRESS:
raise RuntimeError("LLDB breakpoint callback did not supply a readable frame")
thread = frame.GetThread()
events = context["events"]
location = _location(frame.GetPCAddress(), context["target"])
symbol = location["symbol"] or ""
if not _wanted(context["config"]["breakpoints"][index], symbol):
return None
frame_count = max(
0,
min(thread.GetNumFrames() - 1, context["config"]["backtrace_frames"]),
)
event_frame_count = 1 + frame_count
if not _can_retain_trace(
context["retained_trace_bytes"],
context["retained_frames"],
0,
event_frame_count,
):
return "resource-limit"
try:
remaining_bytes = MAX_TRACE_BYTES - context["retained_trace_bytes"]
record = _record(
context, thread, frame, index, len(events), location, remaining_bytes
)
except ValueError:
return "resource-limit"
if record is not None:
if not _fits_trace_budget(record, remaining_bytes):
return "resource-limit"
record_bytes = len(json.dumps(record).encode("utf-8"))
if context["retained_trace_bytes"] + record_bytes > MAX_TRACE_BYTES:
return "resource-limit"
for depth in range(1, frame_count + 1):
try:
location = _location(
thread.GetFrameAtIndex(depth).GetPCAddress(), context["target"]
)
except ValueError:
return "resource-limit"
if (
_estimated_json_size(location) + record_bytes + 1
> MAX_TRACE_BYTES - context["retained_trace_bytes"]
):
return "resource-limit"
location_bytes = len(json.dumps(location).encode("utf-8"))
addition = location_bytes + (1 if record["backtrace"] else 0)
if (
context["retained_trace_bytes"]
+ record_bytes
+ addition
> MAX_TRACE_BYTES
):
return "resource-limit"
record["backtrace"].append(location)
record_bytes += addition
journal = context.get("observation_journal")
if journal is not None and not journal.append(
{"kind": "event", "event": record}
):
return "resource-limit"
events.append(record)
context["retained_frames"] += event_frame_count
context["retained_trace_bytes"] += record_bytes
if len(events) >= context["config"]["max_events"]:
return "event-limit"
return None
def breakpoint_hit(frame, location, internal_dict):
"""LLDB callback: capture before resuming, and stop on a limit or failure."""
context = _TRACE_CONTEXT
if context is None or context["outcome"] is not None or context["failure"] is not None:
return True
try:
index = context["breakpoint_index"][location.GetBreakpoint().GetID()]
context["outcome"] = _handle_breakpoint(context, frame, index)
except Exception as error:
context["failure"] = "%s: %s" % (type(error).__name__, error)
return True
return context["outcome"] is not None
def _handle_stop(context, other_stops):
"""Retain non-breakpoint stop observations; callbacks already capture hits."""
for thread in context["process"]:
if thread.GetStopReason() in (lldb.eStopReasonSignal, lldb.eStopReasonException):
stop_description = thread.GetStopDescription(256)
journal = context.get("observation_journal")
if journal is not None and not journal.append(
{"kind": "other-stop", "reason": stop_description}
):
return "resource-limit"
other_stops.append(stop_description)
if len(other_stops) >= MAX_OTHER_STOPS:
return "stop-limit"
return None
def _breakpoint_report(target, created):
def _breakpoint_report(target, created, config):
report = []
retained_location_bytes = 0
locations_truncated = False
for index, breakpoints in enumerate(created):
locations = []
count = 0
for breakpoint in breakpoints:
count += breakpoint.GetNumLocations()
for position in range(breakpoint.GetNumLocations()):
address = breakpoint.GetLocationAtIndex(position).GetAddress()
location = _location(address, target)
if not _wanted(config["breakpoints"][index], location["symbol"] or ""):
continue
count += 1
if len(locations) < MAX_REPORTED_LOCATIONS:
locations.append(
_location(breakpoint.GetLocationAtIndex(position).GetAddress(), target)
remaining_bytes = (
MAX_BREAKPOINT_LOCATION_BYTES - retained_location_bytes
)
if not _fits_trace_budget(location, remaining_bytes):
locations_truncated = True
continue
location_bytes = len(json.dumps(location).encode("utf-8"))
addition = location_bytes + (1 if locations else 0)
if addition > remaining_bytes:
locations_truncated = True
continue
locations.append(location)
retained_location_bytes += addition
report.append({"index": index, "location_count": count, "locations": locations})
return report
return report, locations_truncated
def _launch_info(target, config, listener):
launch = lldb.SBLaunchInfo(config["arguments"])
launch.SetListener(listener)
_set_launch_flags(launch)
environment = target.GetEnvironment()
for key, value in config["environment"].items():
environment.Set(key, value, True)
@@ -210,81 +546,281 @@ def _wait_for_exit(listener, process, seconds):
listener.WaitForEvent(1, event)
def _failed_launch_description(process):
if process.GetState() != lldb.eStateExited or process.GetExitStatus() != -1:
return None
description = process.GetExitDescription()
if isinstance(description, str) and description.lower().startswith(("attach failed", "launch failed")):
return description
return None
def trace(debugger, config):
global _TRACE_CONTEXT
debugger.SetAsync(True)
# Stop at the symbol itself, where argument registers still hold the ABI
# arguments, even when line tables would let LLDB skip the prologue.
lldb.SBDebugger.SetInternalVariable(
"target.skip-prologue", "false", debugger.GetInstanceName()
)
try:
before_digest = _hash_file(config["executable"])
before_identity = _file_identity(config["executable"])
except OSError as error:
return {"status": "target-integrity-error", "error": str(error)}
if before_digest != config["expected_sha256"]:
return {
"status": "target-integrity-error",
"error": "selected executable digest changed before LLDB target creation",
}
error = lldb.SBError()
target = debugger.CreateTarget(
config["executable"], config["architecture"], None, False, error
)
if not target.IsValid():
return {"status": "target-error", "error": error.GetCString()}
module_path_before, module_file_identity_before, module_uuid_before = (
_module_identity(target)
)
if (
module_file_identity_before is not None
and module_file_identity_before != before_identity
):
return {
"status": "target-integrity-error",
"error": "LLDB executable module resolved to a different pre-launch file identity",
}
created = [_create_breakpoints(target, spec) for spec in config["breakpoints"]]
breakpoint_index = {}
for index, breakpoints in enumerate(created):
for breakpoint in breakpoints:
breakpoint_index[breakpoint.GetID()] = index
listener = lldb.SBListener(LISTENER_NAME)
output_budget = config.get("max_output_bytes", MAX_TARGET_OUTPUT_BYTES)
stdout = _OutputPipe(
config["stdout_path"], config["stdout_capture_path"], output_budget
)
try:
stderr = _OutputPipe(
config["stderr_path"], config["stderr_capture_path"], output_budget
)
except Exception:
stdout.close()
raise
stdout.start()
stderr.start()
observation_journal = None
started = time.monotonic()
process = target.Launch(_launch_info(target, config, listener), error)
if not error.Success() or not process.IsValid():
return {"status": "launch-error", "error": error.GetCString()}
with open(config["pid_path"], "w") as handle:
handle.write(str(process.GetProcessID()))
context = {
"target": target,
"process": process,
"config": config,
"started": started,
"breakpoint_index": breakpoint_index,
}
events = []
other_stops = []
outcome = None
deadline = started + config["duration_ms"] / 1000
event = lldb.SBEvent()
while outcome is None:
if time.monotonic() >= deadline:
outcome = "duration-elapsed"
break
if not listener.WaitForEvent(1, event):
continue
if not lldb.SBProcess.EventIsProcessEvent(event):
continue
state = lldb.SBProcess.GetStateFromEvent(event)
if state == lldb.eStateExited:
outcome = "exited"
elif state == lldb.eStateStopped and not lldb.SBProcess.GetRestartedFromEvent(event):
outcome = _handle_stop(context, events, other_stops)
if outcome is None:
process.Continue()
pid = process.GetProcessID()
exit_status = None
exit_description = None
if outcome == "exited":
exit_status = process.GetExitStatus()
exit_description = process.GetExitDescription()
else:
process.Kill()
_wait_for_exit(listener, process, 5)
return {
"status": "traced",
"version": lldb.SBDebugger.GetVersionString().splitlines()[0],
"pid": pid,
"outcome": outcome,
"exit_status": exit_status,
"exit_description": exit_description,
"killed": outcome != "exited",
"terminated": process.GetState() == lldb.eStateExited,
"elapsed_ms": round((time.monotonic() - started) * 1000, 3),
"breakpoints": _breakpoint_report(target, created),
"events": events,
"other_stops": other_stops,
}
process = None
try:
observation_path = config.get("observation_path")
if observation_path:
observation_journal = _ObservationJournal(
open(observation_path, "wb")
)
process = target.Launch(_launch_info(target, config, listener), error)
if not error.Success() or not process.IsValid():
stdout.close()
stderr.close()
return {"status": "launch-error", "error": error.GetCString()}
launch_failure = _failed_launch_description(process)
if launch_failure is not None:
stdout.close(expected_exit=True)
stderr.close(expected_exit=True)
return {"status": "launch-error", "error": launch_failure}
stdout.mark_launched()
stderr.mark_launched()
# Publish the PID before any post-launch file or module inspection. The
# process is stopped at entry and remains there until identity_ack.
with open(config["pid_path"], "w") as handle:
handle.write(str(process.GetProcessID()))
identity_ack = _wait_for_identity_ack(config)
if identity_ack != "readable":
process.Kill()
_wait_for_exit(listener, process, 5)
expected_exit = process.GetState() == lldb.eStateExited
stdout.close(expected_exit=expected_exit)
stderr.close(expected_exit=expected_exit)
return {
"status": "target-identity-unavailable",
"error": "host could not establish the launched process identity; target was stopped",
}
try:
after_digest = _hash_file(config["executable"])
after_identity = _file_identity(config["executable"])
except OSError as identity_error:
process.Kill()
stdout.close(expected_exit=process.GetState() == lldb.eStateExited)
stderr.close(expected_exit=process.GetState() == lldb.eStateExited)
return {"status": "target-integrity-error", "error": str(identity_error)}
if after_digest != config["expected_sha256"] or after_identity != before_identity:
process.Kill()
_wait_for_exit(listener, process, 5)
stdout.close(expected_exit=process.GetState() == lldb.eStateExited)
stderr.close(expected_exit=process.GetState() == lldb.eStateExited)
return {
"status": "target-integrity-error",
"error": "selected executable digest or file identity changed during LLDB launch",
}
module_path_after, module_file_identity_after, module_uuid_after = (
_module_identity(target)
)
if (
module_file_identity_after is not None
and module_file_identity_after != before_identity
):
process.Kill()
_wait_for_exit(listener, process, 5)
stdout.close(expected_exit=process.GetState() == lldb.eStateExited)
stderr.close(expected_exit=process.GetState() == lldb.eStateExited)
return {
"status": "target-integrity-error",
"error": "LLDB executable module resolved to a different file identity",
}
if (
module_uuid_before is not None
and module_uuid_after is not None
and module_uuid_before != module_uuid_after
):
process.Kill()
_wait_for_exit(listener, process, 5)
stdout.close(expected_exit=process.GetState() == lldb.eStateExited)
stderr.close(expected_exit=process.GetState() == lldb.eStateExited)
return {
"status": "target-integrity-error",
"error": "LLDB executable module identity changed during launch",
}
context = {
"target": target,
"process": process,
"config": config,
"started": started,
"breakpoint_index": breakpoint_index,
"retained_frames": 0,
"retained_trace_bytes": 0,
"observation_journal": observation_journal,
}
events = []
context.update({"events": events, "outcome": None, "failure": None})
_TRACE_CONTEXT = context
for breakpoints in created:
for breakpoint in breakpoints:
breakpoint.SetScriptCallbackFunction("rea_lldb_tracer.breakpoint_hit")
other_stops = []
outcome = None
deadline = started + config["duration_ms"] / 1000
event = lldb.SBEvent()
while outcome is None:
if stdout.reader_error is not None:
raise RuntimeError("stdout capture failed: %s" % stdout.reader_error)
if stderr.reader_error is not None:
raise RuntimeError("stderr capture failed: %s" % stderr.reader_error)
if time.monotonic() >= deadline:
outcome = "duration-elapsed"
break
if not listener.WaitForEvent(1, event):
continue
if not lldb.SBProcess.EventIsProcessEvent(event):
continue
state = lldb.SBProcess.GetStateFromEvent(event)
if state == lldb.eStateExited:
outcome = "exited"
elif state == lldb.eStateStopped and not lldb.SBProcess.GetRestartedFromEvent(event):
if context["failure"] is not None:
raise RuntimeError(context["failure"])
outcome = context["outcome"] or _handle_stop(context, other_stops)
if outcome is None:
process.Continue()
pid = process.GetProcessID()
exit_status = None
exit_description = None
if outcome == "exited":
exit_status = process.GetExitStatus()
exit_description = process.GetExitDescription()
else:
process.Kill()
_wait_for_exit(listener, process, 5)
process_exited = process.GetState() == lldb.eStateExited
stdout.close(expected_exit=process_exited)
stderr.close(expected_exit=process_exited)
# SBTarget.Launch may succeed before debugserver reports an asynchronous
# attach failure as an exited SBProcess. Its -1 status is not a target
# exit code; preserve the debugger's concrete launch denial.
launch_failure = _failed_launch_description(process)
if launch_failure is not None:
return {"status": "launch-error", "error": launch_failure}
launch_identity_stable = (
module_path_before is not None
and module_path_after == module_path_before
and module_file_identity_before == before_identity
and module_file_identity_after == module_file_identity_before
and module_uuid_before is not None
and module_uuid_after == module_uuid_before
)
breakpoint_report, breakpoint_locations_truncated = _breakpoint_report(
target, created, config
)
return {
"status": "traced",
"version": lldb.SBDebugger.GetVersionString().splitlines()[0],
"pid": pid,
"outcome": outcome,
"exit_status": exit_status,
"exit_description": exit_description,
"killed": outcome != "exited",
"terminated": process.GetState() == lldb.eStateExited,
"elapsed_ms": round((time.monotonic() - started) * 1000, 3),
"breakpoints": breakpoint_report,
"breakpoint_locations_truncated": breakpoint_locations_truncated,
"events": events,
"resource_limit_reached": outcome == "resource-limit",
"target_output": {
"stdout_bytes": stdout.bytes,
"stderr_bytes": stderr.bytes,
"stdout_truncated": stdout.truncated,
"stderr_truncated": stderr.truncated,
"stdout_complete": stdout.complete,
"stderr_complete": stderr.complete,
},
"target_identity": {
"selected_file_sha256": after_digest,
"loaded_image_sha256": None,
"file_device": after_identity[0],
"file_inode": after_identity[1],
"module_path": module_path_after,
"module_uuid": module_uuid_after,
# This records matching path/module identity observations. LLDB
# does not expose the exact mapped bytes, so it never proves a digest.
"stable": launch_identity_stable,
},
"other_stops": other_stops,
}
finally:
_TRACE_CONTEXT = None
if observation_journal is not None:
try:
observation_journal.handle.close()
except Exception:
pass
if process is not None:
try:
if process.IsValid() and process.GetState() != lldb.eStateExited:
process.Kill()
_wait_for_exit(listener, process, 5)
except Exception:
pass
for output_pipe in (stdout, stderr):
try:
output_pipe.close(
expected_exit=(
process is not None
and process.IsValid()
and process.GetState() == lldb.eStateExited
)
)
except Exception:
pass
def run(debugger, command, result, internal_dict):
+273
View File
@@ -0,0 +1,273 @@
"""Source-owned tests for the LLDB bridge's bounded retention helpers."""
from io import BytesIO
import importlib.util
import json
import os
import sys
import tempfile
import types
import unittest
fake_lldb = sys.modules.setdefault("lldb", types.ModuleType("lldb"))
fake_lldb.LLDB_INVALID_ADDRESS = -1
fake_lldb.eLaunchFlagDebug = 2
fake_lldb.eLaunchFlagStopAtEntry = 4
spec = importlib.util.spec_from_file_location(
"rea_lldb_tracer", os.path.join(os.path.dirname(__file__), "rea_lldb_tracer.py")
)
tracer = importlib.util.module_from_spec(spec)
spec.loader.exec_module(tracer)
class NativeTraceBudgetTests(unittest.TestCase):
def test_journal_checks_exact_byte_budget_before_writing(self):
record = {"kind": "other-stop", "reason": "signal"}
encoded_size = len((tracer.json.dumps(record) + "\n").encode("utf-8"))
stream = BytesIO()
journal = tracer._ObservationJournal(stream, max_bytes=encoded_size - 1)
self.assertFalse(journal.append(record))
self.assertEqual(stream.getvalue(), b"")
self.assertEqual(journal.bytes, 0)
def test_journal_appends_each_row_once_and_flushes_it(self):
class CountingStream(BytesIO):
writes = 0
flushes = 0
def write(self, value):
self.writes += 1
return super().write(value)
def flush(self):
self.flushes += 1
return super().flush()
stream = CountingStream()
journal = tracer._ObservationJournal(stream)
journal.append({"kind": "event", "event": {"sequence": 0}})
journal.append({"kind": "other-stop", "reason": "signal"})
self.assertEqual((stream.writes, stream.flushes), (2, 2))
self.assertEqual(journal.bytes, len(stream.getvalue()))
def test_aggregate_trace_budget_admits_exact_boundary_and_rejects_next_record(self):
self.assertTrue(
tracer._can_retain_trace(
tracer.MAX_TRACE_BYTES - 10,
tracer.MAX_TRACE_FRAMES - 2,
10,
2,
)
)
self.assertFalse(
tracer._can_retain_trace(
tracer.MAX_TRACE_BYTES - 10,
tracer.MAX_TRACE_FRAMES - 2,
11,
2,
)
)
self.assertFalse(
tracer._can_retain_trace(
tracer.MAX_TRACE_BYTES - 10,
tracer.MAX_TRACE_FRAMES - 2,
10,
3,
)
)
def test_oversized_producer_string_is_rejected_before_serialization(self):
self.assertTrue(tracer._fits_trace_budget("method", 1024))
self.assertFalse(tracer._fits_trace_budget("m" * 2048, 1024))
self.assertGreater(
tracer._estimated_json_size("🙂"), len('"🙂"'.encode("utf-8"))
)
def test_launch_flags_preserve_default_debug_and_stop_at_entry(self):
class Launch:
flags = 10
def GetLaunchFlags(self):
return self.flags
def SetLaunchFlags(self, flags):
self.flags = flags
launch = Launch()
tracer._set_launch_flags(launch)
self.assertEqual(launch.flags, 14)
def test_output_pipe_counts_all_bytes_and_keeps_only_the_prefix(self):
with tempfile.TemporaryDirectory() as directory:
fifo = os.path.join(directory, "stdout.fifo")
capture = os.path.join(directory, "stdout.capture")
sink = tracer._OutputPipe(fifo, capture, 5)
sink.start()
sink.mark_launched()
writer = os.open(fifo, os.O_WRONLY)
try:
for index in range(100):
chunk = (b"hello world" if index == 0 else b"x" * 4096)
os.write(writer, chunk)
finally:
os.close(writer)
sink.drain()
sink.close(expected_exit=True)
with open(capture, "rb") as retained:
self.assertEqual(retained.read(), b"hello")
self.assertEqual(sink.bytes, 11 + 99 * 4096)
self.assertTrue(sink.truncated)
self.assertTrue(sink.complete)
def test_output_pipe_reports_incomplete_capture_after_unexpected_close(self):
with tempfile.TemporaryDirectory() as directory:
fifo = os.path.join(directory, "stdout.fifo")
capture = os.path.join(directory, "stdout.capture")
sink = tracer._OutputPipe(fifo, capture, 5)
sink.start()
sink.mark_launched()
sink.close(expected_exit=False)
self.assertFalse(sink.complete)
self.assertTrue(sink.truncated)
def test_selector_location_report_uses_the_requested_method_type(self):
class Symbol:
def IsValid(self):
return True
def GetName(self):
return "+[Greeter shared]"
class FileSpec:
def GetFilename(self):
return "Fixture"
def __str__(self):
return "/tmp/Fixture"
class Module:
def IsValid(self):
return True
def GetFileSpec(self):
return FileSpec()
class Address:
def GetModule(self):
return Module()
def GetSymbol(self):
return Symbol()
def GetFileAddress(self):
return 1
def GetLoadAddress(self, target):
return 2
class Location:
def GetAddress(self):
return Address()
class Breakpoint:
def GetNumLocations(self):
return 1
def GetLocationAtIndex(self, index):
return Location()
report, locations_truncated = tracer._breakpoint_report(
object(),
[[Breakpoint()]],
{
"breakpoints": [
{
"kind": "objc-method",
"selector": "shared",
"method_type": "instance",
}
]
},
)
self.assertEqual(report[0]["location_count"], 0)
self.assertEqual(report[0]["locations"], [])
self.assertFalse(locations_truncated)
def test_breakpoint_report_bounds_aggregate_location_json(self):
tracer.lldb.LLDB_INVALID_ADDRESS = -1
symbol_name = (
"-[VeryLongButPlausibleObjectiveCController "
"performTransactionWithRequestIdentifier:accountContext:"
"securityPolicy:completionHandler:]"
)
module_path = (
"/Applications/Enterprise Product.app/Contents/Frameworks/"
"EnterpriseSubsystem.framework/Versions/A/EnterpriseSubsystem"
)
class Symbol:
def IsValid(self):
return True
def GetName(self):
return symbol_name
class FileSpec:
def GetFilename(self):
return "EnterpriseSubsystem"
def __str__(self):
return module_path
class Module:
def IsValid(self):
return True
def GetFileSpec(self):
return FileSpec()
class Address:
def GetModule(self):
return Module()
def GetSymbol(self):
return Symbol()
def GetFileAddress(self):
return 0x123456789
def GetLoadAddress(self, target):
return 0x7123456789
class Location:
def GetAddress(self):
return Address()
class Breakpoint:
def GetNumLocations(self):
return 64
def GetLocationAtIndex(self, index):
return Location()
breakpoint_count = 1000
created = [[Breakpoint()] for _ in range(breakpoint_count)]
config = {"breakpoints": [{"kind": "objc-method"}] * breakpoint_count}
report, locations_truncated = tracer._breakpoint_report(
object(), created, config
)
retained_bytes = sum(
len(json.dumps(location).encode("utf-8"))
+ (1 if index > 0 else 0)
for row in report
for index, location in enumerate(row["locations"])
)
self.assertTrue(locations_truncated)
self.assertEqual(sum(row["location_count"] for row in report), 64_000)
self.assertLess(sum(len(row["locations"]) for row in report), 64_000)
self.assertLessEqual(retained_bytes, tracer.MAX_BREAKPOINT_LOCATION_BYTES)
if __name__ == "__main__":
unittest.main()
+8 -4
View File
@@ -152,7 +152,14 @@ private func readRunToken(pid: Int32, pointerSize: Int) -> RunTokenObservation {
}
bytes = Array(bytes.prefix(size))
do {
return RunTokenObservation(pid: pid, state: "readable", run_id: try token(in: bytes, pointerSize: pointerSize), reason: nil)
let runId = try token(in: bytes, pointerSize: pointerSize)
// A complete empty environment is readable for an ordinary image. Apple
// platform images can expose the same layout after the kernel withholds
// their environment, so they remain explicitly unverified without a token.
if runId == nil && isPlatformBinary(pid: pid) {
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
}
return RunTokenObservation(pid: pid, state: "readable", run_id: runId, reason: nil)
} catch RunTokenReadError.duplicateToken {
return unavailable(pid: pid, reason: "duplicate_run_token")
} catch RunTokenReadError.invalidTokenEncoding {
@@ -259,9 +266,6 @@ func token(in bytes: [UInt8], pointerSize: Int = MemoryLayout<UnsafeRawPointer>.
throw RunTokenReadError.ambiguousEnvironmentBoundary
}
let environmentEntries = trailingEntries[..<appleVectorStart]
guard !environmentEntries.isEmpty else {
throw RunTokenReadError.environmentUnavailable
}
var found: String?
for entry in environmentEntries {
if entry.starts(with: Array("REA_PROCESS_RUN_ID=".utf8)) {
+4
View File
@@ -43,6 +43,10 @@ def inspect_core(path, cache):
raise CoreFailure("unsupported-target", "Initial recorded-core profile requires ELF64 little-endian.")
if len(content) < 64:
raise CoreFailure("format", "Selected ELF64 core header is truncated.")
if content[6] != 1:
raise CoreFailure("format", "ELF identification version is not EV_CURRENT.")
if int.from_bytes(content[20:24], "little") != 1:
raise CoreFailure("format", "ELF header version is not EV_CURRENT.")
# Linux commonly emits ELFOSABI_NONE (0), also accepted as GNU/Linux (3).
# Other declared OSABIs must never select Linux note ctypes.
if content[7] not in (0, 3) or content[8] != 0:
@@ -252,7 +252,7 @@ No request can cross a concurrent target or provider transition.
canonical path, source digest, generic kind and format, selected architecture,
and available architectures. Hopper loader arguments leave the domain model.
Provider-owned databases use a generic analysis-database classification; their
adapter determines support. A legacy `.hop` target is therefore accepted only
adapter determines support. A `.hop` analysis database is therefore accepted only
by Hopper without teaching the domain how to open it. Ghidra v1 continues to use
private temporary projects and does not open user projects.
@@ -336,19 +336,19 @@ entries, so snapshots must be recaptured before they can be used as a cache.
An embedded Evidence bundle can still be imported separately through the
Evidence import flow; it is never promoted into an analysis cache.
### 8. Add profile commitments to new deep Evidence while preserving legacy records
### 8. Commit concrete or explicitly unknown analysis profiles in Evidence
The optional `analysis_profile` field uses the canonical envelope above. Every
newly created deep-analysis observation must include it, and its provider ID
and version must match the record's provider. The field participates in the
Evidence semantic ID.
Every Evidence record includes `analysis_profile`, either the canonical profile
envelope above or `null` when no profile was observed. Deep-analysis observations
commit the resolved provider ID, version, and effective configuration in this
profile. The field participates in the Evidence semantic ID.
Existing Evidence records without `analysis_profile` remain valid and keep
their existing IDs. Absence on a legacy deep record means the analysis profile
is unknown; REA does not infer or enrich it during import. Such evidence can be
displayed and cited but cannot establish profile compatibility, seed snapshot
replay, or support an "unchanged" conclusion that depends on identical analysis
semantics.
An explicitly unknown profile can be displayed and cited, but cannot establish
profile compatibility, seed snapshot replay, or support an "unchanged"
conclusion that depends on identical analysis semantics. Snapshot query entries
must have matching profile-bound Evidence; imports reject missing or mismatched
observations instead of silently dropping query entries. Both `entries` and
`workflow_entries` are required arrays, including when empty.
Different providers or profile digests are never snapshot-compatible. A
derived comparison may compare cross-provider normalized facts only when its
@@ -407,12 +407,12 @@ means no dynamic provider has supplied a snapshot. Once one or more start,
snapshots are not live inventories, and a point-in-time observation never proves
historical absence.
The existing flat `provider`, `providers`, and `capabilities` status fields stay
during the 1.x compatibility window. Their effective deep-operation entries
come only from the active binding; target-free ambiguity is unavailable rather
than arbitrarily collapsed. The new candidate fields are authoritative for
selection. A future major release may remove the synthetic composite provider
field after callers have migrated.
Status exposes `analysis_provider_candidates` for configured deep providers and
`analysis_provider_binding` for the selected concrete provider and profile.
`capabilities` retains effective operation availability and exact provider
provenance, including auxiliary operation families. There are no synthetic
flat `provider` or `providers` aliases. Provider prompt completion uses the
actual selectable candidates.
Selection failures extend the tagged `ProviderSelectionError` with a stable
reason:
@@ -448,26 +448,20 @@ snapshot's provider.
- Tool names and normalized output contracts remain provider-neutral. REA does
not add `ghidra_decompile` or `hopper_decompile` tools.
## Compatibility and migration
## Provider and snapshot representations
| Existing behavior or data | Decision |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| No provider selector, only Hopper usable | `auto` selects the sole candidate; behavior remains equivalent |
| No deep provider usable | Automatic open may remain unbound for auxiliary operations; deep capabilities explain why they are unavailable |
| Hopper and Ghidra both usable, no preference | Open fails with `ambiguous`; installation order never becomes policy |
| Existing Hopper environment settings | Remain supported as Hopper adapter configuration |
| `open_binary` and CLI structured `loaderArgs` output | Retained as a deprecated Hopper-only compatibility projection for 1.x; the analysis profile is authoritative |
| `BinaryTarget.loaderArgs` | Removed; Hopper derives them inside its adapter and commits normalized semantics in the profile |
| Legacy `.hop` input | Classified generically as an analysis database and accepted only by Hopper |
| Snapshot v1 | Not accepted as the current snapshot format; recapture before cache replay |
| Existing Evidence without a profile | Accepted unchanged; profile compatibility is unknown |
| New deep-analysis Evidence | Must include `analysis_profile`; its semantic ID commits the field |
| Existing status fields | Retained for 1.x with additive binding and candidate fields |
| Provider failure after selection | Returned from the selected provider; no transparent retry through another engine |
The deprecated `loaderArgs` response must never be read to select a provider or
validate a snapshot. It is empty for non-Hopper bindings and may be removed only
with a separately documented major contract change.
| Behavior or data | Contract |
| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| No selector, one usable deep provider | `auto` selects the sole candidate |
| No usable deep provider | Automatic open may remain unbound for auxiliary operations; deep capabilities explain their unavailability |
| Several usable deep providers without a preference | Open fails with `ambiguous`; installation order never becomes policy |
| Hopper environment settings | Adapter configuration committed into the typed analysis profile, including effective loader arguments |
| `open_binary` output | Provider-neutral target identity only; no `loaderArgs` projection |
| `.hop` input | Analysis database accepted only by Hopper |
| Snapshots | Concrete provider/profile binding, required primitive and workflow entry arrays, and matching authenticated Evidence |
| Evidence without an observed profile | Explicit `analysis_profile: null`; ineligible for profile-dependent replay |
| Snapshot save receipt | `primitive_entries`, `workflow_entries`, and `evidence_records` counts without a duplicate `entries` alias |
| Provider failure after selection | Returned from that provider, without transparent retry through another engine |
## Required behavior scenarios
+60 -2
View File
@@ -23,10 +23,37 @@ export REA_JADX_MCP_JAR=/absolute/path/jadx-headless-mcp-0.7.1-all.jar
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:
```powershell
$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 is unsupported for this provider's owned stdio process boundary.
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
@@ -111,7 +138,14 @@ rea project-android-application-graph '{"inventory_evidence":[<inventory_artifac
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.
runtime calls. 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
@@ -123,6 +157,19 @@ 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. An abrupt CLI/server process termination closes the
job and stops Java, but cannot run JavaScript workspace cleanup; temporary
workspace files can remain after that forced exit.
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.
@@ -170,6 +217,17 @@ 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 after abrupt CLI owner termination. Windows forced termination is checked
separately from the POSIX CLI SIGTERM handler; it does not claim a structured
cancellation result or workspace removal after the owner is forcibly killed.
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
+42 -11
View File
@@ -20,16 +20,35 @@ rea project-apple-application-graph '{"inventory_evidence":[<inventory_artifact
}
```
The result includes every component in each inventory category and every
JavaScript-to-native bridge candidate pair. It has no caller-selected component
budget, prefix truncation, or omitted-count fields. Duplicate inventory pages
are merged by artifact identity and occurrence path; their Evidence IDs remain
attached as source 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 and bridge-candidate arrays are still
the complete projection of the inventory that was supplied.
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
@@ -177,10 +196,22 @@ cache rather than on disk, so REA does not check them against the host.
- `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 Mach-O placed at an earlier search path
would load first. Whether code-signing library validation would reject it is
not evaluated; use `inspect_signature`.
- `dyld-environment-present`: `LC_DYLD_ENVIRONMENT` search paths are not modeled.
- `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
+7
View File
@@ -84,6 +84,13 @@ uses isolated mode and an owned cache. Exact upstream profiles are recorded in
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.
Complete results have a 32 MiB input, 64 MiB reply, 1 MiB combined diagnostics
and 30-second owned command deadline. The reply budget applies to the decoder
record; the CLI/MCP Evidence envelope adds copies and encoding overhead. The
+8 -5
View File
@@ -138,7 +138,7 @@ REA retains local observation data and removes only URL userinfo credentials, ex
- Console observations with a stack source retain call type, argument types, timestamp, and source location. 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. When shape capture is selected, text frames are classified as text or complete value-free JSON shape; binary bytes, hashes, prefixes, and raw frames 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 bodies above 64 KiB and partial IndexedDB remote objects make fingerprint coverage incomplete, so identical observations remain `unknown`.
- Script metadata is included only when CDP supplies a URL on an allowed origin. Stable keys exclude transient CDP script IDs, and exact transient raw URLs are used only during script/resource reconciliation. URL-less scripts are excluded because their origin cannot be established. When CDP supplies an execution-context association, the accepted script retains its authorized frame ID for later attribution. Source content is omitted unless explicitly requested and is returned inline as a self-verifying artifact with its SHA-256 digest.
- Script metadata is included only when CDP supplies a URL on an allowed origin. Stable keys exclude transient CDP script IDs, and exact transient raw URLs are used only during script/resource reconciliation. URL-less scripts are excluded because their origin cannot be established. When CDP supplies an execution-context association, the accepted script retains its authorized frame ID for later attribution. Script metadata admission bounds retained identity and source-map strings to 8 MiB and 50,000 records. When a source-map URL cannot fit, the script identity remains and source-map coverage reports `resource_budget_exhausted`; rejected script observations are counted without retaining their IDs. Source content is omitted unless explicitly requested and is returned inline as a self-verifying artifact with its SHA-256 digest.
Frames, resources, scripts, events, and workers outside the selected origin scope are excluded. An explicit `allowed_origins` list can include additional origins; excluded target details are counted without being exposed.
Retained workers include validated opener-target and parent-frame IDs when CDP
@@ -148,18 +148,21 @@ prove static module ownership.
## Completeness and limits
The default inspection window is 500 ms; callers choose its duration. The
capture returns all in-scope frames, DOM and accessibility nodes, scripts,
capture returns in-scope frames, DOM and accessibility nodes, admitted scripts,
resources, workers, storage names, and observed network, console, and WebSocket
events. Full selected text, script sources, and JSON shapes are returned inline.
The result distinguishes
`complete_within_window`, `policy_filtered`, `attach_limited`, and `truncated`
coverage. It reports excluded items and any values omitted or truncated by
validation; it does not drop records to meet an internal collection quota.
validation and resource bounds. Omitted script metadata makes its affected
section explicitly truncated.
Disallowed-origin entries are filtered before retention.
CDP discovery, WebSocket connection, and commands continue until the peer responds or the caller cancels the operation; REA has no internal byte, message-count, or pending-command quota for these exchanges. Without caller cancellation, a stalled peer can leave an operation waiting. On cancellation, REA closes only its own WebSocket, waiting up to one second before terminating it. Malformed unsolicited protocol events poison the connection so later commands fail closed.
CDP discovery, WebSocket connection, and commands continue until the peer responds or the caller cancels the operation; REA has no internal byte, message-count, or pending-command quota for these exchanges. Without caller cancellation, a stalled peer can leave an operation waiting. On cancellation, REA 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. When a correlated reply reports a session ID, it must match the command's selected session. An optional method is reported unavailable only when the browser 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. 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. Every requested map, mapping, original source, AST node, and finding from parsed content is returned inline; failed maps remain explicitly unavailable and make bundle completeness partial. Original source artifacts with `.ts`, `.tsx`, `.mts`, or `.cts` suffixes carry `text/typescript`; other source names retain `text/javascript`.
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. 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 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`.
## Non-goals and threat model
+7
View File
@@ -109,6 +109,13 @@ 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.
The MCP save receipt reports `primitive_entries`, `workflow_entries`, and
`evidence_records` separately. Zero primitive bindings can still accompany retained workflow
results and Evidence. These are cached observations, not a saved provider
database: only eligible exact queries can reuse a result, and new or live
queries can still require provider startup. The CLI uses the same snapshot
format and preserves these records when loading and updating it.
## Import, export and compare Evidence
Evidence records retain artifact identity, source locations, observations,
+23 -6
View File
@@ -86,16 +86,33 @@ rea capture-electron-scenario scenario.json --json
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 are capped at
1,024 characters and argument-shape metadata at 32 entries; values are never
retained. Renderer crash/restart and deep-link actions are synthetic
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 external shell/navigation,
permission, download, popup, updater, and OS-integration effects. The timeline
is explicitly partial when attachment starts after application activity. The
owned experiment has an internal 60-second deadline and a 5-second per-action
timeout so hung work reaches process cleanup; these are lifecycle timeouts, not
caller-selected capture limits.
startup has a 60-second deadline. Actions wait until completion or request
cancellation; a click whose target never appears remains pending until cancelled.
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.
Window instrumentation uses Electron's window-created event, including runtimes
with a read-only `BrowserWindow` export. Preload configuration comes from the
window's runtime metadata, including Electron's optional internal preload
descriptor when web preferences omit it. This descriptor is verified on
Electron 44.7.0; runtimes that expose neither representation retain an unavailable
path. A configured path or working IPC handler does not prove preload execution.
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.
The real verifier exercises compiled CLI and stdio MCP discovery, page/script
inspection, static/runtime reconciliation, active observation, and CLI SIGTERM
cleanup before checking the IPC, renderer crash/reload, utility/child-process,
deep-link, and blocked-effect fixture. A cleanup error keeps the full lane failed
even when the retained capture contains useful observations.
Active Electron Evidence can also be supplied to
[`reconcile_javascript_runtime`](javascript-runtime-reconciliation.md). That
+3 -2
View File
@@ -129,8 +129,9 @@ tree semantics, vulnerability scanning, emulation or hardware runtime capture.
Unblob one worker. No automatic parallel extraction engines.
- **120-second** deadline; inherited **1 GiB per-process address-space** limit,
120 CPU seconds and zero core-dump allowance through `prlimit`.
- JSON reports: at most **8 MiB**; oversized reports fail without a truncated
success. Diagnostic output is bounded with exact captured byte counts.
- JSON reports: at most **8 MiB** and valid UTF-8; malformed encoding and
oversized reports fail without repairing or truncating producer data.
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`). Depth defaults to 3, allowed range 1–10.
+6 -2
View File
@@ -1,7 +1,7 @@
# DOS MZ and COM analysis with Ghidra
REA can analyze DOS MZ executables and explicitly selected COM images through the bring-your-own Ghidra adapter.
The Linux x64 verification lane exercises actual 16-bit disassembly and
The Linux x64 and macOS arm64 verification lanes exercise actual 16-bit disassembly and
decompilation through the CLI and stdio MCP. Provider admission accepts Ghidra
12.1.x and that installation's declared JDK range. The verification lane uses
Ghidra 12.1.4 and JDK 21; no DOS emulator or cross-compiler is required.
@@ -87,6 +87,10 @@ address for subsequent lookups and leaves instruction decoding context with
Ghidra. These coordinates are analysis load addresses, not file offsets or
observations of a running DOS system.
Offsets that Ghidra would truncate to a different address are rejected before
reading or annotating a function. This also applies to explicit or encoded
address-space names; leading zeros preserve the numeric offset.
`procedure_info` and function dossiers include `body` evidence:
```json
@@ -150,7 +154,7 @@ are separate from the initialized MZ module. Raw COM files and DOS extenders
without an admitted MZ real-mode entry are outside this import boundary.
DOS MZ is available through Ghidra, with no Hopper support claim. It is outside
the Windows native PE P0 boundary. macOS DOS verification remains unverified.
the Windows native PE P0 boundary. macOS x64 DOS verification remains unverified.
## Real verification
+187 -18
View File
@@ -119,7 +119,8 @@ readiness for one task instead of auditing every integration, see
## Supported agents
Setup can configure these clients for REA's local MCP server:
Setup can configure these clients for REA's local MCP server. Grok Bot is
listed after the table because its connector is not one of these files:
| Client | `--client` value |
| ------------------ | ---------------- |
@@ -135,12 +136,37 @@ Setup can configure these clients for REA's local MCP server:
| GitHub Copilot CLI | `copilot_cli` |
| Command Code | `commandcode` |
| VS Code | `vscode` |
| Grok Build | `grok_build` |
For OpenCode, setup writes the V1 `mcp.rea` entry, which OpenCode V1 and V2
both load. If the configuration already uses OpenCode V2's native
`mcp.servers` table, setup registers REA there instead and replaces any earlier
`mcp.rea` entry from REA.
Grok Build loads `[mcp_servers.rea]` from `$GROK_HOME/config.toml`, or from
`~/.grok/config.toml` when `GROK_HOME` is unset. Setup edits that server
table, `[mcp_servers.rea.env]`, and a root `disabled_mcp_servers` entry that
names `rea`. It sets `startup_timeout_sec = 30` and leaves every other name
in that list. The shared skill installed under `~/.agents/skills` is already
on Grok Build's skill path.
Grok Bot (`grok_bot`) is detected from `~/.grokbot`, or from `SAND_DATA_ROOT`
when that value is an absolute path. A relative or empty `SAND_DATA_ROOT`
stays on `~/.grokbot`. That directory is not the connector store. Grok Bot
keeps connectors in the signed-in account and runs them on its hosted
computer. It does not import `mcp.json` from the data directory, and it does
not attach a stdio server running on this machine. Setup does not call the
account connector API, does not write a registration file, and does not report
the data directory as aligned. Ask the Grok Bot chat to add a custom MCP
server named `rea` that runs on the Bot's computer:
```bash
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.
## Review setup changes
`rea setup` first offers the supported agents in a multi-select. Existing REA
@@ -169,8 +195,9 @@ 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. Agent configuration writes preserve unrelated
entries, create backups, use atomic replacement, and verify their result.
changes. Agent configuration writes preserve unrelated entries and comments,
create backups, use atomic replacement, and verify their result. Setup and
uninstall retain an existing `.rea.backup` rather than replacing the first snapshot.
After setup, REA reports which agents, analysis tools, and workflow files passed
its final checks. Restart any agent named in the completion message, then begin
@@ -209,7 +236,7 @@ rea setup --yes --all-detected --install-hopper --json
Setup pins package-runner MCP registrations to the exact installed REA version,
installs the matching skill and on-demand references in the same plan, and adds
`startup_timeout_sec = 30` for Codex. `rea update` installs the exact resolved
`startup_timeout_sec = 30` for Codex and Grok Build. `rea update` installs the exact resolved
release into the npm prefix that owns the running package, then checks the new
executable's version before reporting success. It does not reopen onboarding.
Release lookup and installation both use npm's configured registry.
@@ -284,6 +311,19 @@ host mount and the rest of `/tmp` remain unchanged; this fallback never invokes
`sudo`. `rea doctor --provider hopper --json` reports the selected
strategy and both host and effective mount facts.
The supported Linux demo forwards additional launches into its existing
application, even across private displays. REA therefore reserves one Linux
Hopper application per user across cooperating REA processes. A competing
CLI or MCP session receives the owning session ID before Hopper is launched,
for both the same target and a different target. Switch targets through that
MCP session, or close it before starting another session. Closing the owner
releases the reservation. This does not attach to or claim ownership of
manually opened Hopper applications.
Stale lease recovery is serialized. If an interrupted recovery leaves its
reservation directory, the diagnostic identifies that exact path. Stop REA
sessions before removing that recovery directory and retrying.
### Launcher paths and troubleshooting
On macOS, REA uses
@@ -314,8 +354,57 @@ Hopper serializes analysis requests. Cancelling a wait can leave provider work
running, which the session reports. Successful decompilation is cached until
a relevant rename or comment changes it.
Closing a session shuts down REA's bridge and removes its temporary socket
directory while preserving the Hopper application. A `cleanup_incomplete`
Analysis and annotation calls stay bound to the active target's native Hopper
document, even when GUI focus changes or other documents have the same display
name. Use `open_binary` to change targets. Byte reads stop at a segment boundary
and return the readable prefix with `complete: false`. File-offset mapping checks
the reverse lookup and original executable bounds; synthetic external-symbol
memory has no original file offset. For FAT Mach-O, offsets refer to the original
container file. Results retain Hopper's image-relative offset, the observed slice
base, and the source executable path.
Loader selection reads the Mach-O container header, including FAT files with a
single architecture. For FAT64, REA validates the architecture table and selected
Mach-O header, prepares a private thin image, and loads it with Hopper's native
Mach-O loader. It checks the entire source's SHA-256 while copying the slice;
source identity and reported offsets still refer to the original container.
An ambiguous architecture subtype requires an explicitly extracted thin image;
REA does not guess. Configured loader arguments remain explicit overrides.
FAT64 is distinct from the CPU architecture: FAT32 containers can contain 64-bit
executable images. This preparation avoids the Raw Binary loader dialog observed
with native FAT64 loading on Hopper 6.1.0-demo.
The prepared image and its owned document close together, including on MCP exit;
if document closure is unconfirmed, REA retains the backing image and reports
`cleanup_incomplete` with its path. Ordinary documents retain their existing
MCP-exit behavior.
Startup deadlines report missing bridge readiness and preserve the launcher
outcome; a successful helper exit does not prove that a loader dialog completed.
Cursor navigation returns the observed object start when Hopper snaps an interior
address; adjacent-object navigation rejects unmapped inputs and document ends.
Native API text rejects NUL characters and unpaired Unicode surrogates before
annotation changes. Renames preserve unselected label owners; use a batch with
all affected addresses to move or swap existing labels explicitly. Every rename
destination must be mapped, and native symbol names must fit Hopper's 1024 UTF-16
code-unit limit. Oversized names fail before any batch edits; bookmarks and
literal string results are not subject to that symbol-name limit. Rename success
requires exact final readback. New bookmarks must point into mapped memory;
existing legacy bookmarks outside it can still be removed.
String results read each native typed object's complete bytes, retain the original
provider display in `provider_value`, and report its encoding, byte length, and
termination. `encoding_status: inferred` distinguishes REA's decoding from an
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 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.
Regex searches use ECMAScript Unicode syntax in a cancellable worker with a
five-second matching deadline. Deadline or cancellation stops matching while
leaving the Hopper API available. Literal mode retains Unicode casefold matching.
Closing or switching a target closes its bound Hopper document, shuts down REA's
bridge and removes its temporary socket directory while preserving the Hopper
application and unrelated documents. A `cleanup_incomplete`
result identifies resources whose cleanup could not be verified.
### Hopper in CI
@@ -340,14 +429,17 @@ copying license secrets into logs, or killing unrelated Hopper processes.
## Ghidra
REA connects to an existing Ghidra installation on Linux x64, macOS x64/arm64,
REA connects to an existing Ghidra installation on Linux x64/arm64, macOS x64/arm64,
or experimental Windows x64 P0.
It accepts Ghidra 12.1.x and the 64-bit full JDK declared by that installation's
`application.java.min` and `application.java.max`. Current 12.1 releases require
JDK 21 or newer and set no maximum. The bridge is verified with Ghidra 12.1.4
and JDK 21. On macOS, the installation
must include the native decompiler for the host architecture; REA does not
build it or change Gatekeeper quarantine settings.
and JDK 21. Each installation must include the native decompiler for the host
architecture in `Ghidra/Features/Decompiler/os/<platform>/` or the corresponding
`build/os/<platform>/` directory. Linux ARM64 uses `linux_arm_64`; official
release archives may require you to build that native component separately.
REA checks the executable prerequisite and does not build or install native
tools, or change Gatekeeper quarantine settings.
The adapter exposes 25 read-only operations: thirteen inventory/name/search
operations and twelve function-analysis operations. These cover metadata,
@@ -401,17 +493,32 @@ installs, upgrades, or modifies Ghidra or Java.
Each verified session uses an ephemeral temporary project and isolated
home/cache/config/temp paths. REA passes `-readOnly`, `-deleteProject`, uses
Ghidra's default analysis and resource settings, and loads its packaged Java
Ghidra's default analysis settings, and loads its packaged Java
bridge via `-scriptPath`; it never opens an existing user project. Linux and
macOS use a current-user-only local bridge socket and descriptor. The
experimental Windows transport uses authenticated IPv4 loopback with a
project remains under the selected temporary directory. If its Unix socket
pathname would exceed the host's byte limit, REA allocates a separate mode-0700
socket directory under `/tmp` and removes it on close, cancellation, or failure.
Diagnostics retain the actual endpoint and both owned directories.
On Linux and macOS, REA starts the inspected JVM directly using Ghidra's own LaunchSupport
configuration. Apple platform shell wrappers hide their environments from
ownership inspection, so retaining those wrappers would prevent verified
process-group cancellation during startup.
If ownership remains unverifiable, it reports the reason and retains the process
supervisor and private runtime instead of removing files beneath a live provider.
The experimental Windows transport uses authenticated IPv4 loopback with a
private native-owned bearer descriptor and Job Object process ownership.
Operations begin only after default auto-analysis completes. `open_binary`
selects and validates the target/provider binding; it does not wait for Ghidra
import and auto-analysis. The first Ghidra-backed query starts that work lazily.
The provider startup deadline is 330,000 ms for import, analysis, bridge, and
health readiness; a startup failure is returned by the query that triggered it,
The provider startup deadline is 330,000 ms by default for import, analysis,
bridge, and health readiness. Large binaries can need more: set
`REA_GHIDRA_STARTUP_TIMEOUT_MS` to an integer between 1 and 2,147,483,647
milliseconds in the server environment. An absent setting uses the default;
empty, invalid or out-of-range supplied values fail configuration validation.
REA parses the supplied configuration environment and
passes the deadline to each provider client; a startup failure is returned by the query that triggered it,
without exposing partial analysis. This deadline is separate from MCP transport
initialization and the client's deadline for that individual tool call. A client
can time out earlier even when Ghidra would complete within its startup deadline.
@@ -427,6 +534,65 @@ 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.
### Ghidra heap and CPU controls
Set resource controls in the environment that launches the CLI or MCP server,
before opening a new Ghidra session. On Linux and macOS, REA chooses the first
nonempty setting in this order: `GHIDRA_HEADLESS_MAXMEM`, `GHIDRA_MAXMEM`, then
`2G`. The chosen value becomes the JVM's `-Xmx` argument. Use a JVM heap-size
value such as `512M` or `2G`; an invalid value causes Java startup to fail.
The supported Ghidra 12.1.4 Windows headless script consumes the same settings
with the same precedence.
For a small target on a constrained host:
```bash
export GHIDRA_HEADLESS_MAXMEM=512M
```
In PowerShell:
```powershell
$env:GHIDRA_HEADLESS_MAXMEM = "512M"
```
An MCP client must pass this setting to its REA server process; changing an
unrelated terminal's environment does not reconfigure an existing server or
JVM. A 512 MiB heap was verified with small native fixtures, but larger targets
can require more memory. The Java heap limit does not bound JVM RSS, Node's
heap, or total memory used by the process family. The startup deadline controls
waiting time independently of these memory settings.
REA clears `_JAVA_OPTIONS`, `JAVA_TOOL_OPTIONS`, `JDK_JAVA_OPTIONS`, and
`GHIDRA_JAVA_OPTIONS` inherited from the caller, and owns
`GHIDRA_HEADLESS_JAVA_OPTIONS`. Custom flags injected through these variables
are not supported configuration. On Windows, REA replaces `JDK_JAVA_OPTIONS`
with its own isolated home/temp settings and `-XX:-UsePerfData`; on Linux and
macOS it supplies isolated paths directly to Java. Use the supported heap
settings above instead of adding a second `-Xmx` through an injection variable.
The Linux/macOS launch sets `-XX:ParallelGCThreads=2` and
`-XX:CICompilerCount=2`; the supported Windows headless script also sets these
counts. These constrain particular JVM thread pools, not all analysis threads
or CPU usage. REA does not pass Ghidra's `-max-cpu` option or expose a general
Ghidra CPU limit. When needed, apply host CPU affinity and scheduling controls
to the REA process and verify the resulting JVM's affinity. Select CPUs allowed
on the host; do not assume CPU numbers from another machine are valid.
Verify controls at their consumer after the first Ghidra-backed query starts
the JVM: inspect that owned JVM's effective `-Xmx` and GC/compiler flags, CPU
affinity, and observed memory use. Do not infer them from the parent shell's
environment or the absence of an error. On Linux, `/proc/<jvm-pid>/cmdline`
contains the launch arguments and `/proc/<jvm-pid>/status` reports
`Cpus_allowed_list` and `VmRSS`. Inspect only the process belonging to this
session and retain the resource flags needed for the measurement rather than
copying full command lines or environments into reports. Heap and affinity
settings affect cost and latency; they are not part of REA's semantic analysis
profile identity. See [real-provider testing](testing.md) for bounded
verification workflows.
### Ghidra diagnostics and verification
Unexpected request failures retain their original internal cause. CLI and MCP
errors expose Error names, messages, and available codes under
`details.diagnostics.failure_cause`; other rejection values retain their
@@ -436,7 +602,7 @@ continues. These messages redact known bridge authentication tokens and
preserve local paths and other analysis context.
Run `GHIDRA_INSTALL_DIR=... npm run verify:ghidra` from a source checkout to
compile and analyze debug and stripped host-native fixtures (ELF on Linux x64
compile and analyze debug and stripped host-native fixtures (ELF on Linux x64/arm64
or Mach-O on macOS), plus a native DWARF 4 type-layout object. This lane needs a
host C compiler in addition to Ghidra and its JDK.
@@ -468,8 +634,11 @@ rea uninstall --purge-data
Uninstall preserves Hopper, Node.js, Evidence files, captures, unrelated skills
and other MCP servers. Purging removes only REA's cache and state under
`~/.rea`; malformed client configuration or purge-path symlinks stop the
operation. See the [CLI guide](cli.md#output-and-exit-status) for exit statuses.
`~/.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. A purge path that is a
symbolic link is retained and reported rather than followed. See the
[CLI guide](cli.md#output-and-exit-status) for exit statuses.
## MCP Registry
@@ -490,7 +659,7 @@ For a client that requires manual configuration, use:
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents@6.0.0", "mcp"]
"args": ["-y", "rea-agents@6.1.0", "mcp"]
}
}
}
+37 -15
View File
@@ -2,12 +2,15 @@
REA derives a complete reachable feature trace from one authenticated JavaScript
Application Graph and compares two authenticated graph versions. The MCP tools
are `trace_application_feature`, `trace_javascript_semantics`,
`compare_application_versions`, `compare_source_to_bundle`, and
`compare_javascript_export_shapes`; their CLI equivalents use the same names
with hyphens.
are `inspect_analysis_view`, `trace_application_feature`,
`trace_javascript_semantics`, `compare_application_versions`,
`compare_source_to_bundle`, and `compare_javascript_export_shapes`; their CLI
equivalents use the same names with hyphens. `inspect_analysis_view` projects a
summary, module page, or one module from already completed application
Evidence; it does not walk relationships.
These workflows consume Evidence produced by
`inspect_analysis_view` also projects retained `inspect_binary_layout`
Evidence. The remaining workflows consume Evidence produced by
`analyze_javascript_application` or `reconcile_javascript_runtime`. They do not
read an artifact, execute application code, attach to a process, or open a
native-analysis provider. Static artifact observations, passive runtime
@@ -138,14 +141,33 @@ Nested callable returns are not assigned to their parent callable. Projected
graph observations carry source ranges but never source text.
Return variants pair only when a literal discriminant such as `/type` has one
unique occurrence on each side and pairing is reciprocal. Changes use JSON
Pointer paths with `added`, `removed`, `changed`, or `unknown` status. A missing
field is added or removed only when the relevant parent-property coverage is
complete on both shapes. The output includes exact selector candidates,
omissions, Evidence links, coverage, and limitations; it does not execute
JavaScript. When runtime semantics matter, run behavioral probes directly
against the relevant application versions and capture them through the
available browser, Electron, or process workflows.
unique occurrence on each side and pairing is reciprocal. Source order never
implies correspondence. Each retained variant contributes a property inventory
of observed JSON Pointer names, including fields whose static values stay
unknown, plus that variant's parent-property coverage.
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. 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
one-sided names `unknown` with `unknown-coverage` on the incomplete side.
Unpaired variants remain visible as unknown changes and still list their
property inventories, each with its own `source_range`. Inventories exclude
array holes and slots whose presence was invalidated by mutation; an unresolved
value alone does not make an observed property uncertain. `summary.added` and
`summary.removed` count
presence-level add/remove as well as literal value add/remove;
`summary.unknown` does not absorb complete-coverage presence-only gaps.
The output includes exact selector candidates, omissions, Evidence links,
coverage, and limitations; it does not execute JavaScript. When runtime
semantics matter, run behavioral probes directly against the relevant
application versions and capture them through the available browser, Electron,
or process workflows.
## CLI and verification
@@ -167,8 +189,8 @@ writeFileSync("trace-input.json", JSON.stringify({
rea trace-application-feature ./trace-input.json --json
```
MCP analysis returns an envelope containing `result`, `evidence_id`, and full
`evidence`. If the connected server advertises retained references, reuse its
MCP analysis returns the complete Evidence record, with the operation result
in `normalized_result` and its identity in `evidence_id`. If the connected server advertises retained references, reuse its
exact returned ID as `{"kind":"retained-evidence","evidence_id":"RETURNED_ID"}`
in `application`, or `left`/`right` for comparisons. `RETURNED_ID` is a template,
not a literal valid ID. Native Evidence arrays still use complete records.
+41 -6
View File
@@ -108,14 +108,15 @@ memory needed to construct the analysis graph itself.
MCP prepares the complete repeated response incrementally against the pinned
SDK's 10 MiB stdio receive-buffer budget. Oversized results return an actionable
transport constraint and the exact same-session Evidence reference. Use
`trace_application_feature` to inspect a selected module's relationships, or
`export_evidence_bundle` to write the complete canonical bundle without a
document-sized allocation. Same-session analysis reads reuse authenticated
immutable snapshots; foreign inline Evidence is still parsed and authenticated.
`inspect_analysis_view` with the retained Evidence ID for a summary, one
module, or a stable page of module identities. Use `trace_application_feature`
once a module, route, or string seed is known, or `export_evidence_bundle` to
write the complete canonical bundle without a document-sized allocation.
Same-session analysis reads reuse authenticated immutable snapshots; foreign
inline Evidence is still parsed and authenticated.
See [MCP tool results](mcp-contracts.md#tool-results) for larger client buffers
and `REA_MCP_MAX_RESPONSE_BYTES`. Follow-up results remain complete and can also
exceed the transport budget. Compact result views are tracked separately in
[#1050](https://github.com/morluto/rea/issues/1050).
exceed the transport budget.
## What is reconstructed
@@ -125,6 +126,13 @@ SHA-256 digests, inventory IDs, ASAR container identity, and `.asar.unpacked`
status. Direct ASAR inputs and filesystem-backed ASAR files nested beneath a
directory are supported.
JavaScript sources (`.js`, `.jsx`, `.mjs`, and `.cjs`) and TypeScript sources
(`.ts`, `.tsx`, `.mts`, and `.cts`) are parsed as inert text; analysis does not
execute them.
JavaScript and HTML source ranges retain an initial UTF-8 BOM as one UTF-16
code unit, matching the original bytes identified by the artifact digest.
If an ASAR declares an unpacked companion entry but the corresponding
`<archive>.unpacked` file is absent from the operator-supplied artifact set, REA
keeps the ASAR occurrence with `hash_status: unavailable`, records an explicit
@@ -133,6 +141,9 @@ missing bytes. This allows static JavaScript/Electron reconstruction to proceed
for the embedded files while preserving the missing native/resource bytes as an
unknown instead of silently treating them as absent or verified.
Plain `.ts` artifact sources use TypeScript syntax without JSX, including
angle-bracket type assertions. `.tsx` and `.jsx` retain JSX parsing.
Selected bounded text is then parsed as inert data to recover:
- `package.json` metadata and declared main or renderer entrypoints;
@@ -156,6 +167,12 @@ Selected bounded text is then parsed as inert data to recover:
- utility-process entrypoints and native `.node` binding requests without
parsing or executing the add-on.
Overloaded `.open` calls use lexical receiver facts: ambient browser window and
document receivers are treated as browsing-context or document operations,
while locally shadowed receivers can still contribute network endpoint
candidates. A template recovered with a parser error and no cooked value stays
dynamic; its raw spelling is not treated as a valid JavaScript string.
Each recovered bundle module retains the exact factory-source digest. A complete
bounded AST also receives a `babel-ast-v1` structural fingerprint that ignores
ordinary identifier names while retaining syntax, literals, operators, object
@@ -217,6 +234,22 @@ These safeguards are not caller-selectable output budgets. Source-map parsing
reports truncation when its format or parser safety boundary is reached. The
application graph has no aggregate node, edge, root, or observation prefix cap.
Semantic evaluation retains at most 256 distinct primitive candidates per
expression and at most 1 MiB of worst-case JSON string bytes across a normalized
primitive value's string candidates. The byte budget estimates six JSON bytes
per UTF-16 code unit, the expansion bound for escaped strings, and bounds both
retained candidate strings and temporary canonical-key serialization. This is
an in-memory semantic allocation bound independent of CLI or MCP transport
budgets. Source literal bytes remain in the parsed source and artifact evidence;
when a normalized semantic value exceeds the bound, its value is unknown and
coverage is partial. Expression
evaluation and provenance walks stop after 256 nested levels. Union products
and string lengths are checked before allocating combinations or concatenated
strings. When a boundary is reached, the value stays unknown, semantic coverage
becomes partial, and graph evidence retains the expression location and typed
limiting reason. The number of unresolved alternatives is unknown; it is not
reported as an exact omission count.
Byte, entry, path, and graph-shape bounds are hard limits. The parse deadline is
checked before and between bounded parsing and traversal phases; the synchronous
Babel and JSON parser calls cannot be preempted mid-call, so their input byte
@@ -265,3 +298,5 @@ document base and query/fragment rules. CommonJS module lookups retain extension
and directory resolution.
Unresolved HTML references retain their declaration, source range, and resolution
reason in the renderer observations.
HTML script source ranges follow the HTML parser across LF, CRLF, and bare CR
line endings, preserving UTF-16 columns.
+12 -6
View File
@@ -119,12 +119,18 @@ targets belong to the same Electron application.
## Capture and determinism
Every observation records its time window and returns all valid script and
execution-context events received during it. Per-location protocol validation
remains in force. Scripts are validated after capture, deduplicated by stable
metadata, and canonically sorted. Wall-clock timestamps and protocol script IDs
are excluded from the durable result, so identical inputs and captured metadata
produce the same Evidence ID.
Capture retains valid script and execution-context metadata within an 8 MiB
aggregate budget, including conservative estimates for UTF-16 strings, record
and collection overhead, and projected URL fields. Each CDP message also has a
16 MiB transport limit. A message over that limit fails with `payload_limit`;
when the aggregate budget is reached, later metadata events are dropped and
the partial result reports `events_dropped`, `truncated: true`, and
`retained_metadata_budget_exceeded`. Repeated context transitions update the
current context record instead of accumulating duplicate objects. Scripts are
validated after capture, deduplicated by stable metadata, and canonically
sorted. Wall-clock timestamps and protocol script IDs are excluded from the
durable result, so identical inputs and captured metadata produce the same
Evidence ID.
Inspector attach is inherently incomplete. Enabling `Debugger` reports known
and uncollected scripts, but scripts collected before attachment may be
+6
View File
@@ -137,6 +137,12 @@ function implements a specific managed method.
## Evidence record shape
Member comparison, application graph projection, and managed/native verification
authenticate supplied inspection Evidence and check that any subject SHA-256
matches the normalized artifact SHA-256. A valid Evidence ID alone does not
establish that those two identities agree. Evidence without a subject remains
usable, with no subject digest available to cross-check.
Every planned operation returns a provider result and Evidence with four
commitment groups:
+76 -34
View File
@@ -2,9 +2,9 @@
## Generated catalog
Run `npm run build:cached` in a source checkout to generate the machine-readable
catalog at `docs/public/product-catalog.json`. The [catalog](product-catalog.json)
is also included in documentation builds. PR CI retains it with the packaged
Run `npm run docs:generate` in a source checkout to generate the machine-readable
catalog at `docs/public/product-catalog.json`. Documentation deployments serve
the same file at [/rea/product-catalog.json](/rea/product-catalog.json). PR CI retains it with the packaged
skill and portable conformance projections in the `generated-docs` artifact.
These outputs describe the exact source revision being built; they are not
checked-in snapshots. For a running server, `binary_session` remains the
@@ -24,22 +24,28 @@ health transition leaves that catalog unchanged and does not emit
`notifications/tools/list_changed`.
Advertised input and output schemas contain no reachable recursive references.
The input compatibility profile limits nesting to ten object, array, and
`anyOf`/`oneOf`/`allOf` levels, following local references per path. Property
maps, reference definitions, and example data do not add schema levels. Tests
check the complete catalog after SDK conversion and its generated counterpart.
This is REA's local compatibility profile; individual model APIs can impose
additional limits.
Schemas share repeated definitions through schema-local references while
preserving complete fields and validation rules. Input properties retain their
descriptions and literal examples. The SDK advertises an object root for
canonical object unions and retains each branch's requirements and exclusions.
Tests validate the schemas after SDK conversion and compare advertised
validation with actual calls. Individual model APIs can impose additional
nesting limits; complete producer captures can exceed ten structural levels.
For passive `compare_web_captures` inputs, pass each complete
`inspect_web_page` result in `before.inspection` or `after.inspection`, with an
optional complete `discover_webmcp_tools` result in the matching `webmcp` field.
The input schema describes these producer-result objects as round-trip payloads.
For scenario comparisons, pass complete `capture_browser_scenario` results in
`before_scenario` and `after_scenario`. REA validates all nested fields with the
original capture schemas; observation output schemas remain complete. Scenario
authoring inputs and comparison normalization options retain their full
advertised structure.
`compare_web_captures` accepts exactly one of two input shapes:
- Passive: `before` and `after` each contain `inspection`, the complete
`normalized_result` from `inspect_web_page`. Each may also contain `webmcp`,
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": [] }`.
The advertised schema includes complete nested capture fields and rejects
incomplete pairs, mixed comparison families, and structurally malformed
captures. Domain validation additionally checks relationships such as event
sequence references and retained counts; JSON Schema does not express those
cross-field invariants.
Call `binary_session` with `{}` and read `result.tool_availability` to choose a
callable operation for the current target, provider, host, and negotiated client
@@ -78,12 +84,30 @@ Provider calls receive the request cancellation signal. Artifact traversal,
hashing, version comparisons, Hopper requests, and process capture
check the same signal. Cancellation is distinct from timeout. A cleanup failure
uses `cleanup_incomplete` and lists only the owned resource kinds that remain.
Native call tracing and process capture retain available observations in
`details.partial_observation` on failure, including when cleanup succeeds.
The observation reports its partial coverage; cleanup details describe host
state separately from the execution failure.
Derived comparisons and reconstruction verification yield before computation
and before publication, so cancellation cannot race with successful Evidence.
CLI calls work without a progress token and translate SIGINT into the same
AbortSignal used by providers. Existing controlled-process cleanup and provider
shutdown rules still apply; REA never kills a process it cannot prove it owns.
`analyze_javascript_application` also yields between reconstruction phases and
during graph/result sealing, cross-graph binding checks, Evidence JSON validation,
and canonical hashing. Its final result validation reuses exact graphs whose
owned constructors validated and completely sealed them; imported graphs still
receive full schema and commitment checks.
Cancellation observed before completion returns `cancelled` and prevents the
provisional result from entering the session ledger; prior Evidence stays usable.
Single-file parsing, graph construction, and validation of imported graphs
still run synchronously, so control messages can wait for those
phases to release the event loop. A rejected client promise alone does not
establish that the server has stopped its work.
Direct CLI analysis calls work without a progress token and translate SIGINT or
SIGTERM into the same AbortSignal used by providers. Signal guards remain active
through provider cleanup, including repeated delivery by package runners.
Existing controlled-process cleanup and provider shutdown rules still apply;
REA never kills a process it cannot prove it owns.
## Ghidra first-query deadlines and recovery
@@ -95,11 +119,11 @@ 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. |
| Ghidra startup | REA allows 330,000 ms for engine readiness from the first provider query; startup failure is reported by that query. |
| 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. |
| 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. |
| 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 an already connected client using the pinned SDK, request options are the
**second** argument of `callTool`:
@@ -155,26 +179,32 @@ increasing a client deadline alone does not fix those failures.
## Tool results
Evidence-producing tools return `{ result, evidence_id, evidence }` in both
text and structured content. `evidence` is the complete canonical Evidence
record, including `normalized_result`, which equals `result`. The same record
is retained in the session bundle. Read `result` directly, or pass `evidence`
to a compatible comparison tool: `analyze_function` Evidence can be passed
Evidence-producing tools return the complete canonical Evidence record in both
text and structured content. Read `structuredContent.normalized_result` for the
operation result and `structuredContent.evidence_id` for its identity. The same
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: `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.
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. If a result cannot fit, REA returns
room for the JSON-RPC envelope. Response budget settings are captured at startup;
restart or recreate the server to apply changes. If a result cannot fit, REA returns
`resource_constraint` with `details.resource: "transport"` before constructing
a document-sized string. Analysis Evidence remains complete in the current
session. Its exact reference is reported in
`details.reported_limits.evidence_reference`; use it with a focused application
workflow, or call `export_evidence_bundle` with a destination path. Complete
`details.reported_limits.evidence_reference`; call `inspect_analysis_view` with
that reference for a summary, one section/module, or a stable page, or call
`export_evidence_bundle` with a destination path. Complete
bundle exports stream canonical JSON into an atomically published file. A broad
follow-up or `get_evidence_bundle` can also exceed the response budget; exporting
preserves the complete session without sending it through a single MCP frame.
Cancelling an export stops further serialization and removes its staging file.
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
@@ -186,6 +216,18 @@ Ordinary responses keep their existing complete result contract.
## Retained application Evidence inputs
`inspect_analysis_view` projects a caller-selected view of already completed
`inspect_binary_layout` or `analyze_javascript_application` Evidence. Source is
an exact same-session retained reference or portable inline Evidence. Views are
a summary, a layout mitigations or linkage facet, one section/symbol/module, or
a stable page with a caller-selected positive `limit`. Module pages include
JavaScript assets, bundled modules, and source modules. Select an exact
`node_id` when a path is ambiguous or unavailable. Module items retain their
recorded property values and source locations; summaries include parent
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.
`trace_application_feature`, `trace_javascript_semantics`,
`compare_application_versions`, `compare_source_to_bundle`, and
`compare_javascript_export_shapes` accept complete inline application Evidence
+3 -1
View File
@@ -27,7 +27,9 @@ prompt argument or tool explicitly accepts one.
For a JavaScript/Electron application directory, `investigate_feature` points
directly to `analyze_javascript_application` with `input_path`; it does not
require `open_binary`. The CLI equivalent is `rea analyze <directory>`.
require `open_binary`. When that Evidence, or `inspect_binary_layout` Evidence,
is already retained, it suggests `inspect_analysis_view` before tracing a
feature seed. The CLI equivalent is `rea analyze <directory>`.
`open_binary` and the doctor target check admit files and macOS app bundles.
A directory outside that opening route is reported with an available analysis
action; this does not establish support for every directory format.
+201 -6
View File
@@ -12,6 +12,27 @@ input file and include the observed lipo slice offset for universal binaries.
If that offset is unavailable, segment evidence locations are omitted with an
explicit limitation; architecture inventory locations remain available.
Hopper's `address_to_file_offset` combines the native image-relative mapping with
original-file coordinates only after checking the source against the session's
SHA-256. FAT tables and embedded Mach-O headers come from that same hashing pass.
A changed or nonregular source reports `artifact_changed`; an unreadable or
missing source reports its access or filesystem failure. Those errors retain the native offset,
loaded header and provider source path in `details.partial_observation`, with the
original-file coordinate explicitly unavailable. Restore the original bytes or
reopen the changed executable to obtain verified coordinates. Snapshot replay
remains historical evidence for its recorded artifact identity.
A selected Hopper database's digest identifies the database rather than its
original executable. Original-file mapping is unavailable for that selection;
native mapping facts remain in partial evidence, and `read_bytes` still reads
the loaded database. Open the original executable to verify source coordinates.
Native Xcode command execution uses shared process supervision with a 60-second
deadline and a 64 MiB aggregate stdout/stderr budget. Timeout, cancellation,
stream failure, and output exhaustion retain the captured output, exit details,
and cleanup outcome in the error result. Truncated output is never parsed as a
complete observation.
- `trace_dylib_resolution` / `rea trace-dylib-resolution <app-or-mach-o>`
parses Mach-O load commands in TypeScript and follows dyld's path expansion
for every executable in an app bundle, or for one Mach-O within its directory.
@@ -37,6 +58,28 @@ explicit limitation; architecture inventory locations remain available.
connections and controller/class names. `verify:interface-builder` compiles
a source-owned AppKit XIB with `ibtool`. Storyboards require an installed
iOS platform; unsupported archive forms remain explicit.
XML plist archives accept UTF-8 and BOM-marked UTF-16 in either byte order.
App bundle `Info.plist` executable resolution uses the same decoder as keyed
archives and Interface Builder. The decoder consumes an initial
BOM and rejects malformed byte sequences, unsupported encoding declarations,
and declarations that disagree with the detected encoding. It never substitutes
UTF-8 for a declared encoding it cannot process. Archive and evidence digests
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
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.
NIB string values, data, property names and class names are charged at each
projected JSON occurrence, including when the archive reuses one source
value. Binary plist data projection also accounts for the temporary byte
copy made by the pinned decoder projection. These are format-derived
representation reservations, not exact heap measurements or a process-wide
heap ceiling.
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.
- `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,
@@ -62,10 +105,73 @@ explicit limitation; architecture inventory locations remain available.
partial coverage. Other providers retain the
existing symbol-based inventory with its narrower coverage.
## Exact CLI selectors
`function`, `instructions` and `decompile` accept `--procedure=<name-or-address>`;
`xrefs` accepts `--address=<name-or-address>`. `search` accepts `--pattern=<text>`
and `trace` accepts `--query=<text>`. These alternatives preserve names and text
that begin with a dash, including Objective-C names such as
`rea function ./app '--procedure=-[REAWidget delegate]' --provider hopper --format json`.
Use the equals form when the value resembles a CLI flag, for example
`rea trace ./app --query=--help --provider hopper --format json`.
The existing positional forms remain available. Missing selectors and conflicting
positional/named values fail before analysis starts; identical selections agree.
## Native instruction, call and type primitives
Ghidra supplies three exact-object operations:
Ghidra accepts hexadecimal addresses with or without a `0x` prefix, in either
letter case, and explicit address-space coordinates such as `EXTERNAL:0x1`.
Results use canonical lowercase hexadecimal offsets. For a procedure identifier,
an explicit `0x` or address-space prefix selects an address; otherwise an exact
database symbol name takes precedence over a bare hexadecimal address. Thus a
function renamed to `dead` remains selectable by name. Use the names returned
by the inventory, including their namespaces and any platform symbol prefix.
Ghidra also accepts secondary symbols at a function entry, such as an imported
`_main` label. A label inside a function is not a procedure-name selector; use
its address when selecting the containing function.
Overloads can share a fully qualified name; ambiguity errors return every
matching entry address so the caller can select the intended function directly.
`resolve_containing_procedure` also resolves an exact external entry. Its empty
body remains explicit; nearby external addresses do not inherit that identity.
Ghidra function references cover the complete function-body AddressSet and its
exact entry, including references into instruction interiors and references
from embedded data. Addresses inside an enclosing span but outside the owned
body are excluded. `procedure_references` returns internal and external edges;
`analyze_function` omits incoming edges whose sources belong to the same function.
The reference collector's semantics participate in the analysis profile, so
older snapshots cannot replay the former incomplete results under this profile.
Ghidra regex searches use Java Pattern semantics. If compiling or matching a
pattern exhausts the engine's stack, REA returns a resource constraint and
preserves the active database and annotations. Retry in literal mode or simplify
the regex; successful literal searches retain complete matching strings.
Cancelling an active request terminates the ephemeral database and discards its
annotations. REA retains the selected target and imports the original artifact
again on the next Ghidra query.
Opening the same target and profile again retains the live database, including
annotations. It does not make those edits eligible for an immutable snapshot.
Snapshot closes drain earlier provider requests before saving and keep later
requests waiting until the save and close finish. A concurrent successful edit
therefore rejects the snapshot save and leaves the edited session open.
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.
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
stable target to acquire its current identity. An already imported database
continues to describe its captured bytes even if the original path is changed
or removed; this identity failure does not mark the Ghidra installation unavailable.
On Linux and macOS, an unreadable source reports `access_denied`; a removed or
nonregular replacement reports `artifact_changed`. Source copying is cancellable
and never replaces an existing private snapshot.
```bash
rea inspect-native-instruction <binary> <address> --provider ghidra
rea resolve-native-call-targets <binary> <call-site> --provider ghidra
@@ -79,10 +185,35 @@ outside-memory and undecodable addresses have separate outcomes. Effective
memory base/index/displacement roles and per-instruction context mode remain
unavailable; `mode` is the program language variant.
Ghidra distinguishes a return using decoded instruction p-code's `RETURN`
operation. `flow.classification_evidence` preserves the Listing flow type and
the observed return evidence; mnemonic spelling alone is not the classifier.
Other terminal or trap flows retain the provider's classification, and this
inspection does not change function extents or prove a calling convention.
The analysis profile commits this decoder behavior. Snapshots from the earlier
profile produce a profile mismatch rather than replaying the former generic
terminal result. The old snapshot's `evidence_bundle` remains valid retained
Evidence: copy that object to a separate JSON file and use `rea evidence-import`
to validate it. Run analysis without the old snapshot and save a fresh one
under the current profile. Other providers and older captures may omit the
classification evidence field.
Call resolution reports direct, resolved indirect, ambiguous, unresolved and
non-call outcomes from static call references. It does not establish runtime
execution or classify Objective-C/Swift/vtable/closure mechanisms from names.
`batch_decompile` keeps each caller-selected symbol or address in `items[].address`.
The separate `procedure` field reports the canonical entry and the provider's
observed entry label. An interior address therefore remains visible without
being mistaken for the function entry. Identity failures are reported as
`procedure.status: "unknown"`; label failures leave a null name and `name_error`.
Neither failure discards usable pseudocode, and names are not parsed from it.
Resolved entries also appear in the composed Evidence locations. New batch
Evidence commits the revised workflow profile; retained historical Evidence
remains valid. This tool
has no dedicated CLI command; the existing `decompile` command remains the
single-procedure CLI operation.
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
@@ -188,16 +319,42 @@ arguments. The receiver class comes from LLDB's Objective-C runtime reader
without running target code; no expression is evaluated, and REA never
attaches to a process it did not launch.
The run ends when the process exits, `max_events` entries are recorded, or
`duration_ms` elapses. In the last two cases the process is killed, and REA
confirms that it is gone. The result reports:
The run ends when the process exits, `max_events` entries are recorded,
`duration_ms` elapses, or the aggregate trace resource budget is exhausted.
Bounded termination reports partial coverage. REA checks process termination
using a launch-time OS start identity; if ownership or cleanup cannot be
verified, the result or error preserves that uncertainty. The result reports:
- the outcome, exit status and captured stdout/stderr (1 MiB kept per stream);
- the outcome, exit status and captured stdout/stderr (1 MiB kept per stream
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;
- `selected_file_sha256` and LLDB module identity observations. These checks do
not pin the executable against concurrent mutation; `loaded_image_sha256`
remains unknown;
- signal or exception stops;
- every breakpoint's resolved locations; a breakpoint that matched no loaded
code makes coverage partial;
- limitations.
The bridge limits retained event JSON to 8 MiB and caller frames to 65,536
across the run. It checks admission before retaining another complete event;
`resource-limit` reports which observations could not be completed without
silently changing the requested event or frame settings.
Resolved breakpoint locations have a separate aggregate 8 MiB metadata budget.
If their details exceed it, `location_count` retains the full observed match
count, coverage is partial, and `breakpoint_locations_truncated` is true. This
post-run metadata limit does not imply that the target was killed.
Accepted entries and signal stops are also flushed to a bounded 8 MiB journal.
If cancellation, timeout, capture failure, or a later bridge error prevents a
full result, the error retains this evidence in `details.partial_observation`
with partial coverage. Available output prefixes remain inline; their byte
counts are observed lower bounds when final drain counters are unavailable.
The original failure classification and cleanup uncertainty remain separate.
An interrupted journal row is ignored with an explicit limitation.
Only entries are observed: return values, floating-point and stack arguments,
and inlined or `objc_direct` calls are not. The target runs with the current
user's permissions and may change files, show UI or use the network.
@@ -210,15 +367,24 @@ the tool fails with a `debugger-attach-denied` reason.
## Provider and verification boundaries
Install a Ghidra 12.1.x release and the 64-bit full JDK it declares, then configure REA to
use them. Ghidra analysis supports Linux x64 and macOS x64/arm64; macOS requires
use them. Ghidra analysis supports Linux x64/arm64 and macOS x64/arm64; each installation requires
the matching native decompiler. Experimental Windows x64 P0 admits native
x86-64 PE applications on local NTFS using bundled Job Object ownership,
protected runtime DACLs, and handle-based path admission. See [Windows Ghidra P0](windows-ghidra-p0.md) and
[issue #527](https://github.com/morluto/rea/issues/527).
On Linux and macOS, `annotate_native_function` atomically edits a function name
and entry comments in the ephemeral database, returning refreshed analysis
without changing executable bytes. Windows P0 remains read-only. Ghidra has no
without changing executable bytes. Annotation text must contain no NUL or
unpaired Unicode surrogate; a rejection identifies the field and UTF-16 index
and leaves every annotation unchanged. CRLF, supplementary Unicode characters,
and combining characters are preserved. Windows P0 remains read-only. Ghidra has no
GUI authority, and REA never falls back automatically to Hopper.
Ghidra name edits preserve the existing namespace. Supply either a leaf name
such as `renamed` or a fully qualified name in that namespace, such as
`alpha::renamed`. The returned qualified name can be reused as an idempotent
rename input. Edits retain the existing namespace; other namespace-like text
remains literal leaf-name text. A qualified name with an empty leaf is rejected
before any comment or name is changed.
- `npm run verify:ghidra`: host-native debug/stripped targets, native type layout,
instruction/call facts, value dependencies and process/project cleanup.
@@ -238,3 +404,32 @@ GUI authority, and REA never falls back automatically to Hopper.
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.
## Interface Builder hierarchy coverage
A keyed-archive hierarchy UID without an object-table entry marks the hierarchy
as partial while preserving decoded objects and known links. UID 0 remains
archived nil and does not count as a missing reference. Archive and evidence
digests identify the original serialized bytes.
## Keyed archive integer precision
The archive reader observes XML number element types and binary number markers.
Integers beyond the exact range of a JSON number are reported with the original
decimal text as `{ "$plist_type": "integer", "decimal": "<exact digits>" }` when
that source value can be associated unambiguously. Integral real values remain
numbers. Colliding integer/real values, unclassified numbers, and malformed UID
markers remain decoded with an explicit precision limitation. The limitation
also counts observed unsafe integer literals, including observations outside the
selected object page. Incomplete supplemental metadata produces an explicit
precision note without rejecting an archive accepted by the byte decoder.
Mixed XML number content (comments, CDATA, processing instructions or child
elements) and integer text the supplemental reader cannot classify disable exact
association and retain decoded numbers with that precision note. Nonnumeric
`UID` and `CF$UID` dictionary values remain ordinary data and are projected
recursively; numeric reference markers retain their original representation.
Value projection preserves serialized node kinds, IDs, references and pagination.
Exact and ambiguous value counts describe emitted root/object value instances;
a root and an object may contain the same value. Archive digests identify the
original bytes.
+40 -2
View File
@@ -94,6 +94,31 @@ deleted, modified, or unchanged. These are bounded snapshots, not a syscall
trace; short-lived changes between snapshots may be missed. With no selected
paths, filesystem effects remain unknown.
A missing entry proves creation or deletion only when the corresponding
observation root's path enumeration was exhausted. Otherwise its effect has
`status: "unknown"`, the available before/after state, and a reason; a null
state in this variant means unobserved. Paths below symlinks that REA did not
follow also remain unknown. Partial enumeration of one root does not erase
known effects in another, and unavailable content hashes do not by themselves
make path absence unknown.
Terminal frame `data` is the comparison text after the selected normalization.
New captures also preserve `raw_data` when normalization changes a PTY chunk;
otherwise `data` is already the original text. Older captures may lack the
original text for changed chunks. Both values participate in Evidence identity,
while comparisons and trace assertions use the normalized text. Retaining the
original adds at most `limits.output_bytes` of text, because it comes from the
same admitted PTY chunks rather than a second unbounded stream.
Port normalization recognizes explicit `port`, `tcp_port`, `udp_port`, and
`listen` fields, URL authorities, IP endpoints, and `localhost` endpoints, with
ports from 0 through 65535. It preserves ordinary counters, dimensions, file
line numbers, and ambiguous bare host labels. Use explicit literal `patterns`
for a different application-specific spelling. Set `ports: false` to preserve
all endpoint numbers in comparison text. Captures commit the port-normalization
version so older broad numeric normalization cannot silently compare as the
same contract.
Output, file count, file size, process sampling, filesystem depth, total
runtime, idle time, and post-exit settlement are bounded by the scenario's
limits. The result marks truncated observations and residual unknowns rather
@@ -108,6 +133,14 @@ and process residual unknowns. Successful cleanup verifies the owned group;
it does not attribute those unrelated processes. Related or otherwise unexplained
unreadable processes still prevent successful cleanup.
On macOS, changing a Node process's `process.title` can make its run token
unreadable. Node documents that [setting the title overwrites argv memory](https://nodejs.org/download/release/v24.18.0/docs/api/process.html#processtitle).
With the pinned toolchain, a live child retained a readable start identity
while its token became unavailable after this change; npm changes its title
as well. A newly started unreadable process can prevent verified cleanup even
after the selected command exits. REA preserves this uncertainty and leaves
that process untouched.
## Compare two captures
Compare saved capture Evidence with:
@@ -130,7 +163,12 @@ causal evidence. Trace comparison returns `unknown` when a capture lacks the
required complete event journal or contains relevant residual unknowns.
Each capture carries commitments for the selected scenario, executable,
comparison contract, and normalization rules. Comparison rejects captures
whose comparison contracts differ. Captures are local Evidence files; keep
comparison contract, and normalization rules. The comparison contract covers
the working directory, explicit environment, filesystem observation paths,
terminal size, scripted events, timeouts, limits, and normalization; the
executable and its arguments may differ. Comparison rejects captures whose
comparison contracts differ and names the differing fields, so run both
scenarios from the same absolute working directory and observation paths.
Captures are local Evidence files; keep
their source artifacts and invocation context available when interpreting a
difference.
+3 -3
View File
@@ -2,11 +2,11 @@
This guide describes repository main. Check the [released package boundary](installation.md#released-package-and-main) when using npm.
Ghidra read-only analysis is available on Linux x64 and macOS x64/arm64.
Ghidra read-only analysis is available on Linux x64/arm64 and macOS x64/arm64.
Install a Ghidra 12.1.x release and the 64-bit full JDK that release declares,
then configure REA to use them. Current 12.1 releases require JDK 21 or newer
and set no maximum. The bridge is verified with Ghidra 12.1.4 and JDK 21.
macOS installations need the matching native decompiler.
Each installation needs the matching native decompiler.
REA imports one target into a temporary project and exposes 25 read-only
operations after analysis completes. They cover inventories, search,
@@ -121,7 +121,7 @@ capability from a successful import.
| 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. |
`npm run verify:ghidra` compiles the versioned C oracle into debug and stripped
host-native targets (x86-64 ELF on Linux x64 or Mach-O on macOS), plus a native
host-native targets (x86-64/AArch64 ELF on Linux or Mach-O on macOS), plus a native
DWARF 4 type-layout object. It proves the admitted operations, external functions, resolved thunks, exports, stripped-name
behavior, direct and targetless indirect calls, typed references, strings/xrefs,
multi-block CFG, bounded p-code def-use/effect links, semantic enhanced workflows, cancellation, startup deadlines,
+46 -21
View File
@@ -1,15 +1,32 @@
# Releasing from a checkpoint
# Releasing REA
REA releases use an explicit source checkpoint. Main can continue accepting
changes while a release is tested and published. The Release workflow runs
only when a maintainer dispatches it; main pushes do not refresh release PRs.
Pushes to main automatically open or refresh a Release Please PR with the next
version and changelog. Review its migration notes and wait for its current CI
checks to pass. Merging that PR into main starts publication automatically.
## 1. Select the source
The Release workflow validates the merged release metadata and ancestry before
creating a tag. npm and MCP Registry publication use the exact reviewed merge
SHA returned by Release Please. Later main commits cannot change that source.
Ordinary main pushes and closed, unmerged PRs cannot publish packages. Check
both publication jobs and the published-package canary; a merged PR or GitHub
tag alone does not establish that publication finished. Retry failed jobs in
the original run after a partial publication.
Choose the next version from the unreleased Conventional Commits, including
For a release that needs an independently frozen application checkpoint, use
the optional manual path below. It retains explicit prepare and publish phases.
## 1. Select a source for a manual checkpoint
Use the next minor version proposed by Release Please, including releases with
breaking changes. Record the full source SHA and create `release/VERSION` at
that commit. The version is a maintainer choice; Release Please's proposed
version must agree before publication.
that commit. The selected version and Release Please's proposed version must
agree before publication.
When choosing the manual path, leave the automatic main release PR unmerged.
Select the application commit from main, create the frozen branch, and prepare
its own release PR using the steps below. Merging the main release PR starts
automatic publication instead. Close the superseded main PR after the manual
release has published and its metadata has been synchronized back to main.
`release/VERSION` also sets the expected version for the workflow. Use an exact
SemVer such as `release/6.1.0` or `release/6.1.0-rc.1`, without build metadata.
@@ -18,19 +35,21 @@ registry metadata or changelog disagrees with that version before creating a tag
### Compatibility and version selection
REA uses the default SemVer strategy: compatible fixes increment patch;
compatible capabilities increment minor; incompatible public behavior increments
major. Adding tools, internal refactoring and correcting behavior outside the
supported contract do not by themselves require a major release.
REA uses Release Please's `always-bump-minor` strategy: every release increments
minor and resets patch, including releases with breaking changes. For example,
the next release after 6.0.0 is 6.1.0. Version numbers use the SemVer format,
but a minor increment does not promise backward compatibility. Review the
changelog's breaking-change section and migration notes before upgrading.
The supported public surface includes documented CLI commands/options, MCP tool
names and input/result contracts, saved evidence formats and supported runtime
requirements. Before adding `!` or a `BREAKING CHANGE` footer, a PR must identify
a previously valid call or configuration that will fail or change meaning,
explain why compatibility cannot be preserved, and give its migration. Prefer
optional additions, compatibility adapters and a documented deprecation period;
group deliberate removals into a planned major release. Maintainers review this
impact before merging; the bot parses markers and cannot infer compatibility.
optional additions, compatibility adapters and a documented deprecation period.
Keep breaking markers so the bot includes migration notes without incrementing
major. Maintainers review this impact before merging; the bot parses markers
and cannot infer compatibility.
For example, adding an optional inspection tool is a minor change. Rejecting a
relative MCP path previously accepted by the public contract is breaking;
@@ -80,9 +99,11 @@ and the PR titles in GitHub's default merge messages. The report preserves the
original merge subject alongside the extracted Conventional Commit title.
This matters after merging a side-branch release back into a newer main:
Release Please's chronological history cutoff can omit unreleased mainline
commits. Unreleased breaking markers require a major increment and references
in the new release's breaking-change migration section. Other omitted entries
are reported for review. Historical notes from an older release cannot satisfy
commits. Under `always-bump-minor`, unreleased breaking markers allow a minor
increment and still require references in the new release's breaking-change
migration section. Checkpoints using the default strategy retain the major
increment requirement. Other omitted entries are reported for review.
Historical notes from an older release cannot satisfy
the new release's migration check. This audits declared markers and references;
it does not prove API compatibility or the quality of a migration explanation.
@@ -115,6 +136,8 @@ gh workflow run release.yml --ref release/5.0.0 \
Publication creates the release from the merged bot PR without preparing or
updating another PR. Both npm and MCP Registry jobs check out Release Please's
exact release SHA. They do not build the current main tip or a mutable branch.
Stable versions publish to npm's `latest` tag; prerelease versions publish to
the `next` tag so they cannot replace the stable install by default.
The publish dispatch runs from the frozen release branch so npm's provenance
records the actual release commit. Before Release Please creates a tag, the
workflow accepts only `prepare` or `publish` and requires the selected branch
@@ -139,7 +162,7 @@ A GitHub tag alone does not establish npm or MCP Registry publication.
## 4. Sync metadata back to main
Post-release synchronization is part of completing the release. Finish it
Manual checkpoint synchronization is part of completing that release. Finish it
before cutting the next checkpoint; otherwise main retains the previous
release baseline and can propose an already-published version again.
@@ -161,8 +184,10 @@ git fetch origin main --tags
git merge-base --is-ancestor rea-agents-5.0.0 origin/main
```
Close any superseded rolling release PR. Future releases repeat the checkpoint
procedure from main; never resume automatic release-PR refreshes on main pushes.
Close the superseded main proposal before merging the synchronization PR.
That merge's main push opens a proposal for the following release using the
updated baseline. Future releases can use the automatic main PR or repeat the
manual checkpoint procedure; automatic proposals do not move frozen branches.
Preparation generates and validates the catalog, portable conformance
projections, and packaged skill from the candidate checkout without committing
them. The checkpoint validator binds tracked version authority to the source
+4 -4
View File
@@ -22,7 +22,7 @@ installation changes and requires approval before writing or installing.
## Planned work
The [website guides](https://morluto.github.io/rea/guides/) and
The [website guides](https://rea.tools/guides/) and
[generated catalog](product-catalog.json) describe available workflows.
Current development priorities are:
@@ -67,12 +67,12 @@ REA setup lets you select agent integration and optional Hopper installation.
It installs the bundled workflow, configures detected agents, and can save
verified paths for an existing Ghidra installation. It configures Claude Code,
Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity,
GitHub Copilot CLI, Command Code, and VS Code using each client's configuration format.
GitHub Copilot CLI, Command Code, VS Code, and Grok Build using each client's configuration format. Grok Bot is detected locally, but its connector is added from the Grok Bot chat because the signed-in account store is not a data-directory file.
Ghidra analysis supports Linux x64 and macOS x64/arm64 with Ghidra 12.1.x and the
Ghidra analysis supports Linux x64/arm64 and macOS x64/arm64 with Ghidra 12.1.x and the
64-bit full JDK that installation declares. Current 12.1 releases require JDK 21
or newer and set no maximum; the bridge is verified with Ghidra 12.1.4 and JDK 21.
macOS also requires the matching native decompiler. The
Each installation requires the matching native decompiler. The
adapter exposes thirteen inventory operations and twelve function-analysis
operations, for 25 read-only operations total. Linux and macOS additionally support
atomic function-name and entry-comment edits with refreshed analysis; metadata
+301 -23
View File
@@ -1,14 +1,21 @@
# Testing REA
Prefer evidence in this order: full end-to-end workflows with real production
providers, integration tests across data/API boundaries, then golden regressions
from real captured inputs. Keep focused module tests for distinct failure or
semantic cases that these workflows cannot reliably reproduce. A test's path
or suite name does not establish its behavioral depth.
Prefer evidence in this order: full end-to-end workflows through the public
CLI or MCP with real production providers and no mocked dependencies,
integration across production data/API boundaries, then golden regressions
from real captured inputs. Keep a focused module test only for a distinct
failure or semantic case that stronger workflows cannot reliably reproduce.
A test's path, compiled imports, or suite name does not establish its depth.
Avoid tests that mirror getters, enum mappings, fixture helpers, or prescribed
call sequences. Prefer one representative workflow over a Cartesian matrix
when every row crosses the same boundary. Keep distinct command handlers,
Delete tests that only mirror getters, serialization helpers, enum mappings,
fixture helpers, prescribed call sequences, arbitrary counts or snapshots,
or cache object identity without a caller-visible contract. Do not retain a
weaker test merely because it is fast when a stronger workflow already proves
the same claim. Keep format-specific malformed representations and actual
capacity, cleanup, permission and target-identity regressions.
Prefer representative workflows over a Cartesian matrix when rows cross the
same boundary. Retain combinations that exercise a distinct interaction, such as
filtering within a full-output envelope. Keep distinct command handlers,
producer representations, failure reasons, and lifecycle states covered.
Consolidate their shared setup without hiding the inputs or expected evidence.
@@ -21,6 +28,43 @@ tests consume a production module, trace CLI/MCP and verifier imports,
including imports of compiled files, and remove abandoned scaffolding with its
tests when it has no runtime consumer.
When a test expects a `Result` to succeed, throw its error before asserting the
returned value. An `.ok` assertion immediately before the same throwing guard
adds no coverage and hides the typed failure. A removed parser matrix can leave
one golden malformed-input case and a real filesystem lifecycle that proves
configure, update, readback, and removal; an add-only journey does not cover the
whole lifecycle.
Protocol fixtures must model the commands they support and their producer reply
shapes. Reject unmodeled commands instead of returning a generic success; an
empty result is appropriate only for a supported command with no result fields.
Keep transport-only in-memory MCP sessions in the MCP boundary lane and direct
filesystem workflows in the filesystem boundary lane. Real process-tree capture
belongs in the serial process boundary lane, including captures invoked through
MCP. Report unavailable capture authority as a named skip rather than returning
from the test without assertions. Reserve acceptance paths for journeys
through the compiled public CLI or MCP runtime. Calling a compiled private
workflow or injecting a provider still establishes its narrower integration
claim, even if a directory name calls it acceptance.
Emit a verifier's final success report only after awaited cleanup. Observe final
process lineage after closing its owned resources.
Owned JavaScript semantic graphs capture and schema-parse caller input before
returning an iterator. Whole-graph hashing, batched integrity checks and sealing
run through the application cancellation driver; only full completion grants
the private immutable-graph proof. Regression coverage checks canonical IDs,
validation diagnostics, nested caller mutations across yields, cancellation
before result validation and subsequent SDK requests. Single-file parsing,
schema cloning and canonical sorting remain synchronous phases; measure them
separately when assessing cancellation latency on real applications.
The Vitest runner releases cached native process inspectors during awaited
worker teardown. Fork termination does not run Node's normal exit hooks; a
per-file teardown would instead retire shared inspectors before later files
can use them.
Measure slow files before removing capacity regressions. Optimize repeated
process startup, fixture construction, and production algorithms when they
dominate runtime. Keep at least one input beyond each formerly failing size or
@@ -113,6 +157,12 @@ covers `trace-dylib-resolution`:
- for the main executable and an XPC service, a predicted load order equal to
the images dyld actually loads under `DYLD_PRINT_LIBRARIES`.
The lane also compiles executable/library pairs with empty embedded directory,
versioned-path, and suffix settings. It checks present and removed dependencies
through CLI and MCP, and compares actual `DYLD_PRINT_SEARCHING` diagnostics for
root-level candidates. These cases distinguish an empty search directory from
an empty versioned scan or a suffix that only repeats the original path.
It runs in macOS CI.
Apple artifact verifiers live in `scripts/verify/apple/`, with the macOS bundle
@@ -138,13 +188,85 @@ case. Real capture tests preserve actual descendant and cleanup checks.
The existing Apple job also exercises the relocated filesystem snapshot identity,
cancellation and descriptor cleanup regressions on macOS.
Prepare the native inspector before cases that measure producer output or exit
behavior; keep startup deadlines and cancellation in distinct cases. Run real
process-capture verification separately from package or build checks. On macOS,
new npm processes can become token-unreadable after changing their display
title and prevent verified cleanup during a capture's ownership sweep.
MCP SDK transport tests with recording providers remain integration tests.
They are useful for schema drift and failure projection but do not prove that
Hopper, Ghidra or another substituted engine works. `verify:package` proves
packaging/install behavior and fake-provider integration; use the corresponding
real-provider lanes for engine claims. Real Apple dispatch and Interface
real-provider lanes for engine claims. Packed-bridge checks verify shipped files
and Python syntax without deleted mock fixtures or source-spelling assertions.
Real Apple dispatch and Interface
Builder verifiers currently prove format integration through production readers.
`verify:hopper` exercises an installed Hopper through the production stdio MCP
server and CLI. It checks source-owned call chains, CFG edges, references and
complete large inventories, then probes unnamed bookmarks, annotation clearing,
batch validation before mutation, malformed addresses and regexes, segment-end
partial reads, and synthetic file-offset rejection. Advertised schemas are
validated in their JSON Schema dialect and successful replies are checked against
their advertised output schemas. Navigation checks cover interior-object cursor
readback and mapped-memory boundaries. Annotation probes verify invalid native
text and duplicate destinations/names before mutation, preserve unselected label
owners, and exercise explicit batch label swaps. Function locals retain observed
names and stack displacements. Graph probes check symbol/interior-address parity
and a source-owned recursive cycle; literal tracing preserves complete queries
and whitespace. Disposable binary copies prove that switching and closing
actually removes the native document, and that CLI byte, function-dossier,
literal-trace results and invalid-address diagnostics agree with MCP.
No provider is mocked in this lane.
Real search probes cover Unicode names, literal metacharacters, case and regex
modes, annotation cache invalidation, complete native fragments of long literals
checked against byte reads, escaped UTF-8/control text, byte-preserving Latin-1
decoding, and Hopper's UTF-16 symbol-name truncation boundary. It verifies native
CallReference classifications across reference and dossier results, retains long
string fragment metadata in dossiers, and exercises pathological regex deadline
and cancellation followed by successful requests in the same native session. When the macOS
Objective-C fixture is present, the lane also verifies native UTF-16 string objects
and their inferred encodings against their actual bytes. Native terminal calls
are checked across reference, instruction, assembly, block-range and procedure-length
projections; block endpoints are normalized using actual native membership.
Exact Objective-C names also exercise named CLI selectors for function, instruction,
decompilation, reference and search operations. A literal `--help` trace query proves
that selector data is preserved independently of global CLI flags.
Unmapped annotation destinations and
oversized later batch names fail before any earlier edit is applied.
`verify:hopper:deadlines` checks the native client's optional request deadlines
on macOS with an owned source-built fixture. Zero and elapsed deadlines must
leave native comments unchanged, including when a synchronous progress observer
delays timer dispatch. It also observes a short analysis deadline and verifies
subsequent wire recovery and clean shutdown. Caller timeout does not interrupt
Hopper's synchronous native operation. Socket boundary tests deterministically
cover active and queued expiry, late replies and timer cleanup.
The Linux demo lane remains a separate `verify:hopper:linux` command. Its
lifecycle checks attempt competing CLI launches for the active target and a
different target, requiring rejection before either can change the owning
MCP session's documents or procedures. Lease boundary tests also preserve
unresponsive or malformed live endpoints while allowing confirmed stale
sockets to recover.
`verify:hopper:fat` is a separate macOS lane requiring installed Hopper and the
existing Xcode clang/lipo toolchain. It compiles arm64/x86-64 thin executables and
one- and two-slice FAT32 containers, verifies exact/interior address mappings
against bytes in the original files, checks CLI/MCP parity, and checks owned
runtime cleanup. Source byte changes, removal, permission denial (for non-root
callers), and nonregular replacement must retain native partial mapping facts,
reject unverified original-file coordinates,
and recover after restoration. Single-slice FAT cases also relocate the slice
without changing its loaded bytes. `verify:hopper:fat64` additionally checks
FAT64 preparation through Hopper's native Mach-O loader, source-container
mappings, profile identity,
malformed and ambiguous slice rejection, and temporary-image lifetime. Both
lanes have been verified on Hopper 6.1.0-demo; this establishes REA's prepared
FAT64 workflow, not native FAT64-loader support.
Cross-architecture fixture compilation is not required by `verify:hopper`.
Golden tests use immutable captured text inputs with producer/source provenance
under `tests/fixtures/golden/`. Expected results are reviewed for the semantic
claim; capture commands do not automatically approve new expected outputs.
@@ -152,6 +274,8 @@ Do not call handcrafted utility output or synthetic binary builders real-data
goldens. Keep unsupported binary layouts and malformed boundaries as targeted
regressions until a real fixture establishes equivalent coverage.
`verify:browser` exercises source-map failure isolation and expanded-output limits through the compiled CLI and stdio MCP with real Chrome. It also submits five 2 MiB source-map annotations, checks retained script identities and explicit map omissions, and closes its owned fixture target. These public workflows replace redundant metadata-admission assertions; replacement and document-reset boundaries remain focused producer regressions.
`verify:browser` also captures a source-owned noise canvas as a real PNG above
8 MiB through the CLI and stdio MCP, with complete byte/digest parity and real PNG
decoding. Its SDK client explicitly permits the larger inline JSON response;
@@ -202,12 +326,25 @@ prove behavior on the verified Ghidra 12.1.4 and JDK 21 build.
| Ghidra lane | Supported runner/target | Additional local tools |
| ------------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `npm run verify:ghidra` | Linux x64 ELF or macOS x64/arm64 Mach-O | Host C compiler, Ghidra 12.1.4, and full JDK 21 |
| `npm run verify:ghidra` | Linux x64/arm64 ELF or macOS x64/arm64 Mach-O | Host C compiler, Ghidra 12.1.4, and full JDK 21 |
| `npm run verify:ghidra:swift` | macOS x64/arm64 Mach-O | Host Swift compiler, Ghidra 12.1.4, and full JDK 21 |
| `npm run verify:ghidra:switch` | Linux x64 ELF; GCC/Clang optimized and stripped switch fixtures | GCC, Clang, GNU nm/objdump/strip, Ghidra 12.1.4, and full JDK 21 |
| `npm run verify:ghidra:aarch64-jump-table` | Any supported Ghidra host; AArch64 ELF; byte/halfword tables; host ARM64 Mach-O | Clang with AArch64 target support, Ghidra 12.1.4, and full JDK 21 |
| `npm run verify:ghidra:cross-format` | Any supported Ghidra host; also analyzes AArch64 ELF, x86-64 PE, and x86-64 Mach-O | `clang`, LLD, and `lld-link` in addition to host-lane prerequisites |
| `npm run verify:ghidra:windows` | Controlled Windows x64 with native x86-64 PE; `-- --x86` selects native x86 PE | Ghidra 12.1.4, full JDK 21, and the matching native artifact |
The optional Swift lane compiles a source-owned fixture and checks modern and
provider-demangled procedure names through real stdio MCP, with and without the
dSYM companion. It checks unfiltered, structs, and literal-name filters,
same-address alias evidence, deduplication, and explicit unresolved categories.
It does not add a Swift prerequisite to the host C fixture lane.
The host lane also verifies namespaced C++ ABI symbols and annotation identity.
That facet requires Ghidra's matching `demangler_gnu_v2_41` native component in
`GPL/DemanglerGnu/os/<platform>/` or `build/os/<platform>/`, in addition to the
native decompiler. The namespace verifier reports missing or non-executable
components before testing the fixture. REA does not build or install them.
Windows native conformance runs with `npm run verify:windows-native` and does
not require Ghidra or Java. An optional independently compiled Windows fixture
adds in-place reparse, breakaway, environment, and token observations.
@@ -219,6 +356,70 @@ The host-native Ghidra lane also verifies native value tracing through the
production CLI and a separate stdio MCP process. It compares complete dependency
graphs, validates Evidence and upstream/workflow profiles, checks capability
discovery, and closes the MCP session. No provider or transport is mocked.
It also validates every advertised input/output JSON Schema and the exercised
MCP outputs, probes address spelling and name/address ambiguity, and checks
direct versus targetless calls, byte-read completeness, invalid input diagnostics,
CLI/MCP parity, atomic annotation rollback, refreshed inventories, unchanged
executable bytes, and discarded edits after reopen. A deliberately long temporary
path exercises private Unix socket allocation and cleanup, including cancellation
after a real headless process launches. Native annotation probes reject NUL and
unpaired Unicode surrogates without partial edits or a broken bridge, preserve
supported Unicode and control text, and check lossless malformed-text diagnostics.
Memory-to-file mapping is checked against original artifact bytes.
The fixture also stores a pointer one byte past a function entry; exact
xrefs, raw procedure references, and CLI/MCP dossiers must retain that data edge.
A valid legacy snapshot reconstructs the former omitted edge under its older
profile; CLI and MCP must reject that binding with a mismatch reason and
recovery advice. The rejected open must preserve the active live session.
Exact external entries must resolve while retaining an empty body; unknown
external addresses remain unresolved and external annotations are rejected.
An adversarial regex over a full 12 KB literal must report stack exhaustion as
a resource constraint, preserve live annotations, and allow complete literal
searches afterward; CLI and MCP must agree on both results and recovery advice.
Real snapshot lifecycle checks retain edited API results as Evidence while
rejecting immutable snapshot saves and imports before and after a repeated
open of the same target. They verify unchanged live annotations and run identity,
absent rejected output files, an unchanged source snapshot, and successful
snapshot import/save after closing and recreating the database. The pristine
snapshot is written by an independent real CLI session.
They also start a real annotation and snapshot close concurrently: the edit
must succeed, the snapshot must be rejected without creating a file, and the
edited session must remain usable until explicitly closed.
Source-admission probes change a caller-owned fixture after `open_binary` but
before the first Ghidra query. They require an actionable `artifact_changed`
error preserving both digests and the selected path, unchanged provider
availability, failed-copy cleanup, and successful recovery after reopening.
After import, deleting that source must preserve the captured database identity.
Instruction inspection and containing-function lookup also agree across
hexadecimal case, leading zeros, and encoded default address-space spellings.
The same source-acquisition workflow exercises missing and directory-replaced
inputs, plus real read-permission denial on a non-root host. Root runs report
that permission-denial check as unverified. A focused filesystem integration
check retains selected-platform routing and exclusive creation; two copy/digest
checks duplicated by the real workflow have been removed.
Namespace annotation probes compile a separate host C fixture with C++ ABI
symbols, avoiding a C++ runtime prerequisite. Real Ghidra demangling supplies
duplicate leaf names in two top-level namespaces and a nested namespace. The
workflow verifies leaf and qualified renames, repeated reuse of fully qualified
readback, lookup by the returned name, literal namespace-like leaf names,
rejection of empty qualified leaf names without changing comments, CLI
behavior, and independent CLI/MCP database ownership.
Large-result probes compile initialized host-native data sized from the pinned
MCP SDK receive budget. Real byte reads, annotation edits and function dossiers
exceed that budget while preserving the connection and active analysis run.
Successful-result and oversized-error delivery constraints must identify their
successfully retained Evidence records;
export must recover every source byte, complete annotation and original error
diagnostic, with CLI parity
and an unchanged executable. The focused formatter check retains only the
separate case where recording was not acknowledged.
Long ordinary procedure names and encoded address-space selectors must produce
normal validation errors without exhausting Java's regex stack or losing the
private bridge connection. Each rejection is followed by a real provider lookup;
address-like literal names still resolve exactly after annotation.
Malformed annotation readback, memory completeness, and inventory data remain separate
SDK/provider integration cases; success from a real
provider cannot establish rejection of a contradictory provider response.
The Linux switch lane checks dense, sparse-with-holes, shared-body, nonzero,
negative, and nonexact JSON integer labels plus a comparison-only control.
@@ -262,6 +463,13 @@ Electron GUI behavior or another engine.
## Android APK analysis
The real Android lane also runs on Windows x64 with its matching bundled native
controls. It verifies bidirectional protocol input, CLI/MCP result parity,
real MCP cancellation/disconnect and Java exit after abrupt CLI owner
termination. The forced Windows exit does not exercise the POSIX SIGTERM
handler and can leave temporary workspace files. `verify:windows-native`
separately checks binary input, backpressure, EOF and pending-write job closure.
`npm run verify:android` requires an existing Java 17+ and an explicit
`REA_JADX_MCP_JAR` for jadx-headless-mcp 0.7.1. Set `REA_ANDROID_TEST_APK` to the
fixed public ApiDemos v6.0.18 fixture. Obtain both with the explicit
@@ -284,7 +492,7 @@ are separate proof levels.
Synthetic producer regressions run independently:
```sh
npm run test:focused -- tests/boundary/android/jadxIntegration.test.ts tests/boundary/mcp/androidAnalysisMcp.test.ts
npm run test:focused -- tests/boundary/process/jadxIntegration.test.ts tests/boundary/process/androidAnalysisMcp.test.ts
```
## Optional NativeAOT Ghidra analysis
@@ -356,22 +564,27 @@ a DOS emulator or compiler, then checks real 16-bit decoding, segment
relocation, near/far calls, decompilation, disjoint function body ranges,
stable CLI/MCP observations, unchanged source bytes, and owned process/project
cleanup. Raw p-code address-space selector tokens are reported separately from
the stable observation comparison. Linux x64 is verified; macOS DOS remains
unverified. This lane is separate from host-native and optional cross-format
the stable observation comparison. Linux x64 and macOS arm64 are verified;
macOS x64 remains unverified. This lane is separate from host-native and optional cross-format
verification. See [DOS analysis](ghidra-dos.md).
`npm run verify:ghidra:com` uses a generated headerless fixture with no compiler,
DOS emulator or game data. It exercises explicit admission, BinaryLoader entry
preparation, measured register context, whole-file byte readback, source offsets,
unmapped PSP/partial reads, actual decompilation, CLI/MCP parity and owned cleanup.
Both segmented-address lanes reject oversized default, explicit-space and encoded-space
coordinates through real reads, function queries and annotation attempts. Rejected
annotations must preserve the live function dossier; CLI and MCP must report the
truncation constraint, while leading-zero coordinates still resolve correctly.
It has the same Ghidra/JDK prerequisites as the MZ lane. Neither lane claims DOS
runtime or PC-98 device execution.
## Apple Interface Builder archives
`npm run verify:interface-builder` compiles the source-owned AppKit XIB into a
real `.nib` with Xcode `ibtool`, wraps it in a temporary app bundle, and checks
the decoded view hierarchy, outlet, action, evidence coverage, and truncation
real `.nib` with Xcode `ibtool`, wraps it in a temporary app bundle, and runs
the compiled CLI and stdio MCP server with production providers. It compares
their decoded results and checks the view hierarchy, outlet, action, evidence coverage, and truncation
status. Storyboard compilation additionally requires an installed iOS platform.
Keep the provider-specific acceptance path independent from optional
@@ -446,6 +659,17 @@ The dry-run option reports the chosen merge base, scope and build prerequisite
without executing tests or building. A missing Git base reports how to fetch
it or select another revision.
Source typechecking needs no compiled runtime or generated test metadata.
`npm run build:cached` compiles the CLI/MCP runtime and its packaged skill.
`npm run test:prepare` also generates the MCP contract test catalog, product
catalog, and portable managed evidence used by the complete suite. Focused
tests prepare those extra outputs only when their selected files consume them.
The MCP test catalog is JSON in `.cache/mcp-tool-catalog.json`; the tracked
test loader supplies its types without making generation a source-check
prerequisite. Run `npm run mcp-catalog:generate` to refresh it independently.
Documentation generation and managed conformance run through their separate
`docs:generate` and `evidence:generate` task graphs.
Changed selection can miss runtime registration, generated data, shell
entrypoints, bridges, or other relationships absent from the import graph.
An empty changed selection means no tests were selected, not verified
@@ -463,7 +687,9 @@ provider evidence.
Each Vitest project uses up to two workers, bounded by available host parallelism.
Default-group projects can run together. Only `process-boundary` runs in the
later sequence group and serializes its files because process-tree observations
share host sampling resources. Acceptance and process-global files retain isolated
share host sampling resources. Tests that launch the real process harness belong
in `tests/boundary/process/`, including CLI command and environment tests.
Acceptance and process-global files retain isolated
forks but are not serialized; isolation does not imply serial scheduling. CI uses
the same per-project worker bound.
The pure domain/contracts and recording-port service projects share one worker
@@ -532,24 +758,36 @@ conformance/vtable fixture.
Both commands reject a changed executable digest and clean up the fixture
process and helper. These lanes require an interactive macOS desktop. See [native investigation](native-investigation.md)
`npm run verify:native-calls` needs only macOS with Command Line Tools (`clang`,
`lldb`, `codesign`, `nm`). It compiles `tests/conformance/native/calls.m` and
`npm run verify:native-calls` needs macOS with Command Line Tools (`clang`,
`lldb`, `codesign`, `nm`) and actual debugger access to the owned fixtures.
The lane reports Developer Mode status without treating it as proof of access
or denial, and does not change host settings. LLDB and target entitlements
establish the tested permission boundary.
It compiles `tests/conformance/native/calls.m` and
runs `observe-native-calls` through the CLI and stdio MCP. It checks:
- the receiver class, selector and argument registers of every entry, and that
breakpoint addresses equal `nm`'s symbol addresses;
- captured stdout and an environment override;
- overlapping breakpoint selections retain each selection's hits and share
the aggregate event limit; callback capture reads the stopped frame before
LLDB resumes, avoiding stale frame metadata from asynchronous stop events;
- captured stdout and an environment override, plus a 2 MiB flood on each
output stream with bounded retained prefixes and exact drained-byte counts;
- the event-limit and duration outcomes, with the process confirmed gone;
- that a hardened-runtime copy is refused with `debugger-attach-denied`, and
that the same copy signed with `get-task-allow` is traced.
for the exact ABI, authority, graph and observation boundaries.
### Firmware adapters
Linux-owned provider launches require a procps-compatible `ps` on REA's `PATH`
so ownership can be inspected before launching a child. A missing or incompatible
command fails with an actionable capability error before the launch.
`npm run fixtures:firmware` uses existing Python 3 and a host C compiler to make
an ignored gzip/USTAR firmware fixture and independent offset/hash oracle.
`npm run verify:firmware` requires caller-supplied Binwalk 3.1.0, Unblob 26.6.4
and util-linux prlimit on Linux. The provider also accepts other 3.1.x and
and util-linux prlimit on Linux, with `7z` on `PATH` for the gzip/USTAR
fixture's Unblob extractor. The provider also accepts other 3.1.x and
26.6.x builds and reports them as unverified; this lane proves the audited
releases. It verifies CLI/MCP parity, selected ranges,
unknown chunks, depth limits and extracted child digests. The optional
@@ -686,8 +924,48 @@ real local Codex CLI with:
npm run verify:agent
```
Its report records tool selection, repeated calls, token use, completion quality,
and handling of permissions and unknowns.
The packaged desktop and parser-comparison scenarios now assess a closed set of
known fixture claims. Each final answer must be a strict JSON manifest containing
exactly the requested claim IDs, values, Evidence IDs, and Evidence authority and
confidence. Golden values are not included in the prompt. The evaluator checks
exact values against both the fixture oracle and authenticated successful REA
results; an Evidence ID by itself is insufficient. Missing, duplicate, extra,
contradictory, incorrectly sourced, or unsupported claims fail the scenario.
The desktop rubric covers the exposed bridge API and members, renderer and main
IPC operations, and the resolved preload. It binds its Evidence to the exact
packaged artifact path and SHA-256. The parser rubric checks the precise heading
depth addition, discriminant, complete comparison counts, and the explicit limit
on runtime semantics. Its comparison must link to the two delivered source
analyses and use the requested module and export selectors. Boundary tests package
the same source-owned desktop fixture and compare the same source-owned parser
files through actual MCP tool results, then verify that fabricated answers fail.
Per-scenario `factualCorrectness` is `passed`, `failed`, or `not_assessed` within
the `configured_fixture_claims` scope. A pass establishes only the configured
claims, not unrestricted factual correctness. Native, managed, browser,
navigation-context, and address-context scenarios have no factual rubric and
remain `not_assessed`; the managed workflow requires both artifact and member
inspection to answer its type and entry-point question.
All scenarios retain routing, workflow, validation, repetition, process-exit,
and token-use gates. Configured factual scenarios additionally require a factual
pass. Their answer-text heuristics are diagnostic and do not affect the gate.
Scenarios without a rubric retain the legacy text-heuristic gate.
`answerTermCoverageMet` checks case-insensitive substrings,
`epistemicCuePresent` checks keywords, and `finalCitesEvidence` checks only an ID's
presence. These metrics can still accept fabricated prose and must not be read
as factual assessments. Set `REA_AGENT_EVAL_TRANSCRIPT_DIR` to retain complete
tool results and final answers for review.
Report schema version 3 changes 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
configured claim IDs. The evaluation scope is
`routing_workflow_and_configured_fixture_claims`. Update report consumers for
these changes. The narrower heuristic field names introduced in version 2 remain:
`answerHeuristicsMet`, `epistemicCuePresent`, `answerTermCoverageMet`, and
`requiredAnswerTermGroups`.
Regenerate the managed conformance manifest and Evidence completion ledger from
live verification results, or check them for drift:
+9
View File
@@ -126,6 +126,15 @@ process cleanup. Owned decoder diagnostics retain at most 1 MiB across stdout
and stderr; the collector drops overflow as it arrives, reports the limit
failure and verifies cleanup. Heap limits alone do not establish aggregate RSS limits.
Observed decoder memory exhaustion returns `resource_constraint`, with the
selected capture, applicable limits and retained process diagnostics. HAR's
old-generation heap is fixed; inherited `NODE_OPTIONS` cannot raise it.
Record selection happens after complete decoding and does not reduce that
workload. Use a smaller capture exported by its producer or another decoder
with sufficient capacity, retaining the original for provenance. Subsets do
not establish complete-capture coverage. Repeating the same workload or
running provider health diagnostics does not resolve the heap constraint.
Stable regular-file reads reject symlinks, replacement and concurrent changes.
Private snapshots, declarations and tool configuration are removed before the
result returns. Cleanup uncertainty retains the selected capture, resource and
+3 -1
View File
@@ -20,7 +20,9 @@ Results retain:
- The selected target, frame and document loader identity; local armed/end timestamps and separate backend monotonic coverage timestamps.
- Every admitted producer function/block range, exclusive end offset and execution count in UTF-16 code units, with verified bounds when exact text is available. Nested/overlapping counts are preserved and must not be summed.
- Main-document script inventory, exact session script IDs, resource start coordinates, sourceURL/source-map declarations, producer hash and independently computed UTF-8 text digest.
- Armed-window requests from the selected main frame and selected origins, complete reported initiator objects (including stack hierarchy/descriptions), derived callsites, exact script-ID associations and unresolved async parent IDs. Source association does not establish UI causality.
- Armed-window requests from the selected main frame and selected origins, complete reported initiator objects (including stack hierarchy/descriptions), derived callsites, exact script-ID associations and unresolved async parent IDs. Source association does not establish UI causality. Callsite and listener `line_number`/`column_number` values are CDP's zero-based lines and UTF-16 columns in the resource named by `url`. With a `script_id`, that is the script's enclosing resource: the script file for an external script, or the HTML document for an inline script. `resource_start` is where the script text starts in that resource. A callsite with an empty `script_id` comes from the request's reported initiator rather than a script call frame, such as markup that the parser read in a document or a module import in a script file. It still locates a position in `url`, but has no session-script association, so it cannot be joined to captured source or `resource_start`. `trace_web_source_location` takes one-based, script-relative lines and zero-based UTF-16 columns.
For an external script, where `url` is the script file, use `line = line_number + 1` and keep `column = column_number`; this also applies to an empty-`script_id` initiator position in a script file. For an inline script, which requires a `script_id` and its `resource_start`, use `line = line_number - resource_start.line_number + 1`. Subtract `resource_start.column_number` from `column_number` only when `line_number === resource_start.line_number`; keep the reported column on later lines. Columns remain zero-based. This conversion requires known source-start and column coordinates; preserve unavailable coordinates as unknown.
The coverage counter interval starts at backend acceptance before the armed receipt and ends at the resetting sample after the local timer. Counts may include execution during these command intervals; request events are restricted to the locally armed window. The result retains both backend timestamps and the distinct local clock.
+27
View File
@@ -84,6 +84,33 @@ are made.
## Verification
Browser bundle collection uses absolute section offsets for nested inline maps.
An enclosing column offset contributes only when the nested section starts on
that enclosing section's first generated line. Parent boundaries still clip
preceding browser mappings when a nested section starts with a gap or has no
child leaves. Raw map text, original source identities and the dedicated decoder
profile are preserved.
Browser collection accepts up to 64 MiB of map responses per inspection and
preflights indexed sections, sources, names, generated rows and mapping segments
against a 262144-record decode budget before upstream flattening. Indexed maps
are limited to 64 nested levels. These structural limits bound decoded object
growth while preserving large embedded `sourcesContent` strings within the
response-byte budget. It also bounds the expanded inline evidence to 32 MiB
across the inspection, checking resolved source identities and mapping rows
before publishing them, and bounds raw resolved-source identities to 64 MiB
before upstream materializes their arrays. Exceeding either decoder budget
returns a `fetch_failed` item while retaining other maps. Decoded coordinates
and source/name indexes are validated per map; a malformed map is reported as
`invalid` without discarding earlier or later maps.
Collection reserves each requested map's failure context before fetching it.
When the aggregate inline-result budget cannot reserve another item, remaining
requests are left unprocessed and reported as unknown through the top-level
`limitation`; `requested` retains the full count and `processed` counts returned
items.
Malformed UTF-8 map responses are reported as invalid rather than repaired with
replacement characters.
Focused tests cover duplicate mappings, indexed offsets/overlap/local indexes,
32-bit VLQ overflow, nullable content, UTF-16/CRLF positions, immutable artifact
identity, cancellation, late output closure, cleanup failures, SDK schemas and
+1 -2
View File
@@ -10,8 +10,7 @@ verified manifest into one caller-selected, absent directory.
For passive inspection, select `include_script_sources: true` (CLI:
`--include-script-sources`). For scenarios, select
`capture.network.response_body: true`. Save either the complete Evidence record
or its normalized result as UTF-8 JSON. The compact MCP wrapper's `evidence`
field contains the complete Evidence record.
or its normalized result as UTF-8 JSON. MCP `structuredContent` contains the complete Evidence record.
```sh
rea export-web-scripts /analysis/capture.json /analysis/exported-scripts --json
+8 -1
View File
@@ -1,6 +1,13 @@
{
"$schema": "./node_modules/knip/schema.json",
"entry": ["src/main.ts", "src/cli.ts", "src/contracts/errorSchemas.ts"],
"entry": [
"src/main.ts",
"src/cli.ts",
"src/contracts/errorSchemas.ts",
"src/evaluation/AgentEvaluationFixtures.ts",
"src/application/RealHopperAssertions.ts",
"tests/processOwnershipRunner.ts"
],
"project": ["src/**/*.ts", "tests/**/*.ts", "scripts/**/*.mjs"],
"vitepress": {
"entry": ["docs/.vitepress/config.ts"]
+3 -1
View File
@@ -57,7 +57,9 @@ without this lane reports Windows native controls as unavailable.
deduplicated using Windows ordinal comparison, preserving their native Unicode
semantics and caller-selected values.
- Cleanup walks locked directory objects, removes files by handle, and unlinks
reparse entries without traversing their targets. Normal close, cancellation,
reparse entries without traversing their targets. Failed removal retains the
root and ancestor leases for retry; a partially removed runtime accepts only
cleanup, and its registry entry remains until removal succeeds. Normal close, cancellation,
startup failure, and timeout release provider resources. Abrupt owner death
kills the owned job; it can leave private runtime files for later operator
cleanup and does not claim transactional deletion after a crash.
+5 -2
View File
@@ -86,6 +86,7 @@ struct Runtime : Resource {
std::vector<Handle> parents;
Handle directory;
std::vector<Handle> immutableFiles;
bool closing = false;
// Main-thread admission is single-flight; only the accepted copy owns the
// atomic cancellation flag consumed by its async worker. ERROR_BUSY rejects
// another copy before it can reset that flag or replace pending ownership.
@@ -94,7 +95,8 @@ struct Runtime : Resource {
Runtime() : Resource(Kind::Runtime) {}
};
struct Process : Resource {
Handle job, process, stdoutRead, stderrRead;
Handle job, process, stdoutRead, stderrRead, stdinWrite;
bool inputPending = false;
DWORD pid = 0;
Process() : Resource(Kind::Process) {}
};
@@ -122,7 +124,8 @@ void closeRuntime(Runtime& runtime);
napi_value filesystemCall(napi_env env, const std::wstring& operation,
const std::vector<napi_value>& args);
std::unique_ptr<Process> spawnProcess(const std::wstring& command, const std::wstring& commandLine,
const std::wstring& cwd, const std::vector<std::wstring>& environment);
const std::wstring& cwd, const std::vector<std::wstring>& environment,
bool protocolStdin = false);
napi_value processCall(napi_env env, const std::wstring& operation,
const std::vector<napi_value>& args);
napi_value inspect(napi_env env);
+6 -4
View File
@@ -328,14 +328,11 @@ static void disposeSnapshot(Runtime& root, Handle& file, const std::wstring& pat
void closeRuntime(Runtime& root) {
if (root.closed) return;
root.closing = true;
// Windows keeps the bearer descriptor and snapshot leases until this owner
// cleanup. POSIX removes its consumed descriptor earlier. Abrupt owner death
// closes process jobs but cannot run this walk, so private files can remain.
root.immutableFiles.clear();
struct ReleaseRuntime {
Runtime& root;
~ReleaseRuntime() { root.directory.reset(); root.parents.clear(); root.closed = true; }
} release{root};
// Each traversed directory stays locked against replacement. Reparse entries
// are unlinked as objects; their targets are never enumerated or deleted.
struct Frame {
@@ -380,6 +377,10 @@ void closeRuntime(Runtime& root) {
frames.push_back(std::make_unique<Frame>(std::move(child), path));
else dispose(child.get(), path);
}
// Failed walks retain the root's identity leases for a later cleanup retry.
root.directory.reset();
root.parents.clear();
root.closed = true;
}
struct SnapshotWork {
@@ -528,6 +529,7 @@ napi_value filesystemCall(napi_env env, const std::wstring& operation, const std
return result;
}
auto& root = static_cast<Runtime&>(resource(env, args[0], Kind::Runtime));
require(!root.closing || operation == L"runtime_close", "Runtime cleanup is pending", root.path, ERROR_BUSY);
if (operation == L"runtime_snapshot_cancel") { root.snapshotCancelled.store(true); return null(env); }
require(!root.snapshotPending, "Runtime snapshot is still pending", root.path, ERROR_BUSY);
// Reject overlapping operations before snapshot() resets cancellation. The
+100 -9
View File
@@ -34,16 +34,17 @@ static Handle createJob() {
return job;
}
static void pipe(Handle& read, Handle& write) {
static void pipe(Handle& read, Handle& write, bool parentReads = true) {
SECURITY_ATTRIBUTES attributes{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
HANDLE rawRead, rawWrite;
require(CreatePipe(&rawRead, &rawWrite, &attributes, 0), "Create owned process output pipe failed");
read.reset(rawRead); write.reset(rawWrite);
require(SetHandleInformation(read.get(), HANDLE_FLAG_INHERIT, 0), "Protect process output handle inheritance failed");
require(SetHandleInformation(parentReads ? read.get() : write.get(), HANDLE_FLAG_INHERIT, 0), "Protect parent process pipe handle inheritance failed");
}
std::unique_ptr<Process> spawnProcess(const std::wstring& command, const std::wstring& commandLine,
const std::wstring& cwd, const std::vector<std::wstring>& environment) {
const std::wstring& cwd, const std::vector<std::wstring>& environment,
bool protocolStdin) {
const auto executable = ordinaryDriveSeparators(command);
require(executable.size() >= 3 && executable[1] == L':' && executable[2] == L'\\',
"Owned Windows process requires an absolute executable path", command, ERROR_INVALID_PARAMETER);
@@ -52,9 +53,13 @@ std::unique_ptr<Process> spawnProcess(const std::wstring& command, const std::ws
Handle stdoutWrite, stderrWrite;
pipe(result->stdoutRead, stdoutWrite); pipe(result->stderrRead, stderrWrite);
SECURITY_ATTRIBUTES security{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
Handle input(CreateFileW(L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
&security, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
require(input.valid(), "Open owned process null input failed");
Handle input;
if (protocolStdin) pipe(input, result->stdinWrite, false);
else {
input.reset(CreateFileW(L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
&security, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
require(input.valid(), "Open owned process null input failed");
}
Attributes attributes(2);
HANDLE job = result->job.get();
attributes.add(PROC_THREAD_ATTRIBUTE_JOB_LIST, &job, sizeof(job));
@@ -198,16 +203,93 @@ static napi_value callerToken(napi_env env) {
return result;
}
struct InputWork {
napi_async_work work = nullptr;
napi_deferred deferred = nullptr;
napi_ref processReference = nullptr;
Process* process;
Handle pipe;
std::vector<unsigned char> bytes;
size_t written = 0;
std::unique_ptr<Failure> failure;
explicit InputWork(Process& process) : process(&process) {}
};
static void writeInput(napi_env, void* pointer) {
auto& work = *static_cast<InputWork*>(pointer);
try {
// Anonymous pipe writes may block. Keep them off the JS thread so output
// polling and job termination remain live while a child stops reading.
while (work.written < work.bytes.size()) {
DWORD written = 0;
const auto count = static_cast<DWORD>(std::min<size_t>(65536, work.bytes.size() - work.written));
require(WriteFile(work.pipe.get(), work.bytes.data() + work.written, count, &written, nullptr),
"Write owned process input failed");
require(written > 0, "Owned process input write made no progress", L"", ERROR_WRITE_FAULT);
work.written += written;
}
} catch (const Failure& failure) {
work.failure = std::make_unique<Failure>(failure);
} catch (const std::exception& failure) {
work.failure = std::make_unique<Failure>(failure.what(), L"", ERROR_GEN_FAILURE);
}
}
static void completeInput(napi_env env, napi_status status, void* pointer) {
std::unique_ptr<InputWork> work(static_cast<InputWork*>(pointer));
work->process->inputPending = false;
if (work->failure) napi_reject_deferred(env, work->deferred, failureValue(env, *work->failure));
else if (status != napi_ok)
napi_reject_deferred(env, work->deferred, failureValue(env, Failure("Owned process input worker was cancelled", L"", ERROR_OPERATION_ABORTED)));
else napi_resolve_deferred(env, work->deferred, number(env, work->written));
napi_delete_reference(env, work->processReference);
napi_delete_async_work(env, work->work);
}
static napi_value inputWrite(napi_env env, napi_value owner, Process& process, napi_value input) {
require(process.stdinWrite.valid(), "Owned process has no writable input", L"", ERROR_BROKEN_PIPE);
require(!process.inputPending, "Owned process input already has a pending write", L"", ERROR_BUSY);
auto work = std::make_unique<InputWork>(process);
void* bytes = nullptr;
size_t size = 0;
bool buffer = false;
check(napi_is_buffer(env, input, &buffer));
require(buffer, "Owned process input must be a Buffer", L"", ERROR_INVALID_PARAMETER);
check(napi_get_buffer_info(env, input, &bytes, &size));
if (size > 0) work->bytes.assign(static_cast<unsigned char*>(bytes), static_cast<unsigned char*>(bytes) + size);
HANDLE pipe;
require(DuplicateHandle(GetCurrentProcess(), process.stdinWrite.get(), GetCurrentProcess(), &pipe,
0, FALSE, DUPLICATE_SAME_ACCESS), "Retain owned process input handle failed");
work->pipe.reset(pipe);
napi_value promise;
check(napi_create_promise(env, &work->deferred, &promise));
check(napi_create_reference(env, owner, 1, &work->processReference));
try {
check(napi_create_async_work(env, nullptr, string(env, std::string("REA owned process input")),
writeInput, completeInput, work.get(), &work->work));
check(napi_queue_async_work(env, work->work));
process.inputPending = true;
} catch (...) {
if (work->work) napi_delete_async_work(env, work->work);
napi_delete_reference(env, work->processReference);
throw;
}
work.release();
return promise;
}
napi_value processCall(napi_env env, const std::wstring& operation, const std::vector<napi_value>& args) {
if (operation == L"process_caller_token") {
require(args.empty(), "Caller token observation takes no arguments", L"", ERROR_INVALID_PARAMETER);
return callerToken(env);
}
if (operation == L"process_spawn") {
require(args.size() == 4, "Wrong native process launch argument count", L"", ERROR_INVALID_PARAMETER);
require(args.size() == 4 || args.size() == 5, "Wrong native process launch argument count", L"", ERROR_INVALID_PARAMETER);
bool protocolStdin = false;
if (args.size() == 5) check(napi_get_value_bool(env, args[4], &protocolStdin));
std::vector<std::wstring> environment;
for (const auto& entry : array(env, args[3])) environment.push_back(wide(env, entry));
auto process = spawnProcess(wide(env, args[0]), wide(env, args[1]), wide(env, args[2]), environment);
auto process = spawnProcess(wide(env, args[0]), wide(env, args[1]), wide(env, args[2]), environment, protocolStdin);
auto result = object(env);
set(env, result, "pid", number(env, process->pid));
set(env, result, "jobAssigned", boolean(env, true));
@@ -215,8 +297,16 @@ napi_value processCall(napi_env env, const std::wstring& operation, const std::v
set(env, result, "handle", wrap(env, std::move(process)));
return result;
}
if (operation == L"process_stdin_write") {
require(args.size() == 2, "Wrong native process input argument count", L"", ERROR_INVALID_PARAMETER);
return inputWrite(env, args[0], static_cast<Process&>(resource(env, args[0], Kind::Process)), args[1]);
}
require(args.size() == 1, "Wrong native process handle argument count", L"", ERROR_INVALID_PARAMETER);
auto& process = static_cast<Process&>(resource(env, args[0], Kind::Process));
if (operation == L"process_stdin_close") {
process.stdinWrite.reset();
return null(env);
}
if (operation == L"process_poll") {
auto result = object(env);
bool stdoutEnded = false, stderrEnded = false;
@@ -240,7 +330,7 @@ napi_value processCall(napi_env env, const std::wstring& operation, const std::v
}
if (operation == L"process_close") {
// Closing the job is the crash-safe authority; no PID enumeration is used.
process.job.reset(); process.process.reset(); process.stdoutRead.reset(); process.stderrRead.reset();
process.job.reset(); process.process.reset(); process.stdoutRead.reset(); process.stderrRead.reset(); process.stdinWrite.reset();
process.closed = true;
return null(env);
}
@@ -266,6 +356,7 @@ napi_value inspect(napi_env env) {
set(env, result, "privateDacl", boolean(env, true));
set(env, result, "atomicJobAssignment", boolean(env, true));
set(env, result, "killOnOwnerClose", boolean(env, true));
set(env, result, "protocolStdin", boolean(env, true));
return result;
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "rea-agents",
"version": "6.0.0",
"version": "6.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rea-agents",
"version": "6.0.0",
"version": "6.1.0",
"license": "MIT",
"dependencies": {
"@babel/parser": "8.0.4",
+41 -29
View File
@@ -1,7 +1,7 @@
{
"name": "rea-agents",
"mcpName": "io.github.morluto/rea",
"version": "6.0.0",
"version": "6.1.0",
"description": "Reverse engineer anything from your terminal or agent with one CLI and MCP server.",
"license": "MIT",
"repository": {
@@ -80,27 +80,26 @@
],
"scripts": {
"deps:check": "node scripts/check-dependency-install.mjs",
"metadata:generate": "node scripts/generate-package-metadata.mjs",
"metadata:generate": "node scripts/run-exclusive.mjs artifacts node scripts/generate-package-metadata.mjs",
"metadata:check": "node scripts/generate-package-metadata.mjs --check",
"mcp-registry:check": "node scripts/verify-mcp-registry-metadata.mjs",
"mcp-catalog:generate": "npm run build:cached",
"mcp-catalog:check": "node scripts/generate-mcp-tool-catalog.mjs --check",
"prebuild": "npm run deps:check && npm run metadata:check",
"build": "node scripts/run-exclusive.mjs build npm run build:unlocked",
"build:unlocked": "node scripts/clean-build-output.mjs && tsc -p tsconfig.build.json && node scripts/generate-mcp-tool-catalog.mjs && node scripts/generate-skill-metadata.mjs && node scripts/generate-product-catalog.mjs && node scripts/generate-completion-ledger.mjs",
"mcp-catalog:generate": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run artifacts:mcp-catalog",
"mcp-catalog:check": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run mcp-catalog:check:built",
"prebuild": "npm run deps:check",
"build": "node scripts/run-exclusive.mjs artifacts turbo run artifacts:skills --force",
"precheck": "npm run deps:check",
"check": "turbo run typecheck lint format:check knip metadata:check",
"check": "node scripts/run-exclusive.mjs artifacts turbo run typecheck lint format:check knip metadata:check",
"precheck:fast": "npm run deps:check",
"check:fast": "turbo run typecheck lint --output-logs=errors-only",
"precheck:changed": "npm run deps:check",
"check:changed": "npm run check:fast && npm run test:changed",
"precheck:test": "npm run deps:check",
"check:test": "turbo run typecheck lint format:check knip metadata:check test:run",
"check:test": "node scripts/run-exclusive.mjs artifacts turbo run typecheck lint format:check knip metadata:check test:run",
"precheck:pr": "npm run deps:check",
"check:pr": "turbo run typecheck lint format:check knip metadata:check test:run && npm run docs:check && npm run jscpd && npm run scan:todos",
"check:pr": "node scripts/run-exclusive.mjs artifacts turbo run typecheck lint format:check knip metadata:check test:run && npm run docs:check && npm run jscpd && npm run scan:todos",
"precheck:ci": "npm run deps:check",
"check:ci": "turbo run typecheck lint format:check knip metadata:check jscpd scan:todos",
"build:cached": "node scripts/check-dependency-install.mjs && turbo run build",
"check:ci": "node scripts/run-exclusive.mjs artifacts turbo run typecheck lint format:check knip metadata:check jscpd scan:todos",
"build:cached": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run artifacts:skills",
"format": "oxfmt .",
"format:check": "oxfmt --check .",
"lint": "oxlint && npm run verify:module-boundaries",
@@ -110,39 +109,43 @@
"scan:todos": "node scripts/scan-todos.mjs",
"start": "node scripts/rea.mjs mcp",
"test": "npm run test:with-build",
"test:with-build": "npm run build:cached && vitest run",
"test:with-build": "npm run test:prepare && vitest run",
"test:run": "vitest run",
"test:local": "node scripts/run-development-tests.mjs local",
"test:fast": "vitest run --project domain --project services --project adapters --project composition --project conformance --project evaluation",
"pretest:boundary": "npm run build:cached",
"pretest:boundary": "npm run test:prepare",
"test:boundary": "vitest run --project boundary --project process-boundary --project mcp-boundary --project process-global",
"pretest:mcp": "npm run build:cached",
"pretest:mcp": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run artifacts:skills artifacts:mcp-catalog",
"test:mcp": "vitest run --project mcp-boundary",
"pretest:acceptance": "npm run build:cached",
"test:acceptance": "vitest run --project acceptance",
"test:changed": "node scripts/run-development-tests.mjs changed",
"pretest:integration": "npm run build:cached",
"pretest:integration": "npm run test:prepare",
"test:integration": "vitest run --project boundary --project process-boundary --project mcp-boundary --project process-global --project acceptance",
"pretest:coverage": "npm run build:cached",
"pretest:coverage": "npm run test:prepare",
"test:coverage": "vitest run --coverage.enabled --coverage.provider=v8",
"test:ci": "npm run test:coverage -- --reporter=default --reporter=junit --outputFile.junit=test-results.xml",
"pretest:ci:shard": "npm run build:cached",
"pretest:ci:shard": "npm run test:prepare",
"test:ci:shard": "npm run test:ci:shard:run",
"test:ci:shard:run": "vitest run --coverage.enabled --coverage.provider=v8 --coverage.thresholds.lines=0 --coverage.thresholds.functions=0 --coverage.thresholds.branches=0 --coverage.thresholds.statements=0 --reporter=default --reporter=blob",
"test:ci:merge": "vitest --merge-reports --coverage.enabled --coverage.provider=v8",
"pretest:verbose": "npm run build:cached",
"pretest:verbose": "npm run test:prepare",
"test:verbose": "vitest run --reporter=verbose",
"test:watch": "vitest --project domain --project services --project adapters --project composition --project conformance --project evaluation --changed",
"pretest:watch:all": "npm run build:cached",
"pretest:watch:all": "npm run test:prepare",
"test:watch:all": "vitest --changed",
"typecheck": "tsc --noEmit",
"verify:test-temp-hygiene": "npm run build:cached && node scripts/verify-test-temp-hygiene.mjs",
"verify:test-temp-hygiene": "npm run test:prepare && node scripts/verify-test-temp-hygiene.mjs",
"verify:hopper": "npm run verify:fixtures && npm run build:cached && node scripts/verify-real-hopper.mjs",
"verify:hopper:deadlines": "npm run verify:fixtures && npm run build:cached && node scripts/verify-real-hopper-deadlines.mjs",
"verify:hopper:fat": "npm run build:cached && node scripts/verify-real-hopper-fat.mjs",
"verify:hopper:fat64": "npm run build:cached && node scripts/verify-real-hopper-fat.mjs --fat64",
"verify:apple-dispatch": "npm run build:cached && node scripts/verify/apple/dispatch.mjs",
"verify:native-ui": "npm run build:cached && node scripts/verify-native-ui.mjs",
"verify:keyed-archive": "npm run build:cached && node scripts/verify/apple/keyed-archive.mjs",
"verify:macos-bundle": "npm run build:cached && node scripts/verify/apple/macos-bundle.mjs",
"verify:ghidra": "npm run build:cached && node scripts/verify-real-ghidra.mjs",
"verify:ghidra:swift": "npm run build:cached && node scripts/verify-real-ghidra-swift.mjs",
"verify:ida": "npm run build:cached && node scripts/verify-real-ida.mjs",
"verify:ghidra:switch": "npm run build:cached && node scripts/verify-real-ghidra-switch.mjs",
"verify:ghidra:dos": "npm run build:cached && node scripts/verify-real-ghidra-dos.mjs",
@@ -177,18 +180,17 @@
"probe:windows-capabilities": "npm run build:cached && node scripts/windows-capabilities.mjs",
"verify:published": "node scripts/verify-published-package.mjs",
"config:print": "node scripts/print-mcp-config.mjs",
"evidence:generate": "npm run build:cached && node scripts/generate-completion-ledger.mjs",
"evidence:check": "npm run build:cached && node scripts/generate-completion-ledger.mjs --check",
"evidence:generate": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run artifacts:managed-evidence",
"evidence:check": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run evidence:check:built",
"predocs:dev": "npm run docs:generate",
"docs:dev": "vitepress dev docs",
"predocs:build": "npm run docs:generate",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs",
"docs:generate": "node scripts/run-exclusive.mjs docs npm run docs:generate:unlocked",
"docs:generate:unlocked": "npm run metadata:generate && npm run build:cached && node scripts/generate-error-schema.mjs",
"docs:check": "node scripts/run-exclusive.mjs docs turbo run docs:check:built",
"docs:check:built": "node scripts/generate-package-metadata.mjs --check && node scripts/generate-mcp-tool-catalog.mjs --check && node scripts/generate-skill-metadata.mjs --check && node scripts/generate-product-catalog.mjs --check && node scripts/generate-completion-ledger.mjs --check && node scripts/check-doc-facts.mjs && node scripts/generate-error-schema.mjs --check",
"docs:error-schema": "npm run build:cached && node scripts/generate-error-schema.mjs",
"docs:generate": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts npm run docs:prepare",
"docs:check": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run docs:check:built",
"docs:check:built": "node scripts/generate-package-metadata.mjs --check && node scripts/generate-skill-metadata.mjs --check && node scripts/generate-product-catalog.mjs --check && node scripts/generate-completion-ledger.mjs --check && node scripts/check-doc-facts.mjs && node scripts/generate-error-schema.mjs --check",
"docs:error-schema": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run artifacts:error-schema",
"prepack": "node scripts/prepack.mjs",
"prepare": "node scripts/prepare.mjs",
"verify:asset-catalog": "npm run build:cached && node scripts/verify/apple/asset-catalog.mjs",
@@ -213,7 +215,17 @@
"verify:web:network-captures": "npm run build:cached && node scripts/verify-web-network-captures.mjs",
"verify:binary:layout": "npm run build:cached && node scripts/verify-binary-layout.mjs",
"verify:evm:interface": "npm run build:cached && node scripts/verify-evm-interface.mjs",
"verify:recorded:crash": "npm run build:cached && node scripts/verify-recorded-crash.mjs"
"verify:recorded:crash": "npm run build:cached && node scripts/verify-recorded-crash.mjs",
"mcp-catalog:check:built": "node scripts/generate-mcp-tool-catalog.mjs --check",
"compile": "node scripts/clean-build-output.mjs && tsc -p tsconfig.build.json",
"artifacts:mcp-catalog": "node scripts/generate-mcp-tool-catalog.mjs",
"artifacts:skills": "node scripts/generate-skill-metadata.mjs",
"artifacts:product-catalog": "node scripts/generate-product-catalog.mjs",
"artifacts:managed-evidence": "node scripts/generate-completion-ledger.mjs",
"artifacts:error-schema": "node scripts/generate-error-schema.mjs",
"test:prepare": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run artifacts:mcp-catalog artifacts:product-catalog artifacts:managed-evidence",
"evidence:check:built": "node scripts/generate-completion-ledger.mjs --check",
"docs:prepare": "node scripts/generate-package-metadata.mjs && turbo run artifacts:product-catalog artifacts:managed-evidence artifacts:error-schema"
},
"lint-staged": {
"*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}": [
+1 -4
View File
@@ -1,6 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"release-type": "node",
"versioning": "always-bump-minor",
"include-v-in-tag": false,
"changelog-sections": [
{ "type": "feat", "section": "Features", "hidden": false },
@@ -16,10 +17,6 @@
".": {
"package-name": "rea-agents",
"extra-files": [
{ "type": "generic", "path": "README_zh.md" },
{ "type": "generic", "path": "README_ja.md" },
{ "type": "generic", "path": "README_ko.md" },
{ "type": "generic", "path": "README_ar.md" },
{ "type": "generic", "path": "docs/installation.md" },
{
"type": "generic",
+1 -4
View File
@@ -4,7 +4,4 @@ import { fileURLToPath } from "node:url";
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
await Promise.all([
rm(join(root, "dist"), { recursive: true, force: true }),
rm(join(root, ".cache", "tsconfig.build.tsbuildinfo"), { force: true }),
]);
await rm(join(root, "dist"), { recursive: true, force: true });
+18 -20
View File
@@ -61,7 +61,7 @@ const createElectronActiveBoundaryPatches = ({
const target = typeof args[0] === "string" ? args[0] : null;
recordRuntime("preload", "context-bridge-exposure", "attempted", {
target,
argument_shapes: args.map((value) => shape(value)),
argument_shape_values: args,
process_type: processType(),
});
try {
@@ -110,7 +110,7 @@ const createElectronActiveBoundaryPatches = ({
event === "will-download" ? "blocked" : "observed",
{
target: identity(args[0], "webContents"),
argument_shapes: args.map((value) => shape(value)),
argument_shape_values: args,
error: event === "will-download",
},
);
@@ -124,7 +124,7 @@ const createElectronActiveBoundaryPatches = ({
(webContents, permission, callback) => {
recordRuntime("permission", "permission-request", "blocked", {
target: identity(webContents, "webContents"),
argument_shapes: [shape(permission)],
argument_shape_values: [permission],
error: true,
});
if (typeof callback === "function") callback(false);
@@ -137,7 +137,7 @@ const createElectronActiveBoundaryPatches = ({
return originalPermissionCheck.call(this, (webContents, permission) => {
recordRuntime("permission", "permission-check", "blocked", {
target: identity(webContents, "webContents"),
argument_shapes: [shape(permission)],
argument_shape_values: [permission],
error: true,
});
return false;
@@ -148,7 +148,7 @@ const createElectronActiveBoundaryPatches = ({
session.setCertificateVerifyProc = function blockedCertificate() {
return originalCertificate.call(this, (request, callback) => {
recordRuntime("permission", "certificate-error", "blocked", {
argument_shapes: [shape(request)],
argument_shape_values: [request],
error: true,
});
if (typeof callback === "function") callback(-2);
@@ -168,14 +168,14 @@ const createElectronActiveBoundaryPatches = ({
} catch {
recordRuntime("popup-attempt", "window-open-handler", "failed", {
target: contentsId,
argument_shapes: args.map((value) => shape(value)),
argument_shape_values: args,
error: true,
});
return { action: "deny" };
}
recordRuntime("popup-attempt", "window-open-handler", "blocked", {
target: contentsId,
argument_shapes: args.map((value) => shape(value)),
argument_shape_values: args,
error: true,
});
return { action: "deny" };
@@ -195,9 +195,11 @@ const createElectronActiveBoundaryPatches = ({
};
};
const patchSessionManager = (sessionModule) => {
const patchSessionManager = (sessionModule, app) => {
if (sessionModule === null || sessionModule === undefined) return;
patchSession(sessionModule.defaultSession);
if (app === undefined || app.isReady())
patchSession(sessionModule.defaultSession);
else app.once("ready", () => patchSession(sessionModule.defaultSession));
for (const method of ["fromPartition", "fromPath"]) {
const original = sessionModule[method];
if (typeof original !== "function") continue;
@@ -223,7 +225,7 @@ const createElectronActiveBoundaryPatches = ({
method === "relaunch" ? "updater" : "protocol",
method,
"blocked",
{ argument_shapes: args.map((value) => shape(value)), error: true },
{ argument_shape_values: args, error: true },
);
return method === "setAsDefaultProtocolClient" ? false : undefined;
};
@@ -250,11 +252,7 @@ const createElectronActiveBoundaryPatches = ({
recordRuntime("native-addon", "dlopen", "attempted", {
artifact_path: typeof filename === "string" ? filename : null,
artifact_sha256: digest,
argument_shapes: [
shape(module),
shape(filename),
...args.map((value) => shape(value)),
],
argument_shape_value_groups: [[module, filename], args],
});
try {
const result = original.call(this, module, filename, ...args);
@@ -280,7 +278,7 @@ const createElectronActiveBoundaryPatches = ({
if (typeof original !== "function") return;
utilityProcess.fork = function patchedFork(...args) {
recordRuntime("process-lifecycle", "utility-process-fork", "attempted", {
argument_shapes: args.map((value) => shape(value)),
argument_shape_values: args,
process_type: "main",
});
const child = original.apply(this, args);
@@ -304,7 +302,7 @@ const createElectronActiveBoundaryPatches = ({
recordRuntime("process-lifecycle", `utility.${event}`, "observed", {
target: childId,
process_type: "utility",
argument_shapes: eventArgs.map((value) => shape(value)),
argument_shape_values: eventArgs,
});
});
if (child && typeof child.postMessage === "function") {
@@ -344,7 +342,7 @@ const createElectronActiveBoundaryPatches = ({
if (typeof original !== "function") continue;
shell[method] = function blockedShellCall(...args) {
recordRuntime("shell-attempt", method, "blocked", {
argument_shapes: args.map((value) => shape(value)),
argument_shape_values: args,
error: true,
});
return Promise.reject(new Error("External shell effects are blocked"));
@@ -359,7 +357,7 @@ const createElectronActiveBoundaryPatches = ({
if (typeof original !== "function") continue;
childProcess[method] = function patchedChildProcess(...args) {
recordRuntime("process-lifecycle", `child.${method}`, "attempted", {
argument_shapes: args.map((value) => shape(value)),
argument_shape_values: args,
});
const child = original.apply(this, args);
const childId = identity(child, "child");
@@ -369,7 +367,7 @@ const createElectronActiveBoundaryPatches = ({
patchEmitter(child, (event, eventArgs) =>
recordRuntime("process-lifecycle", `child.${event}`, "observed", {
target: childId,
argument_shapes: eventArgs.map((value) => shape(value)),
argument_shape_values: eventArgs,
}),
);
return child;
+241 -37
View File
@@ -16,9 +16,19 @@ const ipcEventKinds = new Set([
"ipc-renderer-post-message",
]);
const events = [];
// Budget serialized data and estimated retained object/array storage together.
const retentionBudgetBytes = 16 * 1024 * 1024;
const snapshotEnvelopeReserveBytes = 1024;
let estimatedRetainedBytes = 0;
let serializedBytes = 0;
let observed = 0;
let observedIpc = 0;
let observedRuntime = 0;
let dropped = 0;
let droppedIpc = 0;
let droppedRuntime = 0;
const droppedFamilies = new Map();
const droppedRoles = new Map();
let sequence = 0;
let correlationSequence = 0;
@@ -43,6 +53,73 @@ const shape = (value, depth = 0) => {
}
};
// Measure JSON strings without allocating an encoded copy of a caller value.
const jsonStringBytes = (value) => {
let bytes = 2;
for (let index = 0; index < value.length; index += 1) {
const code = value.charCodeAt(index);
if (code === 0x22 || code === 0x5c) bytes += 2;
else if (
code === 0x08 ||
code === 0x09 ||
code === 0x0a ||
code === 0x0c ||
code === 0x0d
)
bytes += 2;
else if (code <= 0x1f) bytes += 6;
else if (code >= 0xd800 && code <= 0xdbff) {
const next = value.charCodeAt(index + 1);
if (next >= 0xdc00 && next <= 0xdfff) {
bytes += 4;
index += 1;
} else bytes += 6;
} else if (code >= 0xdc00 && code <= 0xdfff) bytes += 6;
else if (code <= 0x7f) bytes += 1;
else if (code <= 0x7ff) bytes += 2;
else bytes += 3;
if (bytes > retentionBudgetBytes) return bytes;
}
return bytes;
};
const jsonScalarBytes = (value) => {
if (value === null) return 4;
if (typeof value === "string") return jsonStringBytes(value);
if (typeof value === "boolean") return value ? 4 : 5;
return String(value).length;
};
const eventFamily = (kind, processTypeValue) => {
if (kind === "preload" && processTypeValue === "main")
return "preload-configuration";
if (kind.startsWith("ipc-renderer"))
return processTypeValue === "renderer" ? "renderer-ipc" : "ipc";
return ipcEventKinds.has(kind) ? "ipc" : kind;
};
const noteDropped = (kind, processTypeValue, isIpc) => {
dropped += 1;
if (isIpc) droppedIpc += 1;
else droppedRuntime += 1;
const family = eventFamily(kind, processTypeValue);
droppedFamilies.set(family, (droppedFamilies.get(family) ?? 0) + 1);
if (typeof processTypeValue === "string")
droppedRoles.set(
processTypeValue,
(droppedRoles.get(processTypeValue) ?? 0) + 1,
);
if (kind === "window-lifecycle")
droppedRoles.set("window", (droppedRoles.get("window") ?? 0) + 1);
if (kind === "web-contents-lifecycle" || kind === "navigation")
droppedRoles.set(
"web_contents",
(droppedRoles.get("web_contents") ?? 0) + 1,
);
if (kind === "preload" && processTypeValue === "preload")
droppedRoles.set("preload", (droppedRoles.get("preload") ?? 0) + 1);
};
const isAllowedNavigation = (value) => {
if (typeof value !== "string") return true;
try {
@@ -83,7 +160,8 @@ const frameIdentity = (event) => {
const record = (event) => {
observed += 1;
if (ipcEventKinds.has(event.kind)) observedIpc += 1;
const isIpc = ipcEventKinds.has(event.kind);
if (isIpc) observedIpc += 1;
else observedRuntime += 1;
const raw = {
sequence: ++sequence,
@@ -109,10 +187,15 @@ const record = (event) => {
error: false,
...event,
};
const argumentShapes = Array.isArray(raw.argument_shapes)
const shapeGroups = Array.isArray(raw.argument_shape_value_groups)
? raw.argument_shape_value_groups.filter(Array.isArray)
: Array.isArray(raw.argument_shape_values)
? [raw.argument_shape_values]
: [];
const existingShapes = Array.isArray(raw.argument_shapes)
? raw.argument_shapes
: [];
events.push({
const retainedEvent = {
...raw,
correlation_id: raw.correlation_id,
event: typeof raw.event === "string" ? raw.event : null,
@@ -121,9 +204,7 @@ const record = (event) => {
receiver: typeof raw.receiver === "string" ? raw.receiver : null,
frame: typeof raw.frame === "string" ? raw.frame : null,
target: typeof raw.target === "string" ? raw.target : null,
argument_shapes: argumentShapes.map((value) =>
typeof value === "string" ? value : "unknown",
),
argument_shapes: [],
result_shape:
typeof raw.result_shape === "string" ? raw.result_shape : null,
process_type:
@@ -132,8 +213,91 @@ const record = (event) => {
typeof raw.artifact_path === "string" ? raw.artifact_path : null,
artifact_sha256:
typeof raw.artifact_sha256 === "string" ? raw.artifact_sha256 : null,
});
return events.at(-1);
};
delete retainedEvent.argument_shape_values;
delete retainedEvent.argument_shape_value_groups;
// All event values are JSON scalars except argument_shapes. Measure them
// incrementally so large channels, paths, or shape lists never require a
// full JSON.stringify temporary allocation before the budget decision.
let eventBytes = 2;
let argumentShapeCount = 0;
const eventKeys = Object.keys(retainedEvent);
for (let index = 0; index < eventKeys.length; index += 1) {
const key = eventKeys[index];
const value = retainedEvent[key];
if (index > 0) eventBytes += 1;
eventBytes += jsonStringBytes(key) + 1;
if (key === "argument_shapes") {
eventBytes += 2;
let shapeOverflow = false;
const countShape = (argument) => {
const encodedShapeBytes = jsonStringBytes(shape(argument));
eventBytes += encodedShapeBytes + (argumentShapeCount === 0 ? 0 : 1);
argumentShapeCount += 1;
shapeOverflow =
eventBytes + estimatedRetainedBytes + snapshotEnvelopeReserveBytes >
retentionBudgetBytes;
};
for (const group of shapeGroups)
for (const argument of group) {
countShape(argument);
if (shapeOverflow) break;
}
if (shapeOverflow) {
noteDropped(raw.kind, retainedEvent.process_type, isIpc);
return null;
}
for (const valueShape of existingShapes)
if (!shapeOverflow) {
eventBytes +=
jsonStringBytes(
typeof valueShape === "string" ? valueShape : "unknown",
) + (argumentShapeCount++ === 0 ? 0 : 1);
shapeOverflow =
eventBytes + estimatedRetainedBytes + snapshotEnvelopeReserveBytes >
retentionBudgetBytes;
}
if (shapeOverflow) {
noteDropped(raw.kind, retainedEvent.process_type, isIpc);
return null;
}
} else eventBytes += jsonScalarBytes(value);
if (
eventBytes + estimatedRetainedBytes + snapshotEnvelopeReserveBytes >
retentionBudgetBytes
) {
noteDropped(raw.kind, retainedEvent.process_type, isIpc);
return null;
}
}
// Reserve room for the only fields mutated after recording: result_shape and
// error. The shape vocabulary's longest value is "undefined".
const reservedMutationBytes = retainedEvent.result_shape === null ? 7 : 0;
// Include one byte for the event-array separator and reserve space for the
// snapshot's counters and JSON delimiters.
// One retained event, one defensive snapshot copy, and up to two serialized
// result views (timeline plus IPC), with per-object and per-array-slot costs.
const eventEstimateBytes = eventBytes * 3 + 512 + argumentShapeCount * 16;
const retainedEventBytes = eventEstimateBytes + reservedMutationBytes + 1;
if (
estimatedRetainedBytes + retainedEventBytes + snapshotEnvelopeReserveBytes >
retentionBudgetBytes
) {
noteDropped(raw.kind, retainedEvent.process_type, isIpc);
return null;
}
for (const group of shapeGroups)
for (const argument of group)
retainedEvent.argument_shapes.push(shape(argument));
for (const valueShape of existingShapes)
retainedEvent.argument_shapes.push(
typeof valueShape === "string" ? valueShape : "unknown",
);
events.push(retainedEvent);
estimatedRetainedBytes += retainedEventBytes;
serializedBytes += eventBytes + reservedMutationBytes + 1;
return retainedEvent;
};
const recordRuntime = (kind, event, phase, details = {}) =>
@@ -145,7 +309,7 @@ const recordIpc = (kind, channel, args, details = {}) =>
return record({
kind,
channel: typeof channel === "string" ? channel : null,
argument_shapes: values.map((value) => shape(value)),
argument_shape_values: values,
...details,
});
})();
@@ -222,7 +386,7 @@ const patchNavigationMethods = (webContents, contentsId) => {
webContents[method] = function patchedNavigation(...args) {
recordRuntime("navigation", method, "attempted", {
target: contentsId,
argument_shapes: args.map((value) => shape(value)),
argument_shape_values: args,
});
if (method === "loadURL" && !isAllowedNavigation(args[0])) {
recordRuntime("navigation", method, "blocked", {
@@ -354,7 +518,7 @@ const patchWebContents = (webContents, windowId) => {
recordRuntime("navigation", event, "blocked", {
target: contentsId,
sender: windowId,
argument_shapes: args.map((value) => shape(value)),
argument_shape_values: args,
error: true,
});
return;
@@ -363,7 +527,7 @@ const patchWebContents = (webContents, windowId) => {
recordRuntime(kind, event, "observed", {
target: contentsId,
sender: windowId,
argument_shapes: args.map((value) => shape(value)),
argument_shape_values: args,
error: kind === "error",
});
if (event === "did-create-window") {
@@ -379,31 +543,44 @@ const patchWebContents = (webContents, windowId) => {
boundaries.patchWindowOpenHandler(webContents, contentsId);
};
const patchBrowserWindow = (electron) => {
const original = electron.BrowserWindow;
if (typeof original !== "function") return;
electron.BrowserWindow = new Proxy(original, {
construct(target, args, newTarget) {
const window = Reflect.construct(target, args, newTarget);
const targetId = identity(window, "window");
recordRuntime("window-lifecycle", "created", "completed", {
target: targetId,
argument_shapes: args.map((value) => shape(value)),
});
const preload = args[0]?.webPreferences?.preload;
const patchWindows = (app) => {
if (app === null || app === undefined) return;
app.on("browser-window-created", (_event, window) => {
const targetId = identity(window, "window");
recordRuntime("window-lifecycle", "created", "completed", {
target: targetId,
capture_method: "event-emitter",
});
try {
// Electron 44 separates the preload descriptor from WebPreferences.
// Read the engine's descriptor, never infer a path from application code.
const preferences = window.webContents.getLastWebPreferences?.();
const preload =
typeof preferences?.preload === "string"
? preferences.preload
: window.webContents._getPreloadScript?.()?.filePath;
if (typeof preload === "string")
recordRuntime("preload", "configured", "completed", {
target: targetId,
artifact_path: preload,
});
patchEmitter(window, (event) =>
recordRuntime("window-lifecycle", event, "observed", {
} catch (cause) {
recordRuntime(
"error",
cause instanceof Error ? cause.message : String(cause),
"failed",
{
target: targetId,
}),
error: true,
},
);
patchWebContents(window.webContents, targetId);
return window;
},
}
patchEmitter(window, (event) =>
recordRuntime("window-lifecycle", event, "observed", {
target: targetId,
}),
);
patchWebContents(window.webContents, targetId);
});
};
@@ -427,7 +604,7 @@ try {
"observed",
{
target: "app",
argument_shapes: args.map((value) => shape(value)),
argument_shape_values: args,
process_type: "main",
},
),
@@ -435,33 +612,60 @@ try {
patchIpcMain(electron.ipcMain);
boundaries.patchIpcRenderer(electron.ipcRenderer);
boundaries.patchContextBridge(electron.contextBridge);
patchBrowserWindow(electron);
patchWindows(electron.app);
boundaries.patchUtilityProcess(electron.utilityProcess);
boundaries.patchShell(electron.shell);
boundaries.patchSessionManager(electron.session);
boundaries.patchSessionManager(electron.session, electron.app);
boundaries.patchApplicationEffects(electron.app);
boundaries.patchNativeAddonLoading();
boundaries.patchChildProcess();
process.once("uncaughtException", (cause, origin) => {
recordRuntime("process-lifecycle", "uncaught-exception", "observed", {
argument_shapes: [shape(cause), shape(origin)],
argument_shape_values: [cause, origin],
error: true,
});
throw cause;
});
process.once("unhandledRejection", (reason, promise) => {
recordRuntime("process-lifecycle", "unhandled-rejection", "observed", {
argument_shapes: [shape(reason), shape(promise)],
argument_shape_values: [reason, promise],
error: true,
});
throw reason;
});
} catch {
} catch (cause) {
globalThis.__reaElectronActiveHookError = true;
recordRuntime(
"error",
cause instanceof Error ? cause.message : String(cause),
"failed",
{
target: "active-hook-installation",
error: true,
},
);
}
globalThis.__reaElectronActiveSnapshot = () => ({
events: events.slice(),
events: events.map((event) => ({
...event,
argument_shapes: [...event.argument_shapes],
})),
retention_budget_bytes: retentionBudgetBytes,
estimated_retained_bytes: estimatedRetainedBytes,
event_serialized_byte_upper_bound: serializedBytes,
retained: events.length,
dropped,
dropped_ipc: droppedIpc,
dropped_runtime: droppedRuntime,
dropped_event_families: [...droppedFamilies].map(([family, count]) => ({
family,
count,
})),
dropped_event_roles: [...droppedRoles].map(([role, count]) => ({
role,
count,
})),
observed,
observed_ipc: observedIpc,
observed_runtime: observedRuntime,
+31 -8
View File
@@ -4,7 +4,7 @@ import { access } from "node:fs/promises";
import { isAbsolute } from "node:path";
/** Generate a real compiler map and serve only source-owned fixture code. */
export async function startBrowserSourceMapSite() {
export async function startBrowserSourceMapSite(options = {}) {
const command = process.env.REA_WEB_SOURCE_MAP_COMPILER;
if (!command || !isAbsolute(command))
throw new Error(
@@ -19,19 +19,40 @@ export async function startBrowserSourceMapSite() {
const compiled = await compiler
.transform(original, {
loader: "ts",
sourcefile: "../src/fixture.ts?v=7#original",
sourcefile: options.sourcefile ?? "../src/fixture.ts?v=7#original",
sourcemap: "external",
minify: true,
format: "iife",
target: "es2022",
})
.finally(() => compiler.stop());
const prefixes = options.prefixes ?? [];
const generated =
prefixes.join("") +
compiled.code +
(options.sourceMapAnnotation
? "\n//# sourceMappingURL=/maps/app.js.map"
: "");
let indexed = JSON.parse(compiled.map);
for (const prefix of prefixes.toReversed()) {
const offset = textPosition(prefix, prefix.length);
indexed = {
version: 3,
sections: [
{
offset: { line: offset.line - 1, column: offset.column },
map: indexed,
},
],
};
}
const map = prefixes.length === 0 ? compiled.map : JSON.stringify(indexed);
const token = '"source-map proof"';
const generatedOffset = compiled.code.indexOf(token);
const generatedOffset = generated.indexOf(token);
const originalOffset = original.indexOf(token);
if (generatedOffset < 0 || originalOffset < 0)
throw new Error("Compiler fixture marker missing");
const position = textPosition(compiled.code, generatedOffset);
const position = textPosition(generated, generatedOffset);
const originalPosition = textPosition(original, originalOffset);
let mapRequests = 0;
const server = createServer((request, response) => {
@@ -42,12 +63,12 @@ export async function startBrowserSourceMapSite() {
if (request.url === "/app.js?build=7")
return response
.writeHead(200, { "Content-Type": "text/javascript" })
.end(compiled.code);
.end(generated);
if (request.url?.startsWith("/maps/app.js.map")) {
mapRequests += 1;
return response
.writeHead(200, { "Content-Type": "application/json" })
.end(compiled.map);
.end(map);
}
response.writeHead(404).end("Unselected");
});
@@ -58,8 +79,10 @@ export async function startBrowserSourceMapSite() {
return {
origin: `http://127.0.0.1:${address.port}`,
original,
generated: compiled.code,
map: compiled.map,
generated,
map,
leafCode: compiled.code,
leafMap: compiled.map,
position,
originalPosition,
originalOffset,
@@ -7,6 +7,13 @@ export async function startBrowserVerifierSite() {
let port = 0;
let sessionGeneration = 0;
const server = createServer((request, response) => {
if (request.url === "/slow-json") {
response.setHeader("content-type", "application/json");
response.write('{"pending":');
const timer = setTimeout(() => response.end("true}"), 350);
response.once("close", () => clearTimeout(timer));
return;
}
if (request.url === "/screenshot-noise") {
response.setHeader("content-type", "text/html");
response.end(`<!doctype html><html><body style="margin:0"><canvas width="2048" height="1536"></canvas><script>
@@ -27,6 +34,50 @@ export async function startBrowserVerifierSite() {
response.end(sourceMap());
return;
}
if (request.url === "/bad.js.map") {
response.setHeader("content-type", "application/source-map+json");
response.end(
JSON.stringify({
version: 3,
names: [],
sources: ["../src/bad.ts"],
mappings: "AAAAD",
}),
);
return;
}
if (request.url === "/expanded.js.map") {
response.setHeader("content-type", "application/source-map+json");
response.end(
JSON.stringify({
version: 3,
names: [],
sources: ["../src/" + "x".repeat(300000) + ".ts"],
mappings: "AAAA" + ",CAAA".repeat(127),
}),
);
return;
}
if (request.url === "/overflow.js.map") {
response.setHeader("content-type", "application/source-map+json");
response.end(
JSON.stringify({
version: 3,
sections: [
{
offset: { line: 0, column: Number.MAX_SAFE_INTEGER },
map: {
version: 3,
names: [],
sources: ["overflow.ts"],
mappings: "CAAA",
},
},
],
}),
);
return;
}
if (request.url === "/session-generation") {
response.setHeader("content-type", "application/json");
response.end(JSON.stringify({ generation: sessionGeneration }));
@@ -37,6 +88,27 @@ export async function startBrowserVerifierSite() {
response.end(browserScript(port));
return;
}
if (request.url?.startsWith("/bad.js") === true) {
response.setHeader("content-type", "text/javascript");
response.end(
"export const badMapProbe = true;\n//# sourceMappingURL=/bad.js.map",
);
return;
}
if (request.url?.startsWith("/expanded.js") === true) {
response.setHeader("content-type", "text/javascript");
response.end(
"export const expandedMapProbe = true;\n//# sourceMappingURL=/expanded.js.map",
);
return;
}
if (request.url?.startsWith("/overflow.js") === true) {
response.setHeader("content-type", "text/javascript");
response.end(
"export const overflowMapProbe = true;\n//# sourceMappingURL=/overflow.js.map",
);
return;
}
if (request.url?.startsWith("/api") === true) {
response.setHeader("content-type", "application/json");
response.setHeader(
@@ -109,10 +181,18 @@ const browserScript = (port) => `
},
body: JSON.stringify({ token: "request-body-secret-value", active: true })
});
fetch("/slow-json", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ pending: true })
});
const socket = new WebSocket("ws://127.0.0.1:${String(port)}/live?token=websocket-url-secret");
socket.addEventListener("open", () => socket.send(JSON.stringify({ token: "websocket-secret-value" })));
socket.addEventListener("message", () => socket.close());
};
void import("/bad.js");
void import("/expanded.js");
void import("/overflow.js");
observe();
setInterval(observe, 150);
let observedGeneration = 0;
+36 -7
View File
@@ -11,6 +11,7 @@ import WebSocket, { WebSocketServer } from "ws";
*/
export async function startPageCdpProxy(upstreamEndpoint) {
const clients = new Set();
const upstreams = new Set();
const server = createServer(async (request, response) => {
try {
if (request.url !== "/json/version" && request.url !== "/json/list") {
@@ -43,13 +44,18 @@ export async function startPageCdpProxy(upstreamEndpoint) {
const upstream = new WebSocket(
new URL(request.url, upstreamEndpoint).href.replace(/^http/, "ws"),
);
upstreams.add(upstream);
const pending = [];
clients.add(client);
const close = () => {
const closeClient = () => {
clients.delete(client);
if (client.readyState < WebSocket.CLOSING) client.close();
if (upstream.readyState < WebSocket.CLOSING) upstream.close();
};
const finishUpstream = () => {
upstreams.delete(upstream);
closeClient();
};
client.on("message", (data, binary) => {
if (upstream.readyState === WebSocket.OPEN)
upstream.send(data, { binary });
@@ -63,10 +69,10 @@ export async function startPageCdpProxy(upstreamEndpoint) {
pending.length = 0;
webSockets.emit("connection", client, request);
});
client.on("close", close);
client.on("error", close);
upstream.on("close", close);
upstream.on("error", close);
client.on("close", closeClient);
client.on("error", closeClient);
upstream.on("close", finishUpstream);
upstream.on("error", closeClient);
});
});
await new Promise((resolve, reject) => {
@@ -81,10 +87,20 @@ export async function startPageCdpProxy(upstreamEndpoint) {
endpoint,
disconnectClients() {
for (const client of clients) client.terminate();
for (const upstream of upstreams) upstream.terminate();
},
async close() {
for (const client of clients) client.terminate();
webSockets.close();
const activeClients = [...clients];
const activeUpstreams = [...upstreams];
await Promise.all([
terminateSockets(activeClients),
terminateSockets(activeUpstreams),
]);
await new Promise((resolve, reject) =>
webSockets.close((error) =>
error === undefined ? resolve() : reject(error),
),
);
await new Promise((resolve, reject) =>
server.close((error) =>
error === undefined ? resolve() : reject(error),
@@ -94,6 +110,19 @@ export async function startPageCdpProxy(upstreamEndpoint) {
};
}
async function terminateSockets(sockets) {
const closing = [...sockets]
.filter((socket) => socket.readyState !== WebSocket.CLOSED)
.map(
(socket) =>
new Promise((resolve) => {
socket.once("close", resolve);
socket.terminate();
}),
);
await Promise.all(closing);
}
async function pageScopedVersion(upstreamEndpoint, version) {
const targets = await upstreamJson(upstreamEndpoint, "/json/list");
const page = targets.find(
+10 -104
View File
@@ -1,13 +1,12 @@
import { mkdir } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { formatGeneratedFile } from "./lib/format-generated-file.mjs";
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { Ajv2020 } from "ajv/dist/2020.js";
import { McpServer } from "@modelcontextprotocol/server";
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
import { MANAGED_WORKFLOW_TOOL_CONTRACTS } from "../dist/contracts/managed/managedWorkflowToolContracts.js";
import { auxiliaryAnalysisProviderDeclarations } from "../dist/composition/auxiliaryAnalysisProviders.js";
import { toolRegistrationOptions } from "../dist/server/toolRegistrationOptions.js";
import { ensureGeneratedFile } from "./lib/generated-file.mjs";
@@ -17,10 +16,9 @@ for (const argument of arguments_)
throw new Error(`Unknown MCP tool catalog option: ${argument}`);
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
// The build-generated catalog is a portable package artifact that is not
// committed to git. Keep its canonical provider metadata independent of the
// machine running the generator. It is regenerated by `npm run build`.
const CATALOG_PLATFORM = "darwin";
// This generated metadata supports schema conformance tests;
// production MCP tools are registered from their canonical contracts directly.
// Keep the test metadata portable and regenerate it with `npm run mcp-catalog:generate`.
const sessionToolNames = new Set([
...TOOL_CONTRACTS.filter(({ kind }) => kind === "session").map(
({ name }) => name,
@@ -87,10 +85,6 @@ async function sdkToolCatalog() {
await Promise.allSettled([client.close(), server.close()]);
}
}
const auxiliaryProviders = auxiliaryAnalysisProviderDeclarations(
CATALOG_PLATFORM,
).map(({ identity, capabilities }) => ({ identity, capabilities }));
const canonicalizeJson = (value) => {
if (Array.isArray(value)) return value.map(canonicalizeJson);
if (value === null || typeof value !== "object") return value;
@@ -101,101 +95,13 @@ const canonicalizeJson = (value) => {
);
};
const payloadJson = JSON.stringify(
canonicalizeJson({ catalog, auxiliaryProviders }),
null,
2,
);
const source = await formatGeneratedFile(
"src/generatedMcpToolCatalog.ts",
`import type { ToolAnnotations } from "@modelcontextprotocol/server";
import type {
CapabilityDescriptor,
ProviderIdentity,
} from "./application/AnalysisProvider.js";
import type { ToolKind } from "./contracts/toolContractTypes.js";
import type { ToolEffects } from "./contracts/toolEffects.js";
interface GeneratedTool {
readonly name: string;
readonly analysisOperation: CapabilityDescriptor["operation"] | null;
readonly title: string;
readonly description: string;
readonly kind: ToolKind;
readonly requiresSession: boolean;
readonly inputSchema: Readonly<Record<string, unknown>>;
readonly outputSchema: Readonly<Record<string, unknown>>;
readonly annotations: ToolAnnotations;
readonly effects: ToolEffects;
}
interface GeneratedProvider {
readonly identity: ProviderIdentity;
readonly capabilities: readonly CapabilityDescriptor[];
}
interface GeneratedPayload {
readonly catalog: readonly GeneratedTool[];
readonly auxiliaryProviders: readonly GeneratedProvider[];
}
const GENERATED_PAYLOAD_JSON = ${JSON.stringify(payloadJson)};
const generatedPayload: unknown = JSON.parse(GENERATED_PAYLOAD_JSON);
if (!isGeneratedPayload(generatedPayload))
throw new TypeError("Generated MCP catalog payload is invalid");
/** Generated from TOOL_CONTRACTS; do not edit. */
export const GENERATED_MCP_TOOL_CATALOG = generatedPayload.catalog;
/** Generated lightweight metadata for lazily loaded auxiliary providers. */
export const GENERATED_AUXILIARY_PROVIDERS = generatedPayload.auxiliaryProviders;
function isGeneratedPayload(value: unknown): value is GeneratedPayload {
return (
isRecord(value) &&
Array.isArray(value.catalog) &&
value.catalog.every(isGeneratedTool) &&
Array.isArray(value.auxiliaryProviders) &&
value.auxiliaryProviders.every(isGeneratedProvider)
);
}
function isGeneratedTool(value: unknown): value is GeneratedTool {
return (
isRecord(value) &&
typeof value.name === "string" &&
(typeof value.analysisOperation === "string" ||
value.analysisOperation === null) &&
typeof value.title === "string" &&
typeof value.description === "string" &&
typeof value.kind === "string" &&
typeof value.requiresSession === "boolean" &&
isRecord(value.inputSchema) &&
isRecord(value.outputSchema) &&
isRecord(value.annotations) &&
isRecord(value.effects)
);
}
function isGeneratedProvider(value: unknown): value is GeneratedProvider {
return (
isRecord(value) &&
isRecord(value.identity) &&
Array.isArray(value.capabilities)
);
}
function isRecord(
value: unknown,
): value is Readonly<Record<string, unknown>> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
`,
);
const payloadJson = JSON.stringify(canonicalizeJson({ catalog }), null, 2);
const outputPath = join(root, ".cache/mcp-tool-catalog.json");
if (!arguments_.has("--check"))
await mkdir(dirname(outputPath), { recursive: true });
await ensureGeneratedFile({
path: join(root, "src/generatedMcpToolCatalog.ts"),
source,
path: outputPath,
source: `${payloadJson}\n`,
check: arguments_.has("--check"),
generateCommand: "npm run mcp-catalog:generate",
});
+19 -6
View File
@@ -14,13 +14,23 @@ const environment = () => ({
});
/** Execute the compiled CLI with production providers and no test adapters. */
export async function artifactCli(command, target, arguments_ = []) {
return (await artifactCliEvidence(command, target, arguments_))
export async function artifactCli(
command,
target,
arguments_ = [],
options = {},
) {
return (await artifactCliEvidence(command, target, arguments_, options))
.normalized_result;
}
/** Execute the compiled CLI and return its complete Evidence record. */
export async function artifactCliEvidence(command, target, arguments_ = []) {
export async function artifactCliEvidence(
command,
target,
arguments_ = [],
{ timeoutMs = 60000 } = {},
) {
const { stdout } = await promisify(execFile)(
process.execPath,
[
@@ -30,7 +40,7 @@ export async function artifactCliEvidence(command, target, arguments_ = []) {
...arguments_,
"--json",
],
{ env: environment(), timeout: 60000, maxBuffer: 16 * 1024 * 1024 },
{ env: environment(), timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 },
);
const evidence = JSON.parse(stdout);
assert.equal(evidence.error, undefined, stdout);
@@ -65,6 +75,9 @@ export async function withArtifactMcp(target, verify) {
export async function artifactMcpResult(client, name, arguments_ = {}) {
const result = await client.callTool({ name, arguments: arguments_ });
assert.notEqual(result.isError, true, JSON.stringify(result));
assert.ok(result.structuredContent?.result, "MCP omitted structured result");
return result.structuredContent.result;
assert.ok(
result.structuredContent?.normalized_result,
"MCP omitted structured result",
);
return result.structuredContent.normalized_result;
}
@@ -0,0 +1,209 @@
import { createServer } from "node:http";
import { fileURLToPath } from "node:url";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { artifactCliEvidence, artifactMcpResult } from "./artifact-e2e.mjs";
const ANNOTATION_BYTES = 2 * 1024 * 1024;
const SCRIPT_COUNT = 5;
/** Verify real Chrome scriptParsed metadata is bounded before public capture retention. */
export async function verifyBrowserCaptureMetadataBudget(endpoint) {
const marker = "rea-capture-metadata-budget";
let page = "";
let pageReady = false;
let fixtureOrigin = "";
const server = createServer(
{ maxHeaderSize: 3 * 1024 * 1024 },
(request, response) => {
if (request.url?.startsWith("/maps/") === true) {
response.writeHead(200, {
"content-type": "application/source-map+json",
});
response.end(
JSON.stringify({
version: 3,
names: [],
sources: [`${fixtureOrigin}/source.ts`],
sourcesContent: ["export const retainedSource = true;"],
mappings: "AAAA",
}),
);
return;
}
if (request.url === "/capture-metadata-ready") {
pageReady = true;
response.writeHead(204).end();
return;
}
if (request.url !== "/capture-metadata-budget") {
response.writeHead(404).end();
return;
}
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
response.end(page);
},
);
await new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
let targetId;
try {
const address = server.address();
if (address === null || typeof address === "string")
throw new Error("Browser metadata fixture did not bind a TCP port");
fixtureOrigin = `http://127.0.0.1:${String(address.port)}`;
const annotations = Array.from(
{ length: SCRIPT_COUNT },
(_, index) =>
`<script>globalThis["${marker}${index}"]=true;\n//# sourceMappingURL=${fixtureOrigin}/maps/${String(index)}-${"x".repeat(ANNOTATION_BYTES)}.map\n</script>`,
).join("");
page = `<!doctype html><meta charset="utf-8">${annotations}<script>fetch("/capture-metadata-ready")</script>`;
const fixtureUrl = `${fixtureOrigin}/capture-metadata-budget`;
const created = await fetch(
`${endpoint}/json/new?${encodeURIComponent(fixtureUrl)}`,
{ method: "PUT" },
);
if (!created.ok)
throw new Error(`Chrome target creation failed: HTTP ${created.status}`);
const target = await created.json();
if (typeof target.id !== "string" || target.id.length === 0)
throw new Error("Chrome target creation returned no target ID");
targetId = target.id;
const deadline = Date.now() + 15_000;
let served = false;
while (!served && Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 20));
served = pageReady;
}
if (!served) throw new Error("Chrome did not request the metadata fixture");
const evidence = await artifactCliEvidence("inspect-web-page", endpoint, [
targetId,
"--allowed-origins",
fixtureOrigin,
"--observation-ms",
"0",
]);
const cliFacts = assertMetadataBudget(evidence.normalized_result);
const bundle = await artifactCliEvidence("analyze-web-bundle", endpoint, [
targetId,
"--allowed-origins",
fixtureOrigin,
"--observation-ms",
"0",
"--fetch-source-maps",
]);
assertBundleMetadataOmissions(
bundle.normalized_result,
cliFacts.omittedMaps,
);
const transport = new StdioClientTransport({
command: process.execPath,
args: [fileURLToPath(new URL("../rea.mjs", import.meta.url)), "mcp"],
env: { PATH: process.env.PATH ?? "", REA_LOG_LEVEL: "silent" },
stderr: "pipe",
});
const client = new Client({ name: "browser-metadata-e2e", version: "1" });
try {
await client.connect(transport);
assertMetadataBudget(
await artifactMcpResult(client, "inspect_web_page", {
cdp_endpoint: endpoint,
allowed_origins: [fixtureOrigin],
target_id: targetId,
observation_ms: 0,
}),
);
assertBundleMetadataOmissions(
await artifactMcpResult(client, "analyze_web_bundle", {
cdp_endpoint: endpoint,
allowed_origins: [fixtureOrigin],
target_id: targetId,
observation_ms: 0,
fetch_source_maps: true,
}),
cliFacts.omittedMaps,
);
} finally {
try {
await client.close();
} finally {
await transport.close();
}
}
return { ...cliFacts, public_cli: true, public_stdio_mcp: true };
} finally {
try {
if (targetId !== undefined) {
const closed = await fetch(
`${endpoint}/json/close/${encodeURIComponent(targetId)}`,
);
if (!closed.ok)
throw new Error(
`Chrome fixture target cleanup failed: HTTP ${closed.status}`,
);
}
} finally {
server.closeAllConnections();
await new Promise((resolve, reject) =>
server.close((error) =>
error === undefined ? resolve() : reject(error),
),
);
}
}
}
function assertMetadataBudget(result) {
const scripts = result.scripts.items.filter(
(script) =>
script.url.includes("capture-metadata-budget") &&
script.length >= ANNOTATION_BYTES,
);
const retainedMaps = scripts.filter(
(script) => script.source_map_url !== null,
).length;
const omittedMaps = SCRIPT_COUNT - retainedMaps;
const exclusion = result.completeness.excluded.find(
(item) =>
item.section === "source_maps" &&
item.reason === "resource_budget_exhausted",
);
if (
scripts.length !== SCRIPT_COUNT ||
retainedMaps <= 0 ||
retainedMaps >= SCRIPT_COUNT ||
exclusion?.count !== omittedMaps ||
!result.completeness.truncated_sections.includes("source_maps")
)
throw new Error(
`Real Chrome source-map metadata admission was not reported accurately: scripts=${String(scripts.length)}, retained_maps=${String(retainedMaps)}, excluded=${String(exclusion?.count)}`,
);
return {
scripts: scripts.length,
retainedMaps,
omittedMaps,
declaredBytes: ANNOTATION_BYTES * SCRIPT_COUNT,
};
}
function assertBundleMetadataOmissions(result, omittedMaps) {
const unknown = result.unknowns.find(
({ dimension, reason }) =>
dimension === "source_maps" &&
reason.includes(String(omittedMaps)) &&
reason.includes("source-map declarations"),
);
if (
result.observations.source_maps.status !== "included" ||
result.observations.source_maps.items.length === 0 ||
result.completeness.status !== "partial" ||
unknown === undefined
)
throw new Error(
"Bundle analysis lost capture omissions despite valid retained maps",
);
}
+1 -2
View File
@@ -48,8 +48,7 @@ export async function verifyBrowserDomDestinations(
});
assert.notEqual(result.isError, true, mcpTextValue(result));
assertFormDestinations(
parseEvidence(JSON.parse(mcpTextValue(result)).evidence)
.normalized_result,
parseEvidence(JSON.parse(mcpTextValue(result))).normalized_result,
documentUrl,
);
return { cli: true, stdio_mcp: true, current_document_destinations: 4 };
+3 -5
View File
@@ -105,9 +105,7 @@ export async function verifyBrowserModules(
arguments: input,
});
assert.notEqual(response.isError, true, mcpTextValue(response));
const mcpEvidence = parseEvidence(
JSON.parse(mcpTextValue(response)).evidence,
);
const mcpEvidence = parseEvidence(JSON.parse(mcpTextValue(response)));
const mcpResult = assertTrace(mcpEvidence, oracle, site, scriptIndex);
assert.deepEqual(mcpResult, cliResult, "CLI and MCP module traces differ");
assert.equal(
@@ -143,7 +141,7 @@ export async function verifyBrowserModules(
},
}),
),
).evidence,
),
);
const result = webModuleTraceResultSchema.parse(
withoutMap.normalized_result,
@@ -236,7 +234,7 @@ export async function verifyBrowserModules(
},
});
assert.notEqual(response.isError, true, mcpTextValue(response));
evidence = parseEvidence(JSON.parse(mcpTextValue(response)).evidence);
evidence = parseEvidence(JSON.parse(mcpTextValue(response)));
}
const result = webModuleTraceResultSchema.parse(
evidence.normalized_result,
@@ -0,0 +1,238 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { TraceMap, eachMapping } from "@jridgewell/trace-mapping";
import { CdpBrowserProvider } from "../../dist/browser/CdpBrowserProvider.js";
import { CdpConnection } from "../../dist/browser/CdpConnection.js";
import { waitForBrowserDevtoolsPort } from "../../dist/browser/BrowserProcessStartup.js";
import { parseEvidence } from "../../dist/domain/evidence.js";
import { traceSourceMap } from "../../dist/javascript/sourceMaps/TraceSourceMap.js";
import { startBrowserSourceMapSite } from "../fixtures/browser-source-map-site.mjs";
import {
startRuntimeFixtureBrowser,
closeRuntimeFixtureResources,
} from "./browser-runtime-fixture-lifecycle.mjs";
const cases = [
{ name: "same-line nested column control", prefixes: ["\n\n ", " "] },
{ name: "zero outer column control", prefixes: ["\n\n", "\n "] },
{ name: "nested line resets outer column", prefixes: ["\n\n ", "\n"] },
{
name: "nested line retains its own column",
prefixes: ["\n\n ", "\n "],
},
];
/** Verify compiler-produced nested maps through an actual owned Chrome and public CLI. */
export async function verifyBrowserNestedSourceMaps(
executable,
entrypoint = fileURLToPath(new URL("../rea.mjs", import.meta.url)),
) {
const results = [];
for (const scenario of cases) {
try {
const proof = await verifyCase(executable, entrypoint, scenario);
results.push({ name: scenario.name, status: "pass", ...proof });
} catch (cause) {
results.push({
name: scenario.name,
status: "fail",
phase: cause.qualificationPhase ?? "native-producer-admission",
proof: cause.qualificationReceipt ?? null,
error: String(cause),
});
}
}
// Preserve every affected owner and control even when BEFORE fails.
process.stdout.write(
`${JSON.stringify({ nested_source_map_cases: results })}\n`,
);
assert.equal(results.filter(({ status }) => status === "fail").length, 0);
return { cases: results, actual_chrome: true, public_cli: true };
}
async function verifyCase(executable, entrypoint, scenario) {
const site = await startBrowserSourceMapSite({
prefixes: scenario.prefixes,
sourceMapAnnotation: true,
sourcefile: "../src/nested-fixture.ts",
});
let fixture;
let connection;
let primaryError;
let stderr = "";
let phase = "native-map-admission";
let browserVersion;
let expected;
let observed;
try {
const mapUrl = `${site.origin}/maps/app.js.map`;
// This independent existing decoder already composes valid nested offsets.
// It is a before-product admission control, not the expected browser oracle.
const point = traceSourceMap(site.map, mapUrl, site.position);
assert.equal(point.matches.length, 1);
assert.equal(
point.matches[0].original_position.line,
site.originalPosition.line,
);
assert.equal(
point.matches[0].original_position.column,
site.originalPosition.column,
);
expected = compilerMappingsAtPhysicalPositions(site, mapUrl);
assert.ok(
expected.length > 0,
"Native compiler leaf must carry point mappings",
);
phase = "owned-browser-startup";
fixture = await startRuntimeFixtureBrowser(executable, site.origin);
fixture.browser.stderr.on("data", (chunk) => {
if (stderr.length < 65536) stderr += chunk;
});
const port = await waitForBrowserDevtoolsPort({
child: fixture.browser,
executable,
activePortPath: join(fixture.profile, "DevToolsActivePort"),
stderr: () => stderr,
timeoutMs: 20_000,
});
const endpoint = `http://127.0.0.1:${port}`;
const provider = new CdpBrowserProvider();
const targets = await provider.listTargets({
cdp_endpoint: endpoint,
allowed_origins: [site.origin],
});
if (!targets.ok) throw targets.error;
const target = targets.value.targets[0];
assert.ok(target, "Owned fixture target missing");
const pages = await (await fetch(`${endpoint}/json/list`)).json();
const page = pages.find(({ id }) => id === target.target_id);
assert.ok(page, "Owned CDP fixture page missing");
connection = await CdpConnection.connect(
page.webSocketDebuggerUrl,
"inspect_web_page",
);
await ready(connection);
browserVersion = await connection.send("Browser.getVersion");
phase = "public-cli";
const { stdout } = await promisify(execFile)(
process.execPath,
[
entrypoint,
"analyze-web-bundle",
endpoint,
target.target_id,
"--allowed-origins",
site.origin,
"--fetch-source-maps",
"--observation-ms",
"0",
"--json",
],
{
env: { ...process.env, REA_LOG_LEVEL: "silent" },
timeout: 45_000,
maxBuffer: 16 * 1024 * 1024,
},
);
const evidence = parseEvidence(JSON.parse(stdout));
assert.equal(evidence.operation, "analyze_web_bundle");
const maps = evidence.normalized_result.observations.source_maps;
const item = maps.items.find(({ artifact }) => artifact?.text === site.map);
assert.ok(
item,
"Public browser analysis omitted the exact compiler-derived map",
);
phase = "public-map-assertions";
observed = item?.mappings;
assert.equal(item.status, "included");
assert.deepEqual(item.mappings, expected);
assert.equal(item.artifact.sha256, sha(site.map));
assert.equal(item.artifact.bytes, Buffer.byteLength(site.map));
assert.equal(item.original_sources[0].artifact.sha256, sha(site.original));
assert.ok(
site.mapRequests() > 0,
"Actual browser workflow did not fetch the declared map",
);
return {
compiler: site.compiler,
browser: browserVersion.product,
generated_sha256: sha(site.generated),
source_map_sha256: sha(site.map),
original_sha256: sha(site.original),
mappings: expected.length,
native_decoder_control: true,
};
} catch (cause) {
const failure = new Error(`Nested source-map ${phase}: ${String(cause)}`, {
cause,
});
failure.qualificationPhase = phase;
failure.qualificationReceipt = {
compiler: site.compiler,
browser: browserVersion?.product ?? null,
generated_sha256: sha(site.generated),
leaf_map_sha256: sha(site.leafMap),
source_map_sha256: sha(site.map),
original_sha256: sha(site.original),
expected_first_mapping: expected?.[0] ?? null,
observed_first_mapping: observed?.[0] ?? null,
};
primaryError = failure;
throw failure;
} finally {
await closeRuntimeFixtureResources(
[() => connection?.close(), () => fixture?.close(), () => site.close()],
primaryError,
);
}
}
function compilerMappingsAtPhysicalPositions(site, mapUrl) {
const expected = [];
const prefixLength = site.generated.indexOf(site.leafCode);
assert.ok(prefixLength >= 0);
eachMapping(new TraceMap(site.leafMap, mapUrl), (mapping) => {
if (
mapping.source === null ||
mapping.originalLine === null ||
mapping.originalColumn === null
)
return;
const leafLines = site.leafCode.split("\n");
const before = leafLines.slice(0, mapping.generatedLine - 1).join("\n");
const leafOffset =
before.length +
(mapping.generatedLine > 1 ? 1 : 0) +
mapping.generatedColumn;
const physical = site.generated
.slice(0, prefixLength + leafOffset)
.split("\n");
expected.push({
generated_line: physical.length,
generated_column: physical.at(-1).length,
source: mapping.source,
original_line: mapping.originalLine,
original_column: mapping.originalColumn,
name: mapping.name ?? null,
});
});
return expected;
}
async function ready(connection) {
for (let attempt = 0; attempt < 100; attempt += 1) {
const result = await connection.send("Runtime.evaluate", {
expression: 'document.body.dataset.ready === "source-map proof"',
returnByValue: true,
});
if (result.result.value === true) return;
await new Promise((resolve) => setTimeout(resolve, 20));
}
throw new Error("Owned compiler fixture did not execute in actual Chrome");
}
const sha = (text) => createHash("sha256").update(text).digest("hex");
+2 -8
View File
@@ -112,10 +112,7 @@ export async function verifyBrowserNetworkEvidence(
{ timeout: 60_000 },
);
assert.notEqual(result.isError, true, JSON.stringify(result));
const evidence = parseEvidence({
...result.structuredContent?.evidence,
normalized_result: result.structuredContent?.result,
});
const evidence = parseEvidence(result.structuredContent);
mcpProof = assertBrowserNetworkEvidence(
browserScenarioCaptureSchema.parse(evidence.normalized_result),
site.origin,
@@ -130,10 +127,7 @@ export async function verifyBrowserNetworkEvidence(
{ timeout: 60_000 },
);
assert.notEqual(connected.isError, true, JSON.stringify(connected));
const connectedEvidence = parseEvidence({
...connected.structuredContent?.evidence,
normalized_result: connected.structuredContent?.result,
});
const connectedEvidence = parseEvidence(connected.structuredContent);
assert.deepEqual(
assertBrowserNetworkEvidence(
browserScenarioCaptureSchema.parse(
+5 -8
View File
@@ -8,7 +8,7 @@ import { chromium } from "playwright-core";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { PlaywrightBrowserScenarioProvider } from "../../dist/browser/PlaywrightBrowserScenarioProvider.js";
import { createBrowserScenarioProvider } from "../../dist/composition/browserScenario.js";
import { PlaywrightScenarioEvents } from "../../dist/browser/PlaywrightScenarioEvents.js";
import { BrowserScenarioSecrets } from "../../dist/browser/BrowserScenarioSecrets.js";
import { browserScenarioSchema } from "../../dist/domain/browserScenario.js";
@@ -53,9 +53,9 @@ const scenarioFor = (executable, origin, events) =>
});
const capture = async (scenario) => {
const result = await new PlaywrightBrowserScenarioProvider().captureScenario(
scenario,
);
const result = await createBrowserScenarioProvider(
process.env,
).captureScenario(scenario);
if (!result.ok) throw result.error;
assert.ok(
result.value.steps.every((step) => step.status === "completed"),
@@ -163,10 +163,7 @@ const verifyTransports = async (executable, origin) => {
{ timeout: 60_000 },
);
assert.notEqual(captured.isError, true, JSON.stringify(captured));
const evidence = parseEvidence({
...captured.structuredContent?.evidence,
normalized_result: captured.structuredContent?.result,
});
const evidence = parseEvidence(captured.structuredContent);
assertNetwork(
browserScenarioCaptureSchema.parse(evidence.normalized_result),
origin,
+2 -2
View File
@@ -126,7 +126,7 @@ export async function verifyBrowserRuntime(
mcpTextValue(listenerResponse),
);
const mcpListenerProof = assertListeners(
parseEvidence(JSON.parse(mcpTextValue(listenerResponse)).evidence),
parseEvidence(JSON.parse(mcpTextValue(listenerResponse))),
site,
);
const mcpProof = await executionMcp(
@@ -231,7 +231,7 @@ async function executionMcp(
await actionPromise;
assert.notEqual(response.isError, true, mcpTextValue(response));
return assertExecution(
parseEvidence(JSON.parse(mcpTextValue(response)).evidence),
parseEvidence(JSON.parse(mcpTextValue(response))),
site,
requireBlock,
);
+1 -4
View File
@@ -94,10 +94,7 @@ export async function verifyLargeScreenshotE2e(endpoint, origin) {
{ timeout: 60000 },
);
assert.notEqual(captured.isError, true, JSON.stringify(captured));
const mcpEvidence = parseEvidence({
...captured.structuredContent?.evidence,
normalized_result: captured.structuredContent?.result,
});
const mcpEvidence = parseEvidence(captured.structuredContent);
const mcpScreenshot = webScreenshotSchema.parse(
mcpEvidence.normalized_result,
);
+1 -1
View File
@@ -69,7 +69,7 @@ export async function verifyBrowserScriptExport(
const call = async (name, args) => {
const result = await client.callTool({ name, arguments: args });
assert.notEqual(result.isError, true, mcpTextValue(result));
return parseEvidence(JSON.parse(mcpTextValue(result)).evidence);
return parseEvidence(JSON.parse(mcpTextValue(result)));
};
const mcpCapture = await call("capture_browser_scenario", scenario);
const mcpInput = await save("mcp", mcpCapture);
+1 -3
View File
@@ -184,9 +184,7 @@ async function assertPublicPair(cli, client, oracle) {
arguments: input,
});
assert.notEqual(response.isError, true, mcpTextValue(response));
const mcpEvidence = parseEvidence(
JSON.parse(mcpTextValue(response)).evidence,
);
const mcpEvidence = parseEvidence(JSON.parse(mcpTextValue(response)));
assert.deepEqual(assertPoint(mcpEvidence, oracle), expected);
assert.equal(evidence.parameters.source_map.url, input.source_map.url);
return expected;
+49 -1
View File
@@ -150,7 +150,7 @@ export function assertBundleAnalysis(result) {
)
throw new Error("Real Chrome static bundle findings were missing");
if (
result.observations.source_maps.status !== "included" ||
result.observations.source_maps.status !== "partial" ||
!result.observations.source_maps.items.some(
({ status, original_sources, original_module_edges, mappings }) =>
status === "included" &&
@@ -166,6 +166,41 @@ export function assertBundleAnalysis(result) {
throw new Error(
`Real Chrome approved source-map reconstruction was missing: ${JSON.stringify(sourceMapSummary(result.observations.source_maps))}`,
);
if (
!result.observations.source_maps.items.some(
({ declared_url, status, artifact, mappings }) =>
declared_url.endsWith("/bad.js.map") &&
status === "invalid" &&
artifact === null &&
mappings.length === 0,
)
)
throw new Error(
`Real Chrome malformed-map isolation was missing: ${JSON.stringify(sourceMapSummary(result.observations.source_maps))}`,
);
if (
!result.observations.source_maps.items.some(
({ declared_url, status, limitation, mappings }) =>
declared_url.endsWith("/expanded.js.map") &&
status === "fetch_failed" &&
limitation.includes("Expanded source-map evidence") &&
mappings.length === 0,
)
)
throw new Error(
`Real Chrome expanded-map omission was missing: ${JSON.stringify(sourceMapSummary(result.observations.source_maps))}`,
);
if (
!result.observations.source_maps.items.some(
({ declared_url, status, mappings }) =>
declared_url.endsWith("/overflow.js.map") &&
status === "invalid" &&
mappings.length === 0,
)
)
throw new Error(
`Real Chrome overflowing-map isolation was missing: ${JSON.stringify(sourceMapSummary(result.observations.source_maps))}`,
);
}
/** Assert explicitly selected console text and JSON/WebSocket shape capture. */
@@ -182,6 +217,19 @@ export function assertSensitiveShapes(result) {
);
if (request === undefined)
throw new Error("Real Chrome JSON request/response shapes were missing");
if (
!result.network.requests.some(
(item) =>
item.url.endsWith("/slow-json") &&
item.body_shapes.status === "partial" &&
item.body_shapes.request !== null &&
item.body_shapes.response === null,
) ||
!result.completeness.unavailable_sections.includes("json_body_shapes")
)
throw new Error(
"Real Chrome unfinished JSON bodies were not reported as unavailable",
);
const consoleText = result.console.events.flatMap(
(event) => event.text_capture.values,
);
+9 -6
View File
@@ -75,6 +75,7 @@ export const toolFamilyCatalog = (sources) => {
surface: "application-workflow",
contracts: [
...sources.applicationContracts.APPLICATION_TOOL_CONTRACTS,
...sources.analysisViewContracts.ANALYSIS_VIEW_TOOL_CONTRACTS,
...sources.webScriptContracts.WEB_SCRIPT_TOOL_CONTRACTS,
...sources.javascriptRecoveryContracts
.JAVASCRIPT_RECOVERY_TOOL_CONTRACTS,
@@ -199,7 +200,7 @@ export const providerCatalog = (sources) => {
sources.webNetworkCaptureContracts.WEB_NETWORK_CAPTURE_TOOL_CONTRACTS,
},
{
identity: sources.browserProvider.CDP_BROWSER_PROVIDER_IDENTITY,
identity: sources.browserIdentities.CDP_BROWSER_PROVIDER_IDENTITY,
contracts: [
...sources.browserContracts.BROWSER_TOOL_CONTRACTS,
...sources.webRuntimeContracts.WEB_RUNTIME_TOOL_CONTRACTS,
@@ -207,19 +208,17 @@ export const providerCatalog = (sources) => {
},
{
identity:
sources.browserScenarioProvider
.PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY,
sources.browserIdentities.PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY,
contracts:
sources.browserScenarioContracts.BROWSER_SCENARIO_TOOL_CONTRACTS,
},
{
identity: sources.electronProvider.CDP_ELECTRON_PROVIDER_IDENTITY,
identity: sources.browserIdentities.CDP_ELECTRON_PROVIDER_IDENTITY,
contracts: observationContracts,
},
{
identity:
sources.electronActiveProvider
.PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY,
sources.browserIdentities.PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY,
contracts: activeContracts,
},
{
@@ -246,6 +245,10 @@ export const providerCatalog = (sources) => {
name !== "project_apple_application_graph",
),
},
{
identity: sources.artifactProviders.ANALYSIS_VIEW_PROVIDER,
contracts: sources.analysisViewContracts.ANALYSIS_VIEW_TOOL_CONTRACTS,
},
{
identity: sources.artifactProviders.ANDROID_APPLICATION_PROVIDER,
contracts: sources.applicationContracts.APPLICATION_TOOL_CONTRACTS.filter(
+2 -4
View File
@@ -64,6 +64,7 @@ const SOURCE_PATHS = {
javascriptRuntimeObservationContracts:
"dist/contracts/javascript/javascriptRuntimeObservationToolContracts.js",
applicationContracts: "dist/contracts/applicationToolContracts.js",
analysisViewContracts: "dist/contracts/analysisViewToolContracts.js",
webScriptContracts: "dist/contracts/webScriptToolContracts.js",
javascriptRecoveryContracts:
"dist/contracts/javascript/javascriptRecoveryToolContracts.js",
@@ -74,10 +75,7 @@ const SOURCE_PATHS = {
idaProvider: "dist/ida/IdaProviderCapabilities.js",
nativeProvider: "dist/native/NativeMacOSProviderMetadata.js",
artifactProviders: "dist/application/InvestigationProviders.js",
browserProvider: "dist/browser/CdpBrowserProvider.js",
browserScenarioProvider: "dist/browser/PlaywrightBrowserScenarioProvider.js",
electronProvider: "dist/browser/CdpElectronProvider.js",
electronActiveProvider: "dist/browser/PlaywrightElectronActiveProvider.js",
browserIdentities: "dist/browser/providerIdentities.js",
v8InspectorProvider: "dist/inspector/V8InspectorProvider.js",
evidence: "dist/domain/evidence.js",
evidenceBundle: "dist/domain/evidenceBundle.js",
+2 -1
View File
@@ -11,11 +11,12 @@ export function parseDevelopmentTestRequest(
arguments_: readonly string[],
): DevelopmentTestRequest;
/** Plan source feedback or a strict explicit run with its build prerequisite. */
/** Plan source feedback or explicit tests with runtime/artifact prerequisites. */
export function developmentTestPlan(
request: DevelopmentTestRequest,
baseCommit?: string,
): {
readonly needsBuild: boolean;
readonly artifactTasks: readonly string[];
readonly vitestArguments: readonly string[];
};
+18 -1
View File
@@ -49,7 +49,20 @@ export const parseDevelopmentTestRequest = (mode, arguments_) => {
return { mode, paths: [...new Set(paths)], base, dryRun };
};
/** Plan source feedback or a strict explicit run with its build prerequisite. */
// Test-only outputs are declared beside the test planner rather than becoming
// runtime or typecheck prerequisites. Add a consumer here when it reads one.
const artifactConsumers = {
"artifacts:mcp-catalog": [
"tests/boundary/mcp/contractPresentation.test.ts",
"tests/boundary/mcp/toolSchemaValidity.test.ts",
],
"artifacts:product-catalog": ["tests/boundary/cli/productCatalog.test.ts"],
"artifacts:managed-evidence": [
"tests/boundary/filesystem/setupSkill.test.ts",
],
};
/** Plan source feedback or explicit tests with their runtime/artifact prerequisites. */
export const developmentTestPlan = (request, baseCommit) => {
const explicit = request.paths.length > 0;
if (
@@ -60,6 +73,10 @@ export const developmentTestPlan = (request, baseCommit) => {
const projects = request.mode === "focused" ? [] : sourceProjects;
return {
needsBuild: request.paths.some((path) => !sourceTest(path)),
artifactTasks: Object.entries(artifactConsumers).flatMap(
([task, consumers]) =>
request.paths.some((path) => consumers.includes(path)) ? [task] : [],
),
vitestArguments: [
"run",
...projects.flatMap((project) => ["--project", project]),
+29 -37
View File
@@ -1,28 +1,15 @@
import { readFile, readdir, stat } from "node:fs/promises";
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
const README_PATHS = [
"README.md",
"README_zh.md",
"README_ja.md",
"README_ko.md",
"README_ar.md",
];
const tableCounts = (content, path, expectedCounts) => {
const lines = content.split(/\r?\n/u);
for (const [index, line] of lines.entries()) {
if (!/^\|\s*-/u.test(line)) continue;
const counts = [];
for (const row of lines.slice(index + 1)) {
if (!row.trim().startsWith("|")) break;
const count = Number(row.split("|")[2]?.trim());
if (!Number.isInteger(count)) break;
counts.push(count);
}
if (counts.length === expectedCounts.length) return counts;
}
throw new Error(`Missing tool-family inventory table in ${path}`);
};
const readmeLinkTargets = (content) =>
[...content.matchAll(/\]\(([^)\s]+)\)/gu)]
.map((match) => match[1] ?? "")
.filter(
(target) =>
!target.startsWith("#") &&
!/^README(?:_[A-Za-z0-9-]+)?\.md$/u.test(target),
)
.sort();
const requireText = (issues, path, content, expected) => {
if (!content.includes(expected)) issues.push(`${path}: missing ${expected}`);
@@ -71,32 +58,37 @@ export const documentationFactIssues = async (root, catalog) => {
const issues = await skillReferenceIssues(
join(root, "skills/reverse-engineer-anything"),
);
const expectedCounts = catalog.tools.families.map(({ count }) => count);
for (const path of README_PATHS) {
const readmePaths = (await readdir(root))
.filter((path) => /^README(?:_[A-Za-z0-9-]+)?\.md$/u.test(path))
.sort();
const readmes = new Map();
for (const path of readmePaths) {
const content = await readFile(join(root, path), "utf8");
readmes.set(path, content);
requireText(issues, path, content, "MCP-tool_catalog");
if (path === "README.md") {
requireText(issues, path, content, "(docs/installation.md");
requireText(
issues,
path,
content,
"(docs/installation.md#supported-agents)",
);
requireText(issues, path, content, "(docs/installation.md#mcp-registry)");
requireText(
issues,
path,
content,
"(docs/mcp-contracts.md#generated-catalog)",
);
continue;
}
for (const client of catalog.setup_clients)
requireText(issues, path, content, client.display_name);
try {
const actualCounts = tableCounts(content, path, expectedCounts);
if (JSON.stringify(actualCounts) !== JSON.stringify(expectedCounts))
issues.push(
`${path}: tool family counts ${JSON.stringify(actualCounts)} do not match ${JSON.stringify(expectedCounts)}`,
);
} catch (cause) {
issues.push(cause instanceof Error ? cause.message : String(cause));
}
}
const canonicalLinks = readmeLinkTargets(readmes.get("README.md") ?? "");
for (const path of readmePaths.filter((path) => path !== "README.md"))
if (
JSON.stringify(readmeLinkTargets(readmes.get(path) ?? "")) !==
JSON.stringify(canonicalLinks)
)
issues.push(`${path}: documentation links differ from README.md`);
const installationPath = "docs/installation.md";
const installation = await readFile(join(root, installationPath), "utf8");
@@ -0,0 +1,188 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { randomUUID } from "node:crypto";
import { writeFile, rm } from "node:fs/promises";
import { join } from "node:path";
import { promisify } from "node:util";
import { parseBinaryTarget } from "../../dist/application/BinaryTargetResolver.js";
import { createAnalysisProfile } from "../../dist/domain/analysisProfile.js";
import {
createAnalysisSnapshotEntry,
parseAnalysisSnapshot,
serializeAnalysisSnapshot,
snapshotBinding,
snapshotTarget,
} from "../../dist/domain/analysisSnapshot.js";
import { createEvidence } from "../../dist/domain/evidence.js";
import { createEvidenceBundle } from "../../dist/domain/evidenceBundle.js";
import {
requireMcpLifecycleResult,
requireMcpEvidenceResult,
} from "./mcp-verifier-results.mjs";
/** Reject a valid older snapshot whose instruction-only collector omitted a real edge. */
export async function verifyLegacyGhidraReferenceSnapshot(
client,
{ target, procedure, omittedEdge, entrypoint, env },
) {
const bundle = requireMcpLifecycleResult(
await client.callTool({
name: "get_evidence_bundle",
arguments: {},
}),
"get_evidence_bundle",
);
const observed = bundle.records.find(
(record) =>
record.operation === "analyze_function" &&
record.parameters.procedure === procedure,
);
assert.ok(observed);
const { function_references: coverage, ...legacyParameters } =
observed.analysis_profile.parameters;
assert.equal(
typeof coverage,
"string",
"Reference semantics must participate in snapshot identity",
);
const legacyProfile = createAnalysisProfile(
observed.provider,
legacyParameters,
);
assert.notEqual(legacyProfile.digest, observed.analysis_profile.digest);
const parsed = await parseBinaryTarget(
target.path,
process.cwd(),
process.arch,
);
if (!parsed.ok) throw parsed.error;
const nativeTarget = parsed.value;
const result = structuredClone(observed.normalized_result);
// Reconstruct the observed pre-fix omission; all other data
// comes from the real provider and the file has a fully valid Evidence binding.
result.incoming_references = result.incoming_references.filter(
(edge) =>
edge.source_address !== omittedEdge.source_address ||
edge.target_address !== omittedEdge.target_address,
);
assert.equal(
result.incoming_references.length,
observed.normalized_result.incoming_references.length - 1,
);
const evidence = createEvidence(nativeTarget, observed.provider, {
operation: observed.operation,
parameters: observed.parameters,
result,
rawResult: result,
analysisProfile: legacyProfile,
limitations: observed.limitations,
locations: observed.locations,
});
const binding = snapshotBinding(legacyProfile);
const identity = snapshotTarget(nativeTarget);
const snapshot = parseAnalysisSnapshot({
target: identity,
binding,
entries: [
createAnalysisSnapshotEntry({
target: identity,
binding,
operation: observed.operation,
parameters: observed.parameters,
execution: {
result,
rawResult: result,
provider: observed.provider,
analysisProfile: legacyProfile,
limitations: observed.limitations,
locations: observed.locations,
subject: nativeTarget,
},
}),
],
workflow_entries: [],
evidence_bundle: createEvidenceBundle([evidence]),
});
assert.equal(
snapshot.entries.length,
1,
"The legacy query must remain valid and replayable under its own profile",
);
const path = join(env.TMPDIR, `legacy-reference-${randomUUID()}.json`);
await writeFile(path, serializeAnalysisSnapshot(snapshot), { flag: "wx" });
try {
const session = requireMcpLifecycleResult(
await client.callTool({ name: "binary_session", arguments: {} }),
"binary_session",
);
const reply = await client.callTool({
name: "open_binary",
arguments: {
path: target.path,
provider_id: "ghidra",
snapshot_path: path,
},
});
assert.equal(reply.isError, true);
const mcpRejection = reply.structuredContent.error;
assert.equal(mcpRejection.code, "evidence_integrity_mismatch");
assert.match(mcpRejection.details.reason, /profile_mismatch/u);
assert.match(
mcpRejection.remediation.action,
/without this snapshot.*fresh snapshot/u,
);
assert.deepEqual(
requireMcpLifecycleResult(
await client.callTool({ name: "binary_session", arguments: {} }),
"binary_session",
),
session,
"A rejected snapshot must preserve the selected session",
);
const retained = requireMcpEvidenceResult(
await client.callTool({
name: "analyze_function",
arguments: { procedure },
}),
"analyze_function",
);
assert.ok(
retained.incoming_references.some(
(edge) =>
edge.source_address === omittedEdge.source_address &&
edge.target_address === omittedEdge.target_address,
),
"A rejected legacy snapshot must not replace complete live analysis",
);
await assert.rejects(
promisify(execFile)(
process.execPath,
[
entrypoint,
"function",
target.path,
procedure,
"--provider",
"ghidra",
"--snapshot",
path,
"--json",
],
{ env, timeout: 240000, maxBuffer: 72 * 1024 * 1024 },
),
(error) => {
assert.equal(error.code, 1);
const rejection = JSON.parse(error.stdout);
assert.equal(rejection.code, "evidence_integrity_mismatch");
assert.equal(rejection.details.reason, mcpRejection.details.reason);
assert.equal(
rejection.remediation.action,
mcpRejection.remediation.action,
);
return true;
},
);
} finally {
await rm(path);
}
}
+49 -14
View File
@@ -1,3 +1,5 @@
import { toolContract } from "../../dist/contracts/toolContracts.js";
/** Read the textual JSON projection from one MCP tool call. */
export const mcpTextValue = (result) => {
const text = result.content?.find((item) => item.type === "text")?.text;
@@ -7,25 +9,58 @@ export const mcpTextValue = (result) => {
const jsonValue = (result) => JSON.parse(mcpTextValue(result));
/** Return the compact result projection from one successful MCP tool call. */
export const requireMcpResult = (result, operation) => {
if (result.isError === true) {
const requireOutput = (result, operation, contract) => {
if (result.isError === true)
throw new Error(`${operation} failed: ${mcpTextValue(result)}`);
}
const value = jsonValue(result);
if (value === null || typeof value !== "object" || !("result" in value)) {
throw new Error(`${operation} omitted its result projection`);
}
return value.result;
if (contract.name !== operation)
throw new Error(`${operation} received the ${contract.name} contract`);
return contract.outputSchema.parse(jsonValue(result));
};
const operationResultKey = (contract) => {
const shape = contract.outputSchema.shape;
if (shape === undefined)
throw new Error(`${contract.name} has an unsupported MCP output schema`);
const evidence = Object.hasOwn(shape, "normalized_result");
const lifecycle = Object.hasOwn(shape, "result");
if (evidence === lifecycle)
throw new Error(`${contract.name} has an unsupported MCP output schema`);
return evidence ? "normalized_result" : "result";
};
/** Validate an Evidence-producing tool output and return its normalized result. */
export const requireMcpEvidenceResult = (result, operation) => {
const contract = toolContract(operation);
if (operationResultKey(contract) !== "normalized_result")
throw new Error(`${operation} requires the lifecycle result extractor`);
return requireOutput(result, operation, contract).normalized_result;
};
/** Validate a lifecycle tool output and return its operation result. */
export const requireMcpLifecycleResult = (result, operation) => {
const contract = toolContract(operation);
if (operationResultKey(contract) !== "result")
throw new Error(`${operation} requires the Evidence result extractor`);
return requireOutput(result, operation, contract).result;
};
/** Project a mixed verifier call using its exact named canonical output schema. */
export const requireMcpOperationResult = (
result,
operation,
contract = toolContract(operation),
) => {
const key = operationResultKey(contract);
return requireOutput(result, operation, contract)[key];
};
/** Verify provider identity directly from the tool result. */
export const requireEvidenceProvider = (result, operation, providerId) => {
const evidence = jsonValue(result);
if (
evidence?.evidence?.provider?.id !== providerId ||
evidence?.evidence?.analysis_profile?.provider?.id !== providerId ||
typeof evidence.evidence.analysis_profile.provider.version !== "string"
evidence?.provider?.id !== providerId ||
evidence?.analysis_profile?.provider?.id !== providerId ||
typeof evidence.analysis_profile.provider.version !== "string"
) {
throw new Error(`${operation} omitted its concrete provider provenance`);
}
@@ -38,10 +73,10 @@ export const requireWorkflowEvidenceProvider = (
expected,
) => {
const evidence = jsonValue(result);
const profile = evidence?.evidence?.analysis_profile;
const profile = evidence?.analysis_profile;
const upstream = profile?.parameters?.upstream_analysis_profile;
if (
evidence?.evidence?.provider?.id !== expected.workflowProviderId ||
evidence?.provider?.id !== expected.workflowProviderId ||
profile?.provider?.id !== expected.workflowProviderId ||
upstream?.provider?.id !== expected.upstreamProviderId ||
typeof upstream.provider.version !== "string"
-13
View File
@@ -14,14 +14,6 @@ const MIGRATED_PROVIDER_ROOTS = new Set([
"inspector",
"javascript",
]);
const PROVIDER_ROOTS = new Set([
...MIGRATED_PROVIDER_ROOTS,
"artifacts",
"browser",
"dotnet",
"native",
]);
const repositoryPath = (root, path) =>
relative(root, path).replaceAll("\\", "/");
@@ -88,11 +80,6 @@ const failedBoundary = (file, target) => {
/^src\/(?:cli|main)\./u.test(target))
)
return "artifact-acquisition";
if (
PROVIDER_ROOTS.has(layer) &&
/^src\/generatedMcpToolCatalog\.(?:js|ts)$/u.test(target)
)
return "provider-generated-catalog";
if (PURE_LAYERS.has(layer)) {
if (
(/^src\/[^/]+\//u.test(target) && !PURE_LAYERS.has(targetLayer)) ||
+59 -20
View File
@@ -1,5 +1,8 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { mkdir, mkdtemp, readdir, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";
import { Client } from "@modelcontextprotocol/client";
@@ -8,6 +11,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { parseEvidence } from "../../dist/domain/evidence.js";
import { analysisProfileSchema } from "../../dist/domain/analysisProfile.js";
import { nativeValueTraceSchema } from "../../dist/domain/native/nativeValueTrace.js";
import { verifyGhidraBoundaries } from "./real-ghidra-boundaries.mjs";
const defaultEntrypoint = fileURLToPath(new URL("../rea.mjs", import.meta.url));
const environment = () => ({
@@ -72,6 +76,33 @@ export async function verifyNativeValueE2e(
procedure,
globalAddress,
{ entrypoint = defaultEntrypoint } = {},
) {
const workspace = await mkdtemp(join(tmpdir(), "rea-native-values-e2e-"));
const runtime = join(
workspace,
`runtime${process.platform === "win32" ? "" : " with spaces"}-${"x".repeat(100)}`,
);
await mkdir(runtime);
const env = { ...environment(), TMPDIR: runtime };
try {
return await verifyNativeValueAdapters(
target,
procedure,
globalAddress,
entrypoint,
env,
);
} finally {
await rm(workspace, { recursive: true, force: true });
}
}
async function verifyNativeValueAdapters(
target,
procedure,
globalAddress,
entrypoint,
env,
) {
const { stdout } = await promisify(execFile)(
process.execPath,
@@ -90,7 +121,7 @@ export async function verifyNativeValueE2e(
"5000",
"--json",
],
{ env: environment(), timeout: 240000, maxBuffer: 72 * 1024 * 1024 },
{ env, timeout: 240000, maxBuffer: 72 * 1024 * 1024 },
);
const evidence = parseEvidence(JSON.parse(stdout));
assert.equal(evidence.operation, "trace_native_values");
@@ -114,16 +145,17 @@ export async function verifyNativeValueE2e(
const transport = new StdioClientTransport({
command: process.execPath,
args: [entrypoint, "mcp"],
env: environment(),
env,
stderr: "pipe",
});
const client = new Client({ name: "native-values-real-e2e", version: "1" });
let boundaries;
try {
await client.connect(transport);
const opened = await client.callTool(
{
name: "open_binary",
arguments: { path: target.path, provider: "ghidra" },
arguments: { path: target.path, provider_id: "ghidra" },
},
{ timeout: 180000 },
);
@@ -147,12 +179,12 @@ export async function verifyNativeValueE2e(
{ timeout: 180000 },
);
assert.notEqual(result.isError, true, JSON.stringify(result));
const mcpEvidence = parseEvidence(result.structuredContent?.evidence);
const mcpEvidence = parseEvidence(result.structuredContent);
assert.equal(mcpEvidence.operation, "trace_native_values");
assert.equal(mcpEvidence.provider.id, "rea-workflow");
assert.deepEqual(mcpEvidence.analysis_profile, workflowProfile);
const mcpGraph = verifyGraph(
result.structuredContent?.result,
result.structuredContent?.normalized_result,
target,
procedure,
globalAddress,
@@ -164,40 +196,33 @@ export async function verifyNativeValueE2e(
{ timeout: 180000 },
);
assert.notEqual(analyzed.isError, true, JSON.stringify(analyzed));
const functionEvidence = parseEvidence(
analyzed.structuredContent?.evidence,
);
const functionEvidence = parseEvidence(analyzed.structuredContent);
assert.equal(functionEvidence.operation, "analyze_function");
assert.equal(functionEvidence.provider.id, "rea-workflow");
assert.equal(functionEvidence.provider.id, "ghidra");
const functionProfile = analysisProfileSchema.parse(
functionEvidence.analysis_profile,
);
assert.deepEqual(
functionProfile.parameters.upstream_analysis_profile,
upstreamProfile,
);
assert.deepEqual(functionProfile, upstreamProfile);
assert.deepEqual(
functionEvidence.normalized_result,
analyzed.structuredContent?.result,
analyzed.structuredContent?.normalized_result,
);
const comparison = await client.callTool(
{
name: "compare_functions",
arguments: {
left: analyzed.structuredContent.evidence,
right: analyzed.structuredContent.evidence,
left: analyzed.structuredContent,
right: analyzed.structuredContent,
},
},
{ timeout: 180000 },
);
assert.notEqual(comparison.isError, true, JSON.stringify(comparison));
const comparisonEvidence = parseEvidence(
comparison.structuredContent?.evidence,
);
const comparisonEvidence = parseEvidence(comparison.structuredContent);
assert.equal(comparisonEvidence.normalized_result.status, "unchanged");
assert.deepEqual(
comparisonEvidence.normalized_result,
comparison.structuredContent?.result,
comparison.structuredContent?.normalized_result,
);
assert.ok(
comparisonEvidence.evidence_links.includes(functionEvidence.evidence_id),
@@ -219,6 +244,12 @@ export async function verifyNativeValueE2e(
evidence,
);
}
boundaries = await verifyGhidraBoundaries(client, {
target,
procedure,
entrypoint,
env,
});
const closed = await client.callTool({
name: "close_binary",
arguments: {},
@@ -229,6 +260,13 @@ export async function verifyNativeValueE2e(
arguments: {},
});
assert.equal(status.structuredContent?.result?.open, false);
assert.deepEqual(
(await readdir(env.TMPDIR)).filter((name) =>
name.startsWith("rea-ghidra-"),
),
[],
"Ghidra left runtime files after close",
);
} finally {
await client.close();
await transport.close();
@@ -239,6 +277,7 @@ export async function verifyNativeValueE2e(
stdio_mcp: true,
inline_evidence: true,
direct_function_comparison: true,
boundaries,
nodes: cliGraph.total_nodes,
edges: cliGraph.total_edges,
decompilations: cliGraph.decompilations,
@@ -0,0 +1,85 @@
import assert from "node:assert/strict";
/** Exercise segmented-address truncation through real CLI/MCP reads and edits. */
export async function verifyGhidraAddressBoundaries(client, entry, cli) {
const query = async (name, args) => {
const reply = await client.callTool(
{ name, arguments: args },
{ timeout: 240000 },
);
assert.notEqual(reply.isError, true, JSON.stringify(reply));
return reply.structuredContent.normalized_result;
};
const before = await query("analyze_function", { procedure: entry });
// The segmented parser used to discard the high 32 bits and alias this entry.
const overflow = (0x100000000n + BigInt(entry)).toString(16);
const representations = [
`0x${overflow}`,
`0X${overflow}`,
overflow,
`ram:0x${overflow}`,
`%72am:0x${overflow}`,
];
for (const address of representations) {
for (const [name, args] of [
["read_bytes", { address, length: 1 }],
["inspect_native_instruction", { address }],
["resolve_containing_procedure", { address }],
["procedure_info", { procedure: address }],
[
"annotate_native_function",
{
procedure: address,
name: "MUST_NOT_ALIAS",
comment: "MUST NOT WRITE",
},
],
]) {
const reply = await client.callTool(
{ name, arguments: args },
{ timeout: 240000 },
);
assert.equal(reply.isError, true, `${name} accepted ${address}`);
const error = reply.structuredContent?.error;
assert.equal(error?.code, "invalid_request", JSON.stringify(reply));
assert.equal(error.details.operation, name);
const issues = JSON.stringify(error.details.issues);
assert.match(issues, /cannot be represented without truncation/u);
assert.ok(issues.includes(address), issues);
assert.equal(reply.structuredContent.evidence, undefined);
}
}
assert.deepEqual(
await query("analyze_function", { procedure: entry }),
before,
"Rejected oversized annotations changed the aliased function",
);
// Leading zeros and an encoded explicit space preserve the requested offset.
const padded = `%72am:0x0000${BigInt(entry).toString(16)}`;
assert.deepEqual(
await query("read_bytes", { address: padded, length: 1 }),
await query("read_bytes", { address: entry, length: 1 }),
);
for (const [command, flags, operation] of [
["read-bytes", ["--length", "1"], "read_bytes"],
[
"annotate-native-function",
["--name", "MUST_NOT_ALIAS"],
"annotate_native_function",
],
]) {
let failure;
try {
await cli(command, representations[0], flags);
} catch (error) {
assert.equal(error.code, 1);
failure = JSON.parse(error.stdout);
}
assert.equal(failure?.code, "invalid_request");
assert.equal(failure.details.operation, operation);
assert.match(
JSON.stringify(failure.details.issues),
/cannot be represented without truncation/u,
);
}
}
@@ -0,0 +1,41 @@
import assert from "node:assert/strict";
/** Verify batch selector/entry separation against real source-owned Ghidra functions. */
export async function verifyGhidraBatchIdentity(call, names) {
const leaf = names.find((item) =>
item.value.endsWith("rea_ghidra_inventory_leaf"),
);
assert.ok(leaf, "Source fixture lacks a leaf procedure");
const listing = await call("read_function_instructions", {
procedure: leaf.address,
});
const interior = listing.instructions[1]?.split(": ")[0];
assert.ok(interior, "Source fixture lacks an interior instruction");
const name = await call("address_name", { address: leaf.address });
const selectors = [leaf.address, interior, leaf.value, "0x0"];
const result = await call("batch_decompile", { addresses: selectors });
assert.equal(result.total, 4);
assert.equal(result.succeeded, 3);
assert.equal(result.failed, 1);
for (let index = 0; index < 3; index++) {
const item = result.items[index];
assert.equal(item.address, selectors[index]);
assert.equal(item.status, "ok");
assert.deepEqual(item.procedure, {
status: "resolved",
address: leaf.address,
name,
});
assert.equal(item.pseudocode, result.items[0].pseudocode);
}
assert.equal(result.items[3].status, "error");
assert.equal(result.items[3].procedure.status, "unknown");
assert.ok(result.items[3].procedure.error.code);
return {
entry: leaf.address,
interior,
selectors_preserved: true,
identical_resolved_code: true,
partial_failure_retained: true,
};
}
+912
View File
@@ -0,0 +1,912 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { createHash, randomUUID } from "node:crypto";
import { access, readFile, readdir, stat } from "node:fs/promises";
import { dirname, join } from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import { promisify } from "node:util";
import Ajv from "ajv/dist/2020.js";
import addFormats from "ajv-formats";
import { analysisErrorProjectionSchema } from "../../dist/contracts/errorSchemas.js";
import {
mcpTextValue,
requireMcpOperationResult,
} from "./mcp-verifier-results.mjs";
import { verifyLegacyGhidraReferenceSnapshot } from "./ghidra-reference-snapshot-e2e.mjs";
import { verifyGhidraSnapshotLifecycle } from "./real-ghidra-snapshot-lifecycle.mjs";
import { verifyGhidraTargetAdmission } from "./real-ghidra-target-admission.mjs";
import { verifyGhidraLargeResults } from "./real-ghidra-large-results.mjs";
import { verifyGhidraEntryAliases } from "./real-ghidra-entry-aliases.mjs";
import { verifyGhidraNamespaceAnnotations } from "./real-ghidra-namespace-annotations.mjs";
import { verifyGhidraBatchIdentity } from "./real-ghidra-batch-identity.mjs";
import { verifyGhidraInstructionFlow } from "./real-ghidra-instruction-flow.mjs";
/** Probe real Ghidra location, annotation and error contracts through public adapters. */
export async function verifyGhidraBoundaries(
client,
{ target, procedure, entrypoint, env },
) {
const options = { timeout: 180000 };
const catalog = await client.listTools();
const ajv = new Ajv({ strict: false });
addFormats(ajv);
const validators = new Map();
for (const tool of catalog.tools) {
for (const key of ["inputSchema", "outputSchema"])
assert.ok(
ajv.validateSchema(tool[key]),
`${tool.name} ${key}: ${JSON.stringify(ajv.errors)}`,
);
validators.set(tool.name, ajv.compile(tool.outputSchema));
}
let successfulCalls = 0;
let rejectedCalls = 0;
const call = async (name, args = {}) => {
const reply = await client.callTool({ name, arguments: args }, options);
const result = requireMcpOperationResult(reply, name);
const validate = validators.get(name);
assert.ok(validate, `${name} missing from catalog`);
assert.ok(
validate(reply.structuredContent),
`${name}: ${JSON.stringify(validate.errors)}`,
);
assert.deepEqual(reply.structuredContent, JSON.parse(mcpTextValue(reply)));
successfulCalls++;
return result;
};
const invalid = async (name, args, diagnostic) => {
const reply = await client.callTool({ name, arguments: args }, options);
assert.equal(
reply.isError,
true,
`${name} accepted ${JSON.stringify(args)}`,
);
// SDK input-schema rejections are text-only; application errors use the
// canonical structured error projection, separately from success schemas.
if (reply.structuredContent !== undefined) {
analysisErrorProjectionSchema.parse(reply.structuredContent.error);
assert.deepEqual(
reply.structuredContent,
JSON.parse(mcpTextValue(reply)),
);
}
if (diagnostic !== undefined) {
const error = reply.structuredContent?.error;
assert.equal(error?.code, "invalid_request");
assert.equal(error?.details?.operation, name);
assert.match(JSON.stringify(error.details.issues), diagnostic);
}
rejectedCalls++;
return reply.structuredContent?.error;
};
const cli = async (command, value, flags = []) => {
const { stdout } = await promisify(execFile)(
process.execPath,
[
entrypoint,
command,
target.path,
value,
"--provider",
"ghidra",
"--json",
...flags,
],
{ env, timeout: 240000, maxBuffer: 72 * 1024 * 1024 },
);
return JSON.parse(stdout).normalized_result;
};
const original = await call("analyze_function", { procedure });
const roots = (await readdir(env.TMPDIR)).filter((name) =>
name.startsWith("rea-ghidra-"),
);
assert.equal(roots.length, 1, "Expected one owned Ghidra runtime");
const runtimeRoot = join(env.TMPDIR, roots[0]);
const { endpoint_path: socketPath } = JSON.parse(
await readFile(join(runtimeRoot, "ownership.json"), "utf8"),
);
const socketRoot = dirname(socketPath);
assert.ok(
Buffer.byteLength(join(runtimeRoot, "bridge.sock"), "utf8") > 107,
"Fixture must exceed Unix socket path capacity",
);
assert.ok(Buffer.byteLength(socketPath, "utf8") <= 103);
assert.notEqual(socketRoot, runtimeRoot);
assert.equal((await stat(socketRoot)).mode & 0o777, 0o700);
const address = original.procedure.address;
const name = original.procedure.name;
const procedures = await call("list_procedures");
const external = procedures.find((item) => item.procedure.external);
assert.ok(external, "Source fixture lacks an external function");
const externalDossier = await call("analyze_function", {
procedure: external.address,
});
const externalEntry = await call("resolve_containing_procedure", {
address: external.address,
});
assert.equal(externalEntry.found, true);
assert.equal(externalEntry.procedure.address, external.address);
assert.equal(externalEntry.procedure.classification.external, true);
assert.deepEqual(
externalEntry.procedure.body,
externalDossier.procedure.body,
);
assert.equal(externalEntry.procedure.body.contains_entry, false);
assert.deepEqual(externalEntry.procedure.body.ranges, []);
const externalSpace = external.address.slice(
0,
external.address.indexOf(":"),
);
const lastExternalOffset = procedures
.filter((item) => item.procedure.external)
.map((item) => BigInt(item.address.slice(item.address.indexOf(":") + 1)))
.reduce((maximum, offset) => (offset > maximum ? offset : maximum), 0n);
const unknownExternal = `${externalSpace}:0x${(lastExternalOffset + 1n).toString(16)}`;
assert.deepEqual(
await call("resolve_containing_procedure", { address: unknownExternal }),
{
query_address: unknownExternal,
found: false,
procedure: null,
reason: "outside_segments",
},
);
assert.deepEqual(
(await cli("function", external.address)).procedure,
externalDossier.procedure,
"CLI and MCP must preserve external identity without inventing body bytes",
);
await invalid(
"annotate_native_function",
{ procedure: external.address, name: "rea_external_edit" },
/Annotations require a local function entry/u,
);
const names = await call("list_names");
const batchIdentity = await verifyGhidraBatchIdentity(call, names);
const instructionFlow = await verifyGhidraInstructionFlow(call, names, cli);
const leaf = names.find((item) =>
item.value.endsWith("rea_ghidra_inventory_leaf"),
);
const pointer = names.find((item) =>
item.value.endsWith("rea_ghidra_interior_pointer"),
);
assert.ok(
leaf && pointer,
"Source fixture lacks the interior function pointer",
);
const interiorTarget = `0x${(BigInt(leaf.address) + 1n).toString(16)}`;
assert.ok(
(await call("xrefs", { address: interiorTarget })).includes(
pointer.address,
),
);
const incoming = await call("procedure_references", {
procedure: leaf.address,
direction: "incoming",
});
const interiorEdge = incoming.references.find(
(edge) =>
edge.source_address === pointer.address &&
edge.target_address === interiorTarget,
);
assert.ok(
interiorEdge,
"Procedure references omitted an observed interior-body edge",
);
assert.equal(interiorEdge.target_procedure?.address, leaf.address);
assert.equal(interiorEdge.source_procedure, null);
assert.equal(interiorEdge.kind.data, true);
const leafDossier = await call("analyze_function", {
procedure: leaf.address,
});
assert.ok(
leafDossier.incoming_references.some(
(edge) =>
edge.source_address === pointer.address &&
edge.target_address === interiorTarget,
),
);
assert.deepEqual(
(await cli("function", leaf.value)).incoming_references,
leafDossier.incoming_references,
"CLI and MCP must retain the same interior-body references",
);
await verifyLegacyGhidraReferenceSnapshot(client, {
target,
procedure: leaf.address,
omittedEdge: interiorEdge,
entrypoint,
env,
});
const longSelector = "REA_MISSING_" + "x".repeat(32768);
for (const selected of [
longSelector,
`${longSelector}:0x1`,
`${"%78".repeat(12000)}:0x1`,
`${longSelector}%GG:0x1`,
]) {
await invalid(
"procedure_address",
{ procedure: selected },
/Unknown|invalid/u,
);
assert.equal(
await call("procedure_address", { procedure: address }),
address,
"Invalid selectors must preserve the live provider connection",
);
}
const baseline = await call("inspect_native_instruction", { address });
assert.equal(baseline.status, "decoded");
const bytes = await call("read_bytes", { address, length: baseline.length });
assert.equal(bytes.bytes_hex, baseline.bytes);
assert.equal((await call("read_bytes", { address })).requested_bytes, 256);
const mapped = await call("address_to_file_offset", { address });
assert.equal(mapped.address, address);
assert.equal(
(await readFile(target.path))
.subarray(mapped.file_offset, mapped.file_offset + baseline.length)
.toString("hex"),
bytes.bytes_hex,
"Memory-to-file mapping must identify the bytes in the caller's artifact",
);
const largeRead = await call("read_bytes", {
address,
length: Number.MAX_SAFE_INTEGER,
});
assert.equal(largeRead.requested_bytes, Number.MAX_SAFE_INTEGER);
assert.equal(largeRead.complete, false);
assert.ok(largeRead.returned_bytes >= baseline.length);
assert.equal(largeRead.bytes_hex.length, largeRead.returned_bytes * 2);
for (const spelling of [
address.toUpperCase(),
address.slice(2).toUpperCase(),
`ram:${address.toUpperCase()}`,
]) {
assert.deepEqual(
await call("inspect_native_instruction", { address: spelling }),
baseline,
);
assert.deepEqual(
await call("read_bytes", { address: spelling, length: baseline.length }),
bytes,
);
assert.equal(await call("address_name", { address: spelling }), name);
assert.equal(
await call("procedure_address", { procedure: spelling }),
address,
);
assert.equal(
(await call("resolve_containing_procedure", { address: spelling }))
.procedure.address,
address,
);
}
assert.deepEqual(
await cli("inspect-native-instruction", address.toUpperCase()),
baseline,
);
// x86 function entries can begin with a one-byte PUSH. Locate an observed
// multi-byte instruction instead of assuming entry + 1 is an interior byte.
let multiByteInstruction;
for (const range of original.procedure.body.ranges) {
let cursor = BigInt(range.start);
while (cursor <= BigInt(range.end)) {
const instruction = await call("inspect_native_instruction", {
address: `0x${cursor.toString(16)}`,
});
if (instruction.status === "decoded" && instruction.length > 1) {
multiByteInstruction = instruction;
break;
}
cursor += BigInt(instruction.length ?? 1);
}
if (multiByteInstruction !== undefined) break;
}
assert.ok(multiByteInstruction, "Fixture lacks a multi-byte instruction");
const interior = `0x${(BigInt(multiByteInstruction.address) + 1n).toString(16)}`;
assert.equal(
(await call("inspect_native_instruction", { address: interior })).status,
"not-instruction-boundary",
);
assert.equal(
(await call("inspect_native_instruction", { address: "0x0" })).status,
"outside-memory",
);
assert.equal(
(await call("resolve_native_call_targets", { address })).status,
"not-call",
);
const directEdge = original.outgoing_references.find(
(edge) => edge.kind.call && !edge.kind.computed,
);
assert.ok(directEdge, "Source fixture lacks a direct call");
const direct = await call("resolve_native_call_targets", {
address: directEdge.source_address,
});
assert.equal(direct.status, "direct");
assert.ok(
direct.targets.some(
(candidate) =>
candidate.address === directEdge.target_address &&
candidate.status === "direct",
),
);
const indirectProcedure = (await call("list_procedures")).find((item) =>
item.value.endsWith("rea_ghidra_inventory_indirect"),
);
assert.ok(indirectProcedure);
const references = await call("procedure_references", {
procedure: indirectProcedure.address,
});
assert.ok(
references.unresolved_calls.length > 0,
"Targetless calls must remain visible",
);
for (const site of references.unresolved_calls) {
const unresolved = await call("resolve_native_call_targets", {
address: site.address,
});
assert.equal(unresolved.status, "unresolved");
assert.deepEqual(unresolved.targets, []);
assert.ok(unresolved.limitations.length > 0);
}
const strings = await call("list_strings");
assert.ok(strings.length > 0);
assert.deepEqual(
await call("list_strings", { address: strings[0].address }),
[strings[0]],
);
const marker = strings.find(
(item) => item.value === "REA_GHIDRA_INVENTORY_ENTRY",
);
assert.ok(marker);
assert.ok(
(
await call("search_strings", { pattern: marker.value.toLowerCase() })
).some((item) => item.address === marker.address),
);
assert.equal(
(
await call("search_strings", {
pattern: marker.value.toLowerCase(),
case_sensitive: true,
})
).some((item) => item.address === marker.address),
false,
);
assert.equal(
(await call("inspect_native_instruction", { address: strings[0].address }))
.status,
"data",
);
await invalid(
"inspect_native_instruction",
{ address: "not-an-address" },
/hexadecimal/u,
);
await invalid(
"inspect_native_instruction",
{ address: `missing:${address}` },
/missing:0x/u,
);
await invalid(
"address_name",
{ address: `0x${(BigInt(address) + 2n ** 64n).toString(16)}` },
/invalid Ghidra address/u,
);
await invalid(
"procedure_address",
{ procedure: "rea_missing_procedure" },
/rea_missing_procedure/u,
);
await invalid(
"search_procedures",
{ pattern: "[", mode: "regex" },
/Invalid regex pattern.*index/u,
);
await invalid(
"list_procedures",
{ document: "rea_missing_program" },
/rea_missing_program.*active program/u,
);
// JSON permits escaped lone surrogates. Error replies must preserve them
// without letting the Java UTF-8 writer terminate the provider session.
await invalid(
"procedure_address",
{ procedure: "missing_\ud800" },
/missing_\\ud800/u,
);
await invalid(
"list_procedures",
{ document: "missing_\udfff" },
/missing_\\udfff/u,
);
for (const [operation, args] of [
["read_bytes", { address, length: 0 }],
["list_procedures", { limit: 100 }],
["list_documents", { extra: true }],
["xrefs", {}],
["procedure_info", { procedure: address, extra: true }],
])
await invalid(operation, args);
for (const changes of [
{},
{ name: "" },
{ comment: null },
{ name: "entry", approve: true },
])
await invalid("annotate_native_function", {
procedure: address,
...changes,
});
let cliError;
try {
await cli("search", "[", ["--kind", "procedures", "--mode", "regex"]);
} catch (error) {
assert.equal(error.code, 1);
cliError = JSON.parse(error.stdout);
}
assert.equal(cliError?.code, "invalid_request");
assert.equal(cliError?.details?.operation, "search_procedures");
assert.match(
JSON.stringify(cliError?.details?.issues),
/Invalid regex pattern.*index/u,
);
// Prime both inventories before editing so stale names cannot hide in caches.
await call("list_names");
await call("list_procedures");
const changes = {
procedure: address,
name: "dead",
comment: "Regular\n註記",
inline_comment: "Inline\n註記",
};
const updated = await call("annotate_native_function", changes);
assert.deepEqual(updated.annotations, {
address,
name: changes.name,
comment: changes.comment,
inline_comment: changes.inline_comment,
});
const regexPattern = "^(a|aa)*b$";
const regexFailure = await client.callTool(
{
name: "search_strings",
arguments: { pattern: regexPattern, mode: "regex" },
},
options,
);
assert.equal(regexFailure.isError, true);
const regexError = regexFailure.structuredContent.error;
assert.equal(regexError.code, "resource_constraint");
assert.equal(regexError.details.operation, "search_strings");
assert.equal(regexError.details.resource, "memory");
assert.match(
regexError.remediation.action,
/literal mode or simplify the regex/u,
);
assert.match(
regexError.details.reason,
/exhausted its stack.*Use literal mode/u,
);
rejectedCalls++;
const nestedPattern = "(".repeat(12000) + "a" + ")".repeat(12000);
const compileFailure = await client.callTool(
{
name: "search_procedures",
arguments: { pattern: nestedPattern, mode: "regex" },
},
options,
);
assert.equal(compileFailure.isError, true);
const compileError = compileFailure.structuredContent.error;
assert.equal(compileError.code, "resource_constraint");
assert.equal(compileError.details.operation, "search_procedures");
assert.match(compileError.details.reason, /stack while compiling pattern/u);
assert.equal(compileError.remediation.action, regexError.remediation.action);
rejectedCalls++;
assert.deepEqual(
await call("analyze_function", { procedure: address }),
updated.dossier,
"Regex stack exhaustion must preserve the live annotation database",
);
assert.equal(
(await call("binary_session")).capabilities.find(
(item) => item.operation === "search_strings",
).available,
true,
);
assert.ok(
(await call("search_procedures", { pattern: changes.name })).some(
(item) => item.address === address,
),
"Regex compiler exhaustion must preserve edited names and search availability",
);
const longLiteral = "a".repeat(3 * 16 ** 3) + "!";
const longString = strings.find((item) => item.value === longLiteral);
assert.ok(longString, "The provider must retain the full regression literal");
const literalMatches = await call("search_strings", { pattern: longLiteral });
assert.deepEqual(literalMatches, [
{ address: longString.address, value: longLiteral },
]);
assert.deepEqual(await cli("search", longLiteral), literalMatches);
await assert.rejects(
cli("search", regexPattern, ["--mode", "regex"]),
(error) => {
assert.equal(error.code, 1);
const rejected = JSON.parse(error.stdout);
assert.equal(rejected.code, regexError.code);
assert.equal(rejected.details.reason, regexError.details.reason);
assert.equal(rejected.remediation.action, regexError.remediation.action);
return true;
},
);
assert.deepEqual(updated.effects, {
scope: "session-analysis-database",
source_bytes_modified: false,
persists_after_close: false,
});
assert.equal(updated.dossier.procedure.name, changes.name);
assert.equal(
await call("procedure_address", { procedure: changes.name }),
address,
);
assert.equal(
(await call("procedure_info", { procedure: changes.name })).entrypoint,
address,
);
assert.equal(
(await call("read_function_instructions", { procedure: changes.name }))
.procedure.address,
address,
);
assert.equal(
(await call("analyze_function", { procedure: changes.name })).procedure
.name,
changes.name,
);
assert.equal(await call("address_name", { address }), changes.name);
for (const inventory of ["list_names", "list_procedures"])
assert.ok(
(await call(inventory)).some(
(item) => item.address === address && item.value === changes.name,
),
);
assert.ok(
(
await call("search_procedures", { pattern: "dead", case_sensitive: true })
).some((item) => item.address === address),
);
for (const field of ["name", "comment", "inline_comment"]) {
for (const text of ["bad\0text", "bad\ud800text", "bad\udffftext"]) {
await invalid(
"annotate_native_function",
{
procedure: address,
name: "MUST_ROLL_BACK",
comment: "MUST ROLL BACK",
inline_comment: "MUST ROLL BACK",
[field]: text,
},
new RegExp(
`Annotation ${field}.*(?:NUL|unpaired Unicode surrogate).*index`,
"u",
),
);
assert.deepEqual(
(
await call("annotate_native_function", {
procedure: address,
name: changes.name,
})
).annotations,
updated.annotations,
"Invalid native text must leave every annotation unchanged",
);
}
}
const unicodeText =
"CRLF\r\nastral 🧪 decomposed e\u0301 control \u0001\b\v\f\u001f noncharacter \ufffe\uffff";
const unicode = await call("annotate_native_function", {
procedure: address,
comment: unicodeText,
inline_comment: unicodeText,
});
assert.equal(unicode.annotations.comment, unicodeText);
assert.equal(unicode.annotations.inline_comment, unicodeText);
assert.ok(unicode.dossier.comments.some((item) => item.text === unicodeText));
await call("annotate_native_function", changes);
await invalid(
"annotate_native_function",
{
procedure: changes.name,
name: "invalid name\n",
comment: "MUST ROLL BACK",
inline_comment: "MUST ROLL BACK",
},
/Invalid function name/u,
);
const retained = await call("annotate_native_function", {
procedure: changes.name,
name: changes.name,
});
assert.deepEqual(retained.annotations, updated.annotations);
const cleared = await call("annotate_native_function", {
procedure: changes.name,
comment: "",
inline_comment: "",
});
assert.equal(cleared.annotations.comment, null);
assert.equal(cleared.annotations.inline_comment, null);
assert.equal(cleared.annotations.name, changes.name);
assert.deepEqual(
await call("read_bytes", { address, length: baseline.length }),
bytes,
);
assert.equal(
createHash("sha256")
.update(await readFile(target.path))
.digest("hex"),
target.sha256,
);
const cliUpdated = await cli("annotate-native-function", name, [
"--name",
changes.name,
"--comment",
changes.comment,
"--inline-comment",
changes.inline_comment,
]);
assert.deepEqual(cliUpdated.annotations, updated.annotations);
assert.deepEqual(cliUpdated.effects, updated.effects);
for (const renamed of ["0xordinary", "probe::qualified", "🧪probe"]) {
await call("annotate_native_function", {
procedure: address,
name: renamed,
});
assert.equal(
await call("procedure_address", { procedure: renamed }),
address,
);
assert.equal(await call("address_name", { address }), renamed);
}
for (const selected of [address, indirectProcedure.address]) {
await call("annotate_native_function", {
procedure: selected,
name: "rea_ambiguous",
});
}
const ambiguous = await invalid(
"procedure_address",
{ procedure: "rea_ambiguous" },
/ambiguous.*select an exact entry address/u,
);
for (const selected of [address, indirectProcedure.address]) {
assert.ok(
JSON.stringify(ambiguous.details.issues).includes(selected),
"Ambiguity diagnostics must retain every matching entry address",
);
assert.equal(
await call("procedure_address", { procedure: selected }),
selected,
);
}
const snapshotLifecycle = await verifyGhidraSnapshotLifecycle(
client,
target,
await call("analyze_function", { procedure: address }),
cli,
);
successfulCalls += snapshotLifecycle.successfulCalls;
rejectedCalls += snapshotLifecycle.rejectedCalls;
const targetAdmission = await verifyGhidraTargetAdmission({
call,
reject: invalid,
target,
entry: address,
runtimeRoot: env.TMPDIR,
});
await verifyGhidraNamespaceAnnotations({
call,
reject: invalid,
target,
entrypoint,
env,
});
await verifyGhidraEntryAliases({
call,
reject: invalid,
target,
entrypoint,
env,
});
await verifyGhidraLargeResults({
call,
reject: invalid,
target,
entrypoint,
env,
});
await assert.rejects(access(socketRoot), { code: "ENOENT" });
await assert.rejects(access(runtimeRoot), { code: "ENOENT" });
assert.equal(await call("procedure_address", { procedure: name }), address);
assert.equal(await call("address_name", { address }), name);
assert.equal(
await call("procedure_address", { procedure: indirectProcedure.value }),
indirectProcedure.address,
);
assert.deepEqual(
(await call("analyze_function", { procedure: address })).comments,
original.comments,
);
assert.equal(
createHash("sha256")
.update(await readFile(target.path))
.digest("hex"),
target.sha256,
);
await verifyStartupCancellation(client, call, target, env, options);
return {
mocked: false,
catalog_tools: catalog.tools.length,
successful_calls: successfulCalls,
rejected_calls: rejectedCalls,
cli_mcp_parity: true,
mutation_rollback: true,
annotation_native_text_validation: true,
lossless_unicode_transport: true,
exact_external_entry_resolution: true,
recoverable_regex_stack_exhaustion: true,
complete_function_body_references: true,
ambiguity_candidates_inline: true,
legacy_reference_snapshot_rejected: true,
mutation_snapshot_lifecycle: true,
concurrent_annotation_snapshot_close: true,
changed_source_admission_and_recovery: true,
source_permission_denial_verified: targetAdmission.permissionDenied,
missing_and_nonregular_source_rejected: true,
imported_source_identity_retained: true,
equivalent_instruction_address_spellings: true,
batch_procedure_identity: batchIdentity,
instruction_flow: instructionFlow,
qualified_annotation_name_roundtrip: true,
imported_entry_alias_selection: true,
oversized_result_retention_and_complete_export: true,
long_selector_rejection_and_provider_recovery: true,
source_immutable: true,
reopen_discards_edits: true,
long_tmpdir_private_socket_cleanup: true,
real_startup_cancellation_cleanup: true,
};
}
async function verifyStartupCancellation(client, call, target, env, options) {
await call("close_binary");
await call("open_binary", { path: target.path, provider_id: "ghidra" });
// The composite provider selects the target lazily. Use a fresh search input
// so persisted immutable snapshots cannot bypass actual provider startup.
const request = {
name: "search_procedures",
arguments: { pattern: `startup-cancellation-${randomUUID()}` },
};
const controller = new AbortController();
let settled = false;
let outcome;
const pending = client
.callTool(request, { ...options, signal: controller.signal })
.then(
(reply) => {
settled = true;
outcome = reply;
return { reply };
},
(error) => {
settled = true;
outcome = String(error);
return { error };
},
);
const deadline = Date.now() + 30000;
let launched;
try {
while (Date.now() < deadline && !settled) {
const roots = (await readdir(env.TMPDIR)).filter((name) =>
name.startsWith("rea-ghidra-"),
);
if (roots.length === 1) {
const root = join(env.TMPDIR, roots[0]);
try {
const ownership = JSON.parse(
await readFile(join(root, "ownership.json"), "utf8"),
);
const endpointPath = ownership.endpoint_path;
// Observe the JVM inside the owned process group before the bridge
// publishes its socket. A log file can appear after analysis finishes.
const { stdout } = await promisify(execFile)("ps", [
"-axo",
"pid=,pgid=,comm=",
]);
const javaProcess = stdout.split("\n").find((line) => {
const fields = line.trim().split(/\s+/u);
return (
Number(fields[1]) === ownership.process_group_id &&
/(?:^|\/)java$/u.test(fields.slice(2).join(" "))
);
});
if (javaProcess === undefined) {
await delay(25);
continue;
}
const javaPid = Number(javaProcess.trim().split(/\s+/u)[0]);
await assert.rejects(access(endpointPath), { code: "ENOENT" });
process.kill(ownership.pid, 0);
launched = {
root,
socketRoot: dirname(endpointPath),
pid: ownership.pid,
javaPid,
};
break;
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
}
await delay(25);
}
assert.ok(
launched,
`No real headless process was observed before cancellation: ${JSON.stringify(outcome)}`,
);
assert.equal(
settled,
false,
"Cancellation must interrupt pending provider work",
);
} finally {
controller.abort();
await pending;
}
const cleanupDeadline = Date.now() + 30000;
while (Date.now() < cleanupDeadline) {
const processGone = [launched.pid, launched.javaPid].every((pid) => {
try {
process.kill(pid, 0);
return false;
} catch (error) {
if (error.code === "ESRCH") return true;
throw error;
}
});
try {
await access(launched.root);
} catch (error) {
if (error.code === "ENOENT" && processGone) break;
if (error.code === "ENOENT") {
await delay(25);
continue;
}
throw error;
}
await delay(25);
}
await assert.rejects(access(launched.root), { code: "ENOENT" });
await assert.rejects(access(launched.socketRoot), { code: "ENOENT" });
assert.throws(() => process.kill(launched.pid, 0), { code: "ESRCH" });
assert.throws(() => process.kill(launched.javaPid, 0), { code: "ESRCH" });
assert.equal(
(await call("binary_session")).open,
true,
"Cancellation must preserve the selected target",
);
assert.deepEqual(
await call(request.name, request.arguments),
[],
"A cancelled startup must allow a fresh provider query",
);
}
+190
View File
@@ -0,0 +1,190 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
/** Verify imported entry aliases through CLI/MCP without admitting interior labels. */
export async function verifyGhidraEntryAliases({
call,
reject,
target,
entrypoint,
env,
}) {
const exec = promisify(execFile);
const compiler = process.env.REA_CC ?? "cc";
const workspace = await mkdtemp(join(tmpdir(), "rea-ghidra-entry-aliases-"));
const source = fileURLToPath(
new URL("../../tests/conformance/ghidra/entry-aliases.c", import.meta.url),
);
const path = join(workspace, "entry-aliases");
const prefix = process.platform === "darwin" ? "_" : "";
try {
await exec(compiler, ["-O0", "-g", "-fno-inline", source, "-o", path], {
env,
timeout: 30000,
});
const bytes = await readFile(path);
await call("close_binary");
await call("open_binary", { path, provider_id: "ghidra" });
const names = await call("list_names");
const alias = names.find(
({ value }) => value === `${prefix}rea_entry_alias`,
);
assert.ok(alias, "Fixture entry alias was not imported");
assert.equal(
alias.symbol.primary,
false,
"The regression must exercise a secondary entry symbol",
);
assert.equal(alias.symbol.source, "imported");
const canonical = (await call("list_procedures")).find(
({ address }) => address === alias.address,
);
assert.ok(canonical, "Fixture alias is not at a function entry");
const xrefsSelectors = [];
for (const value of [
canonical.value,
alias.value,
`${prefix}rea_xrefs_data`,
`${prefix}rea_xrefs_unreferenced`,
]) {
const symbol = names.find((item) => item.value === value);
assert.ok(symbol, `Missing xrefs selector ${value}`);
const expected = await call("xrefs", { address: symbol.address });
if (value === `${prefix}rea_xrefs_unreferenced`)
assert.deepEqual(expected, []);
else assert.ok(expected.length > 0, `Missing references for ${value}`);
xrefsSelectors.push({ symbol, expected });
}
for (const procedure of [
alias.value,
"dead",
canonical.value,
alias.address,
]) {
assert.equal(
await call("procedure_address", { procedure }),
alias.address,
);
assert.equal(
(await call("analyze_function", { procedure })).procedure.address,
alias.address,
);
assert.equal(
await call("procedure_pseudo_code", { procedure }),
await call("procedure_pseudo_code", { procedure: alias.address }),
);
}
// Leaf and qualified forms of the primary name must not duplicate a match.
assert.equal(
(await call("procedure_info", { procedure: alias.value })).entrypoint,
alias.address,
);
const interior = names.find(
({ value }) => value === `${prefix}rea_interior`,
);
assert.ok(interior);
assert.notEqual(interior.address, alias.address);
const failure = await reject("procedure_address", {
procedure: interior.value,
});
assert.equal(failure.code, "invalid_request");
assert.match(
JSON.stringify(failure.details.issues),
/Unknown Ghidra procedure name or address/u,
);
assert.equal(
await call("procedure_address", { procedure: interior.address }),
alias.address,
"Explicit interior addresses keep their existing containment semantics",
);
const edited = await call("annotate_native_function", {
procedure: alias.value,
comment: "Selected by imported entry alias",
});
assert.equal(edited.annotations.address, alias.address);
assert.equal(
edited.annotations.comment,
"Selected by imported entry alias",
);
assert.equal(
await call("procedure_address", { procedure: alias.value }),
alias.address,
);
await call("close_binary");
const { stdout } = await exec(
process.execPath,
[
entrypoint,
"function",
path,
alias.value,
"--provider",
"ghidra",
"--json",
],
{ env, timeout: 240000, maxBuffer: 16 * 1024 * 1024 },
);
assert.equal(
JSON.parse(stdout).normalized_result.procedure.address,
alias.address,
);
for (const { symbol, expected } of xrefsSelectors) {
for (const selector of [
[symbol.value],
["--address", symbol.value],
[symbol.address],
]) {
const { stdout } = await exec(
process.execPath,
[
entrypoint,
"xrefs",
path,
...selector,
"--provider",
"ghidra",
"--json",
],
{ env, timeout: 240000, maxBuffer: 16 * 1024 * 1024 },
);
const evidence = JSON.parse(stdout);
assert.deepEqual(evidence.normalized_result, expected);
assert.equal(evidence.parameters.address, selector.at(-1));
}
}
await assert.rejects(
exec(
process.execPath,
[
entrypoint,
"xrefs",
path,
"rea_missing_symbol",
"--provider",
"ghidra",
"--json",
],
{ env, timeout: 240000, maxBuffer: 16 * 1024 * 1024 },
),
(error) => {
const failure = JSON.parse(error.stdout);
assert.equal(failure.code, "invalid_request");
assert.equal(failure.details.operation, "xrefs");
assert.match(
JSON.stringify(failure.details.issues),
/Unknown analyzed symbol name.*rea_missing_symbol/u,
);
return true;
},
);
assert.deepEqual(await readFile(path), bytes);
await call("open_binary", { path: target.path, provider_id: "ghidra" });
} finally {
await rm(workspace, { recursive: true, force: true });
}
}
@@ -0,0 +1,110 @@
import assert from "node:assert/strict";
/** Verify decoded return evidence and distinct non-return flows on owned fixtures. */
export async function verifyGhidraInstructionFlow(call, names, cli) {
const leaf = names.find((item) =>
item.value.endsWith("rea_ghidra_inventory_leaf"),
);
assert.ok(leaf, "Source fixture lacks its returning leaf");
const leafInstructions = await inspectFunction(call, leaf.address);
const returned = leafInstructions.find(
(instruction) => instruction.flow.kind === "return",
);
assert.ok(returned, "Returning source leaf lacks decoded RETURN evidence");
assertReturnEvidence(returned);
assert.equal(
(await call("resolve_native_call_targets", { address: returned.address }))
.status,
"not-call",
);
assert.deepEqual(
await cli("inspect-native-instruction", returned.address),
returned,
);
const controls = [
["rea_ghidra_flow_return", "RET", "c3"],
["rea_ghidra_flow_cleanup_return", "RET", "c21000"],
["rea_ghidra_flow_shared_tail", "JMP", null],
["rea_ghidra_flow_trap", "UD2", "0f0b"],
];
let checked = 0;
for (const [symbol, mnemonic, bytes] of controls) {
const procedure = names.find((item) => item.value === symbol);
if (procedure === undefined) continue;
const before = await call("resolve_containing_procedure", {
address: procedure.address,
});
const instructions = await inspectFunction(call, procedure.address);
const instruction = instructions.find((item) => item.mnemonic === mnemonic);
assert.ok(instruction, `Source fixture lacks ${symbol}'s ${mnemonic}`);
if (bytes !== null) assert.equal(instruction.bytes, bytes);
if (mnemonic === "RET") {
assertReturnEvidence(instruction);
assert.equal(instruction.flow.kind, "return");
if (bytes === "c21000")
assert.equal(instruction.operands[0]?.raw, "0x10");
} else {
assert.notEqual(instruction.flow.kind, "return");
assert.ok(
instruction.flow.classification_evidence.some(
(item) => item.source === "ghidra-listing-flow-type",
),
);
}
assert.deepEqual(
await call("resolve_containing_procedure", {
address: procedure.address,
}),
before,
);
checked++;
}
const unavailable = await call("inspect_native_instruction", {
address: "0x0",
});
assert.equal(unavailable.status, "outside-memory");
assert.equal(unavailable.flow.kind, "unavailable");
assert.deepEqual(unavailable.flow.classification_evidence, []);
return {
return_instruction: returned.address,
decoded_pcode_return: true,
return_cli_mcp_parity: true,
explicit_x86_elf_controls: checked,
explicit_control_scope:
checked === 0
? "Not present on this host-native fixture format/architecture"
: "x86-64 ELF",
generic_unknown_preserved: true,
};
}
async function inspectFunction(call, procedure) {
const listing = await call("read_function_instructions", { procedure });
const instructions = [];
for (const line of listing.instructions) {
const delimiter = line.indexOf(": ");
assert.ok(delimiter > 0, `Malformed provider instruction listing: ${line}`);
instructions.push(
await call("inspect_native_instruction", {
address: line.slice(0, delimiter),
}),
);
}
return instructions;
}
function assertReturnEvidence(instruction) {
assert.equal(instruction.status, "decoded");
assert.ok(
instruction.flow.classification_evidence.some(
(item) =>
item.source === "ghidra-instruction-pcode" && item.value === "RETURN",
),
);
assert.ok(
!instruction.limitations.some((text) =>
text.includes("not classified as a return"),
),
);
}

Some files were not shown because too many files have changed in this diff Show More