diff --git a/.agents/skills/reverse-engineer-anything/references/evidence-workflows.md b/.agents/skills/reverse-engineer-anything/references/evidence-workflows.md
index 32a5962d40..0a83d37e79 100644
--- a/.agents/skills/reverse-engineer-anything/references/evidence-workflows.md
+++ b/.agents/skills/reverse-engineer-anything/references/evidence-workflows.md
@@ -73,6 +73,14 @@ Parent Evidence ID plus source index preserves same-address
alternatives. Follow `coverage.next_offset` only as far as the question needs;
exhausting retained rows does not establish complete program discovery.
+`batch_decompile` returns every body unless you select `detail: "summary"` when
+the connected tool advertises it. Use its parent Evidence ID with
+`view: {kind: "batch-decompile", collection: "items"}` for body-free rows and
+exact `where.indices`, `addresses`, `procedure_addresses` or `statuses`. Add
+`include_pseudocode: true` for several chosen bodies, or use
+`collection: "pseudocode"` with one row's `index` to page a large body. Batch
+limitations aggregate every item, so do not attribute them to one successful row.
+
Use `record_unknown` to track unresolved questions in the user's investigation
and name the authority or environment still required. Supply supporting and
contradicting evidence IDs. It updates the session registry directly.
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 91063384cc..5420b790ad 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -106,7 +106,8 @@ limits, security/privacy/containment implications, and meaningful proof gaps. --
## Author readiness
-
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 55221d1261..744cb9361f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -93,7 +93,8 @@ Before requesting maintainer review, the human owner must:
passing. Follow the [verification requirements](#development-feedback-and-pr-verification)
for required checks and real-provider coverage.
- Complete the **Author readiness** declaration in the PR template, explicitly
- stating that the PR is ready for initial review.
+ stating that the PR is ready for initial review, unless the PR author is
+ exempt from the declaration as listed below.
Keep the PR description concise and current. Explain the problem, observable
outcome, approach, and useful evidence; link long logs or put them in optional
@@ -114,9 +115,12 @@ validation current as work changes. AI agents must leave human declarations to
the human owner and must not attest to a human review on their behalf.
The `PR readiness` check reports pending for drafts and requires a named human
-owner and all three template declarations for non-drafts. It checks that the
-declaration is filled, not whether the code or evidence is correct. Existing PRs
-can copy the **Author readiness** section from the current template.
+owner and all three template declarations for non-drafts. PRs authored by
+@morluto, @kaoru0822-kitauji, @N0zoM1z0, or @gensokyo-dev are exempt from
+the declaration; their human ownership, review, and evidence responsibilities
+above still apply. The check identifies authors by GitHub account ID. It checks
+that the declaration is filled, not whether the code or evidence is correct.
+Existing PRs can copy the **Author readiness** section from the current template.
Open PRs must have distinct head commits because GitHub shares commit statuses.
Maintainers enable merge enforcement after the workflow is merged: run
diff --git a/README.md b/README.md
index 474ecfbec3..a6343e167a 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,8 @@
**[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) · [Build RL environments](#build-rl-environments) · [FAQ](#faq) · [Documentation](#documentation)
npx rea-agents setup
diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md
index e58c4d94f2..61426ff708 100644
--- a/THIRD_PARTY_NOTICES.md
+++ b/THIRD_PARTY_NOTICES.md
@@ -60,7 +60,7 @@ pin inspection sources; they are not included in the npm package.
| ghidra-nativeaot | [MIT](https://github.com/Washi1337/ghidra-nativeaot/blob/effeb734fc570c32650f88b159608979dc7b423e/LICENSE.md) | Pinned inspection source; derived parser attribution above. |
| JADX | [Apache-2.0](https://github.com/skylot/jadx/blob/v1.5.6/LICENSE) | The audited jadx-headless-mcp source uses JADX 1.5.6 for Android decompilation; the engine JAR is separately supplied. |
| jadx-headless-mcp | [Apache-2.0](https://github.com/1013503897/jadx-headless-mcp/blob/5844800a486d2248fa949b74c9896285f5b54de2/LICENSE) | v0.7.1; pinned submodule and user-supplied JAR. |
-| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.988/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.988/NOTICE) | Bring your own `kuna`; REA verified 1.988 and never downloads, installs or bundles it. REA invokes it as a separate process. |
+| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.989/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.989/NOTICE) | Bring your own `kuna`; REA verified 1.989 and never downloads, installs or bundles it. REA invokes it as a separate process. |
| Apktool | [Apache-2.0](https://github.com/iBotPeaches/Apktool/blob/master/LICENSE.md) | User-supplied Android resource decoder. |
| Binwalk | [MIT](https://github.com/ReFirmLabs/binwalk/blob/4fdab3d464d97b68e0af9088df3f9e2e1545b21c/LICENSE) | v3.1.0; pinned submodule and user-supplied firmware engine. |
| Unblob | [MIT](https://github.com/onekey-sec/unblob/blob/1fcc7a0a584a70a96c31f5a276c20944d199a089/LICENSE) | 26.6.4; pinned submodule and user-supplied firmware engine. |
diff --git a/bridge/native/ReaNativeUI.swift b/bridge/native/ReaNativeUI.swift
index b123a57f24..2c7bd73992 100644
--- a/bridge/native/ReaNativeUI.swift
+++ b/bridge/native/ReaNativeUI.swift
@@ -65,7 +65,10 @@ func observe(_ request: Request) async throws -> [String: Any] {
guard SHA256.hash(data: executableBytes).map({ String(format: "%02x", $0) }).joined() == request.sha256 else {
try fail("target-mismatch", "Running executable bytes differ from the active target digest")
}
- guard let windows = CGWindowListCopyWindowInfo([.optionIncludingWindow], request.window_id) as? [[String: Any]],
+ // .optionIncludingWindow omits a selected window while it is off screen, which
+ // is not absence: select from every window, and let capture report what it can.
+ guard let windows = (CGWindowListCopyWindowInfo([.optionAll], kCGNullWindowID) as? [[String: Any]])?
+ .filter({ ($0[kCGWindowNumber as String] as? NSNumber)?.uint32Value == request.window_id }),
windows.count == 1, let window = windows.first,
(window[kCGWindowOwnerPID as String] as? NSNumber)?.int32Value == request.pid,
(window[kCGWindowLayer as String] as? NSNumber)?.intValue == 0,
diff --git a/bridge/process/ProcessRunTokenReader.swift b/bridge/process/ProcessRunTokenReader.swift
index d5152ab50c..931eb48750 100644
--- a/bridge/process/ProcessRunTokenReader.swift
+++ b/bridge/process/ProcessRunTokenReader.swift
@@ -15,6 +15,8 @@ struct ProcessIdentityObservation: Encodable {
let effective_uid: UInt32?
let lifecycle: String?
let relation: ProcessRelationObservation?
+ /** Display-only kernel accounting name (p_comm, at most MAXCOMLEN bytes). */
+ let name: String?
let reason: String?
}
@@ -31,6 +33,7 @@ struct ProcessMetadata {
let parentPid: Int32
let processGroupId: Int32
let pointerSize: Int
+ let name: String
}
struct ProcessSnapshot {
@@ -92,7 +95,8 @@ func readProcessSnapshot(
lifecycle: process.kp_proc.p_stat == SZOMB ? "zombie" : "non-zombie",
parentPid: process.kp_eproc.e_ppid,
processGroupId: process.kp_eproc.e_pgid,
- pointerSize: process.kp_proc.p_flag & P_LP64 != 0 ? 8 : 4
+ pointerSize: process.kp_proc.p_flag & P_LP64 != 0 ? 8 : 4,
+ name: processName(process)
)
)
},
@@ -103,6 +107,13 @@ func readProcessSnapshot(
return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(ENOMEM)")
}
+/** Decode the NUL-terminated accounting name; invalid UTF-8 is replaced for display. */
+private func processName(_ process: kinfo_proc) -> String {
+ withUnsafeBytes(of: process.kp_proc.p_comm) { bytes in
+ String(decoding: bytes.prefix { $0 != 0 }, as: UTF8.self)
+ }
+}
+
func readProcessIdentities(pids: [Int32]) -> [ProcessIdentityObservation] {
readProcessIdentities(pids: pids, snapshot: readProcessSnapshot())
}
@@ -130,6 +141,7 @@ func readProcessIdentities(
effective_uid: nil,
lifecycle: nil,
relation: nil,
+ name: nil,
reason: "not-in-snapshot"
)
}
@@ -144,6 +156,7 @@ func readProcessIdentities(
parent_pid: metadata.parentPid,
process_group_id: metadata.processGroupId
),
+ name: metadata.name,
reason: nil
)
}
@@ -157,6 +170,7 @@ private func unavailableIdentity(pid: Int32, reason: String) -> ProcessIdentityO
effective_uid: nil,
lifecycle: nil,
relation: nil,
+ name: nil,
reason: reason
)
}
@@ -214,6 +228,22 @@ func processRunTokenObservation(
parse: Result,
codeSigningFlags: () -> UInt32?
) -> RunTokenObservation {
+ // Native inspection restrictions apply even when argv is incomplete or
+ // overwritten, before parsing reaches the environment. Keep this fact at
+ // the native boundary; cleanup still decides whether the process is related.
+ func withheldEnvironment() -> RunTokenObservation? {
+ guard let flags = codeSigningFlags() else { return nil }
+ if (flags & csPlatformBinary) != 0 {
+ return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
+ }
+ if (flags & csRestrict) != 0 {
+ return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
+ }
+ return nil
+ }
+ func unavailableEnvironment(_ reason: String) -> RunTokenObservation {
+ withheldEnvironment() ?? unavailable(pid: pid, reason: reason)
+ }
switch parse {
case .success(let parsed):
if let runId = parsed.runId {
@@ -224,40 +254,25 @@ func processRunTokenObservation(
if parsed.environmentEntryCount > 0 {
return RunTokenObservation(pid: pid, state: "readable", run_id: nil, reason: nil)
}
- let flags = codeSigningFlags()
- if let flags, (flags & csPlatformBinary) != 0 {
- return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
- }
- if let flags, (flags & csRestrict) != 0 {
- return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
- }
+ if let withheld = withheldEnvironment() { return withheld }
return RunTokenObservation(pid: pid, state: "readable", run_id: nil, reason: nil)
case .failure(let failure):
switch failure {
case .environmentUnavailable:
- let flags = codeSigningFlags()
- if let flags, (flags & csPlatformBinary) != 0 {
- return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
- }
- if let flags, (flags & csRestrict) != 0 {
- return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
- }
- return unavailable(pid: pid, reason: "environment_unavailable")
+ return unavailableEnvironment("environment_unavailable")
case .ambiguousEnvironmentBoundary:
- if let flags = codeSigningFlags(), (flags & csRestrict) != 0 {
- return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
- }
- return unavailable(pid: pid, reason: "ambiguous_environment_boundary")
+ return unavailableEnvironment("ambiguous_environment_boundary")
+ // These failures already establish that token bytes were available.
case .duplicateToken:
return unavailable(pid: pid, reason: "duplicate_run_token")
case .invalidTokenEncoding:
return unavailable(pid: pid, reason: "invalid_run_token_encoding")
case .appleVectorUnavailable:
- return unavailable(pid: pid, reason: "apple_vector_unavailable")
+ return unavailableEnvironment("apple_vector_unavailable")
case .invalidBuffer:
- return unavailable(pid: pid, reason: "malformed_procargs")
+ return unavailableEnvironment("malformed_procargs")
case .invalidBufferAt(let stage):
- return unavailable(pid: pid, reason: "malformed_procargs_\(stage.rawValue)")
+ return unavailableEnvironment("malformed_procargs_\(stage.rawValue)")
}
}
}
diff --git a/bridge/pwntools/layout_impl.py b/bridge/pwntools/layout_impl.py
index 86ec3f623b..a9c112fb70 100644
--- a/bridge/pwntools/layout_impl.py
+++ b/bridge/pwntools/layout_impl.py
@@ -16,10 +16,10 @@ PROFILE = "pwntools@4.15.0;pyelftools@0.33;unicorn@2.1.2"
PACKAGES = {"pwntools": "4.15.0", "pyelftools": "0.33", "unicorn": "2.1.2"}
OUTPUT_BYTES = 64 * 1024 * 1024
# (e_machine, ELF class bits, byte order) -> architecture pwntools must also report.
-PROFILES = {(62, 64, "little"): "amd64", (3, 32, "little"): "i386", (40, 32, "little"): "arm",
- (8, 32, "little"): "mips", (8, 32, "big"): "mips"}
-PROFILE_SCOPE = ("Layout profile requires ELF64 x86-64, ELF32 i386, ELF32 ARM little-endian "
- "or ELF32 MIPS little- or big-endian objects.")
+PROFILES = {(62, 64, "little"): "amd64", (183, 64, "little"): "aarch64", (3, 32, "little"): "i386",
+ (40, 32, "little"): "arm", (8, 32, "little"): "mips", (8, 32, "big"): "mips"}
+PROFILE_SCOPE = ("Layout profile requires ELF64 x86-64, ELF64 AArch64 little-endian, ELF32 i386, "
+ "ELF32 ARM little-endian or ELF32 MIPS little- or big-endian objects.")
class LayoutFailure(Exception):
diff --git a/docs/agent-environments.md b/docs/agent-environments.md
index 8bcc62be86..71fd35bd65 100644
--- a/docs/agent-environments.md
+++ b/docs/agent-environments.md
@@ -1149,7 +1149,12 @@ global conflict involving an equal pair is not used to normalize either pair; th
A step agrees when the statuses and the normalised bodies are equal. The report gives agreement overall, per probe and
per tool, `agreement_ignoring_order` for steps that differ only in item order, and each step's differences, at most 20,
-with a JSON pointer and both values cut short:
+with a JSON pointer and both values cut short. Each step reports `differences_total` before that cap,
+`differences_omitted`, and `differences_truncated` (true when entries were omitted). Agreement and categories use the
+complete comparison. Omitted entries are not retained, so `--full-output` cannot recover them; inspect the original
+capture and probe Evidence to investigate beyond the returned pointers.
+
+The difference categories are:
| Category | Meaning |
| --------------- | ------------------------------------------------------------------------------------------------------------ |
diff --git a/docs/binary-diagnostics.md b/docs/binary-diagnostics.md
index b58b8d04dc..cf074b10c3 100644
--- a/docs/binary-diagnostics.md
+++ b/docs/binary-diagnostics.md
@@ -9,9 +9,11 @@ ordinary binary inspection use the same modular capability.
## Bring your own engine
Real verification covers **Linux x64** hosts and **EXEC/DYN/REL** objects in
-these profiles: ELF64 x86-64, ELF32 i386, ELF32 little-endian ARM and ELF32
-little- or big-endian MIPS. Other machines, classes and byte orders, including
-AArch64 and big-endian ARM, return `unsupported_target`. Provide an absolute
+these profiles: ELF64 x86-64, ELF64 little-endian AArch64, ELF32 i386, ELF32
+little-endian ARM and ELF32 little- or big-endian MIPS. Other machines, classes
+and byte orders, including big-endian ARM, return `unsupported_target`. Other
+hosts, including Linux arm64, return `capability_unavailable` naming the
+detected host before any target is read. Provide an absolute
Python executable whose environment already contains unchanged pwntools 4.15.0,
pyelftools 0.33 and Unicorn 2.1.2:
@@ -424,8 +426,10 @@ rea inspect-native-exports ./libexample.so --detail summary --evidence_output ./
without an export directory is a complete empty inventory.
- ELF rows list defined `.dynsym` symbols with global, weak or GNU-unique
binding and default or protected visibility. Each row keeps `value`
- (`st_value` as stored; bit 0 marks Thumb code on 32-bit ARM), `size`, `type`
- and `binding`. ELF string tables do not declare a text encoding. Names that
+ (`st_value` as stored; bit 0 marks Thumb code on 32-bit ARM), `size`, `type`,
+ `binding` and `visibility` (`"default"` or `"protected"`; `"unknown"` in older
+ historical Evidence where visibility was not recorded). ELF string tables do
+ not declare a text encoding. Names that
contain non-ASCII bytes are decoded as UTF-8 when those bytes form valid
UTF-8; `name_utf8_decodable` records whether the non-ASCII name bytes are
valid UTF-8 and `name_bytes_base64` retains those exact bytes (`null` for
diff --git a/docs/browser-observation.md b/docs/browser-observation.md
index 22265bd53f..c2830a91a0 100644
--- a/docs/browser-observation.md
+++ b/docs/browser-observation.md
@@ -500,7 +500,7 @@ REA retains local observation data and removes only URL userinfo credentials, ex
connection/frame inventories; matching direction/opcode sequences alone do
not prove alignment after drops. Redirect resource types use only shared known metadata:
an omitted type leaves equality unknown rather than proving a modification.
-- Console observations with a stack source retain call type, argument types, the CDP `Runtime.Timestamp` in milliseconds since Unix epoch, and source location. An absent or invalid timestamp is `null` and makes `console_events` incomplete; an observed zero remains zero. Argument types are listed by CDP argument position. An absent or non-array argument inventory is `null`, and a malformed argument keeps its position as `unknown`; either makes `console_events` incomplete, while an observed empty inventory remains `[]`. A malformed stack or top stack frame is not replaced by its caller: the console event is excluded, and a request initiator location or WebMCP `registration_source` is `null`; each is reported as `invalid_protocol_value`. This epoch-millisecond clock is separate from the monotonic-second timestamps in `observe-web-session`. When console text capture is selected, already-delivered primitive values are retained verbatim with their original `argument_index`; objects, getters, and remote properties are never expanded. Text capture is `unavailable` when the argument inventory is unknown.
+- Console observations with a stack source retain call type, argument types, the CDP `Runtime.Timestamp` in milliseconds since Unix epoch, and source location. An absent or invalid timestamp is `null` and makes `console_events` incomplete; an observed zero remains zero. Argument types are listed by CDP argument position. An absent or non-array argument inventory is `null`, and a malformed argument keeps its position as `unknown`; either makes `console_events` incomplete, while an observed empty inventory remains `[]`. A malformed stack or top stack frame is not replaced by its caller: the console event is excluded, and a request initiator location or WebMCP `registration_source` is `null`; each is reported as `invalid_protocol_value`. Source locations are CDP's zero-based lines and UTF-16 columns. A negative column, which Chrome reports on the first line of some inline scripts, is kept as reported and does not locate a column in the source URL; an invalid line or a non-integer column is `null` (WebMCP `registration_source` is `null`) and is reported as `invalid_protocol_value`. This epoch-millisecond clock is separate from the monotonic-second timestamps in `observe-web-session`. When console text capture is selected, already-delivered primitive values are retained verbatim with their original `argument_index`; objects, getters, and remote properties are never expanded. Text capture is `unavailable` when the argument inventory is unknown.
- WebSocket observations retain direction, opcode, and payload byte length. A missing or malformed CDP opcode is returned as `null`, with opcode-dependent payload byte length and shape also `null`, and marks `websocket_frames` incomplete. Missing or wrong-typed payload data is not treated as empty; its byte length is `null`, and a text payload shape is unknown. Numeric opcode `0` and an observed empty payload remain distinct from missing data. Opcode is nullable in the public inspection schema, so clients must handle unknown protocol values. When shape capture is selected, text messages are classified as text or complete value-free JSON shape; binary bytes, hashes, prefixes, and raw payloads are never retained.
- Storage observations always redact values. Key names, IndexedDB names, and cache names require `include_storage_keys`; stable content fingerprints require the additional `include_storage_fingerprints` selection. Cookie fingerprints cover cookies applicable to the current main-frame URL, including its path, rather than every cookie path on that origin. Other storage remains scoped to the origin. Cache fingerprints hash all decoded response bytes, including bodies above 64 KiB, subject to the [64 MiB per-message CDP limit](#request-boundary). Missing or undecodable cache bodies and partial IndexedDB remote objects make fingerprint coverage incomplete, so identical observations remain `unknown`.
- Unavailable or malformed storage inventories mark `storage_keys` unavailable, even without fingerprints. Comparisons suppress inferred key additions and removals while preserving modifications proven by complete fingerprints on both sides. Previously exported captures are not retroactively corrected; recapture them to obtain these coverage markers.
diff --git a/docs/cli.md b/docs/cli.md
index e1575faa0e..76a3e5ef5c 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -569,6 +569,17 @@ prints a failure, so it omits the same-session reuse and
`parameter`) for a new invocation and rerun. For example, a Ghidra decompile
deadline names `REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS`.
+`rea debug-with-gdb` and `rea debug-with-rizin` hold their debugger only for the
+command's own process. If cleanup cannot confirm the debugger stopped, they print
+`cleanup_incomplete` and exit nonzero without stopping it. `remediation.action`
+names the process, as does `retained_debugger_pids` in `details.diagnostics`, or
+in `details.diagnostics.cleanup_error.details.diagnostics` when the command also
+failed or was interrupted. Exiting closes the debugger's command and output pipes.
+REA does not establish what the debugger or its target does next, so check that
+process. An MCP server that shuts down likewise exits with status 1 instead of
+waiting on a debugger it could not stop. It names the process on stderr and as
+`retained_debugger_pids` in its error log.
+
If one-shot analysis fails and closing its session also fails, the original
error details remain inline and `details.cleanup_error` describes the later
close failure. Each diagnostic retains its own reported resources; the later
@@ -1294,6 +1305,18 @@ new file. A publication that has committed can win a later cancellation; the
after commit, the `cleanup_incomplete` error reports the same file as
`details.published_path` and `details.bytes`.
+When a failure retains a completed observation and no bundle committed,
+`details.source_closure` states what that observation cites. `publication` is
+`not_published`. `records` holds authentic source Evidence this invocation
+captured, within 64 KiB. `omitted` lists up to 64 other identities and
+`omitted_count` the total: `not_captured` means the invocation holds no such
+record, so nothing is shown for it or its ancestors; `not_inlined` means it was
+captured but is not in this response. `complete` is true only when every cited
+record is in `records` or embedded in the observation. If examining the closure
+itself fails, `collection_failure` names the error type and the closure state is
+unknown; the original failure is still returned. Nothing is retained after exit,
+and running the command again is a new observation.
+
When the reader of piped output closes early, as `rea ... | head` does, the
CLI stops writing, prints no error and exits `141` (`128 + 13`, the status of a
SIGPIPE-terminated writer). The operation itself may have completed; only its
diff --git a/docs/client-configuration.md b/docs/client-configuration.md
index 3461bd6855..87f55ed530 100644
--- a/docs/client-configuration.md
+++ b/docs/client-configuration.md
@@ -45,6 +45,10 @@ the client's configured command or reproduce the client's environment. If doctor
passes but the client disconnects, inspect the client's startup error and registered
launcher; see [verification scope](client-verification.md#launch-environment-verification).
+`rea doctor` has the same limit for provider checks, and reports a provider setting
+the registration's file does not carry as a
+[non-blocking unknown](installation.md#check-readiness-for-your-task).
+
## Runtime settings and client deadlines
`update_analysis_settings` changes curated defaults inside the running REA
diff --git a/docs/evm-bytecode.md b/docs/evm-bytecode.md
index 0b4d97bd65..e215f0028e 100644
--- a/docs/evm-bytecode.md
+++ b/docs/evm-bytecode.md
@@ -28,6 +28,15 @@ The host must already provide util-linux `prlimit`, normally `/usr/bin/prlimit`;
no system tools and changes no user configuration. [Upstream provenance and
license](https://github.com/morluto/rea/blob/main/third_party/evmole/README.md) are packaged.
+Limiter discovery validates the selected path without launching it. A relative
+path or directory is reported as `not_configured`, a missing file as
+`executable_missing`, and denied access or another filesystem-probe failure as
+`runtime_unavailable`. Recovery names the selected path and
+`REA_EVM_PRLIMIT_COMMAND`; denied access asks for execute permission and
+filesystem access. The original admission or system code stays in diagnostics.
+An available filesystem probe does not verify the selected tool's version or
+util-linux compatibility, and REA does not substitute another command.
+
## Evidence semantics
- `encoding` is required: raw bytes or UTF-8 hex, with optional `0x` prefix and
diff --git a/docs/firmware-analysis.md b/docs/firmware-analysis.md
index 28d70d4f27..de61db9794 100644
--- a/docs/firmware-analysis.md
+++ b/docs/firmware-analysis.md
@@ -26,6 +26,9 @@ REA does not install these tools, their dependencies or system packages. It
checks the tool's `--version` banner when executing a request. Binwalk must
print `binwalk ` and Unblob must print the version alone. Inspection and
extraction have independent prerequisites; one does not require the other.
+Capability discovery recommends `REA_BINWALK_COMMAND` for missing inspection
+and `REA_UNBLOB_COMMAND` for missing extraction. Configuring the other engine
+does not enable the unavailable operation.
Both require util-linux `prlimit`, normally `/usr/bin/prlimit`; override its
absolute path with `REA_FIRMWARE_PRLIMIT_COMMAND` if needed. `rea doctor` does
not currently inspect firmware readiness. Engine selection/version checks run
diff --git a/docs/flutter-analysis.md b/docs/flutter-analysis.md
index 2d18753d63..7ffb31d2d6 100644
--- a/docs/flutter-analysis.md
+++ b/docs/flutter-analysis.md
@@ -109,9 +109,13 @@ server; older servers keep the complete request after schema discovery.
header check does not establish valid table contents or exported symbols.
- **`libflutter` facts** are per-ABI observations: byte size, SHA-256
digest, GNU build IDs from ELF-declared `PT_NOTE` / `SHT_NOTE` ranges,
- and any _labeled_ strings the build
- carries. Release engine builds often strip the `Dart VM version` string —
- absence is reported as `null`, not guessed. Android clang/LLVM toolchain
+ and Dart/toolchain strings the build carries. `dart_version` accepts the
+ labeled `Dart VM version:` form and the unlabeled Dart
+ `Version::String()` form (` () ()` with an
+ optional ` on "_"` suffix) that release engines embed. An
+ unlabeled match is a candidate based on its shape; it is not bound to a
+ Dart symbol and another string can match. `null` means no supported pattern
+ was found, not proof that no version exists. Android clang/LLVM toolchain
lines are reported verbatim as build provenance.
`build_id_candidates` preserves distinct IDs. `build_id` is populated only
when all declared note ranges were examined and one ID was found;
@@ -122,10 +126,13 @@ server; older servers keep the complete request after schema discovery.
Compressed `SHT_NOTE` sections are skipped with an unsupported-compression
limitation and partial coverage; IDs from other note ranges remain candidates.
Encoded note storage is never interpreted as note headers or inflated.
-- Labeled strings must fit their complete grammar. `dart_version_capped`
- and `toolchain_capped` independently report labels omitted by grammar or
- projection bounds; either makes identification coverage partial. Values
- split across read chunks are retained without publishing truncated prefixes.
+- Version and toolchain strings must fit their complete grammar.
+ `dart_version_capped` and `toolchain_capped` independently report labels
+ omitted by grammar or projection bounds; either makes identification
+ coverage partial. Values split across read chunks are retained without
+ publishing truncated prefixes. Unlabeled runs that miss the
+ `Version::String()` grammar are ignored and do not set
+ `dart_version_capped`.
- **`coverage: "partial"`** also appears when an ABI carries `libapp.so`
without `libflutter.so` (or vice versa).
- The APK itself is digested before parsing, so `target.sha256` binds the
diff --git a/docs/index.md b/docs/index.md
index 09a8845890..bd9cbc36a5 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -51,7 +51,6 @@ For runtime instrumentation, see the [Frida guide](./frida-instrumentation.md).
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Identify a desktop installation | [Runtime candidates, evidence and follow-up files](desktop-applications.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [PE TLS/load config](pe-load-config.md), [ELF notes](elf-notes.md), [Linux kernel modules](kernel-modules.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
-| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [PE TLS/load config](pe-load-config.md), [ELF notes](elf-notes.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Select a native provider or import format | [Ghidra semantics](ghidra-provider.md), [Windows](windows-ghidra-p0.md), [DOS](ghidra-dos.md), [raw bytes](raw-binary.md), [GameCube DOL](ghidra-dol.md), [MIPS](ghidra-mips.md), [PSP](ghidra-psp.md), [NativeAOT](ghidra-nativeaot.md), [IDA](ida-provider.md), [Kuna](kuna-provider.md), [JEB](jeb-analysis.md) |
| Analyze JavaScript or Electron artifacts | [Static analysis](javascript-artifact-reconstruction.md), [tracing and comparisons](javascript-application-workflows.md), [application graph](javascript-application-graph.md), [source recovery](javascript-recovery.md) |
| Compare static JavaScript with runtime observations | [Runtime attribution](javascript-runtime-observation.md), [reconciliation](javascript-runtime-reconciliation.md), [Electron observation](electron-observation.md), [Electron fuses](electron-fuses.md) |
diff --git a/docs/installation.md b/docs/installation.md
index 1378665340..a9ff7d110a 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -390,6 +390,20 @@ A scoped report has `scope.mode: "explicit"`. Its `scope_checks` determine
`environment_healthy` summarizes the full audit. `--target` adds a target check,
but the report remains audit-wide unless a scope option is also supplied.
+Provider checks read the terminal running doctor. A client-started server may
+receive a different environment, and doctor cannot observe it. When an available
+provider relies on a setting that a registration's file does not record, or
+records with other text, doctor adds a non-blocking
+`registration_environment:` check with `classification: "unknown"`. The
+setting names appear in that registration's `provider_settings`. Selecting the
+client or an affected provider places the check in `scope_checks`; it never
+changes `healthy`, because the client may still pass the setting on. Preview
+`rea setup --client --skill=false --dry-run` from that terminal and
+approve it to record the settings, then restart the client. To see what the
+running server can use, call `binary_session` in the client and read
+`result.tool_availability`. For Hopper, only an explicit `HOPPER_LAUNCHER_PATH`
+is compared.
+
An invalid `REA_LOG_LEVEL` fails every scope, including one client or the
installed skill, because the executable rejects it before any command. A
selected client also fails for every other setting that MCP startup rejects,
@@ -666,7 +680,8 @@ Hopper is separate commercial software with its own license. Its free demo has
vendor-defined limits, and a paid license is optional. REA reuses any detected
installation and preserves Hopper during uninstall.
-The automated Hopper installer supports macOS 12+ and x86_64 hosts running
+The automated Hopper installer supports macOS 12+, subject to the minimum
+macOS version of the current Hopper release, and x86_64 hosts running
Ubuntu 24.04+, Fedora 41+, Nobara 44+, Arch Linux, CachyOS, or Omarchy. The
official Linux packages are x86_64-only, so setup does not offer Hopper on other
Linux architectures; configure [Ghidra](#ghidra) there instead. REA itself runs on any
@@ -709,6 +724,15 @@ and digest, validates the application bundle, and atomically installs it to
operator can choose its demo mode or activate an existing license. Homebrew and
administrator access are not used.
+Each Hopper release declares its own minimum macOS version, so the setup plan
+cannot confirm support before approval. Setup compares this Mac's version with
+the release metadata's `deployment_target` before downloading the DMG, and with
+the signed application's `LSMinimumSystemVersion` before copying it. If this Mac
+is older, setup fails with `incompatible_host_version`, names both versions, and
+installs nothing. Update macOS, or install a Hopper release that supports this
+macOS version yourself and set `HOPPER_LAUNCHER_PATH`. An existing Hopper
+installation is not checked against the current release's minimum.
+
The Hopper launcher action used by REA creates a document from an executable;
its supported command-line interface does not attach to an already-open
document. While another live REA session owns the same target and loader
diff --git a/docs/javascript-artifact-reconstruction.md b/docs/javascript-artifact-reconstruction.md
index 88d97864a9..1dd3c99d58 100644
--- a/docs/javascript-artifact-reconstruction.md
+++ b/docs/javascript-artifact-reconstruction.md
@@ -876,8 +876,22 @@ and Evidence. If that expansion does not fit, the file node retains
`static_analysis` and any admitted `semantic_module_analysis` observations,
including their exact values and source locations. Use the public modules item
view to read them. The graph marks those unexpanded relationships as unknown.
-If cumulative result capacity is exhausted, analysis stops; inventoried but
-unexamined files remain present with explicit unknown scopes.
+
+The parent charges everything it retains against the heap that was free when
+analysis started, less a transfer reserve. Semantic facts and expanded
+relationships stop at half of that capacity, and a source refused there keeps
+its static facts with the refusal reported. Static facts can continue to three
+quarters of it while the live heap also has room for another copy of what it
+holds, so results charged for earlier sources do not cost later sources their
+static facts when the heap can still hold them. When that share runs short,
+relationships that were reserved but not yet expanded are given up before any
+static facts, and each affected file reports the omission.
+
+If static facts themselves exhaust that share, or the parent's live heap cannot
+hold the next checkpoint, analysis stops; inventoried but unexamined files remain
+present with explicit unknown scopes. The failure reports the limit that applied
+in bytes, what was still available, the live heap in use and what the checkpoint
+needed.
### Large local JSON inputs
@@ -1002,7 +1016,12 @@ 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.
Inline JavaScript in local HTML, meaning classic or module `