Compare commits

...
Author SHA1 Message Date
morluto b5f9874cfe test(kuna): bound retry lifecycle acknowledgement 2026-11-03 22:45:21 +08:00
morluto 5f588485fe fix(kuna): correct independent retry recovery 2026-11-03 22:45:21 +08:00
morluto 768a4c169d feat(kuna): allow independent decompile retries 2026-11-03 22:45:21 +08:00
morluto b55b0c317b test(native): retain source after export conflict 2026-11-03 22:37:18 +08:00
morluto 7c1bd003ec test(native): verify historical export source facts 2026-11-03 22:24:50 +08:00
morluto 22b5914c3d feat(native): retain decompile export source facts 2026-11-03 22:22:34 +08:00
Cursor Agent 8c857ce56c docs(readme): move Discord button below site links
Place the Join us on Discord button under each hero's website, guides,
and showcases line. The invite and alt text stay the same.
2026-11-03 16:56:44 +08:00
morluto 28bffe92c8 docs(readme): move Discord button below site links (#8755) 2026-11-03 16:51:45 +08:00
morluto 4f7c4cc218 docs(readme): add Join us on Discord button (#8754) 2026-11-03 16:45:28 +08:00
Liu Jie 4b961e3715 fix(cli): protect Amp settings during default registration (#8076) 2026-11-03 14:50:33 +08:00
Paritosh LahreandClaude Opus 5.5 8db538a7ca fix(browser): keep negative script columns Chrome reports (#8684)
* fix(browser): keep negative script columns Chrome reports

Chrome reported a Debugger.scriptParsed event with startColumn -60 and
endColumn -15 for an inline script in WordPress's helper frame. CDP declares
these columns as plain integers, but REA's admission required min(0), so one
script failed the whole observe-web-execution run with unreadable_output.

CDP resource columns now share one representation, cdpResourceColumnSchema,
for script start/end columns, initiator call frames, parser initiators and
event listeners. Positions on a script's first line include its start column,
so keeping the reported values keeps the documented script-relative
conversion consistent. The domain contract and web runtime docs state that a
negative column does not locate a column in the resource.

Fixes #8674

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(browser): apply the CDP column policy to passive capture

inspect_web_page and discover_webmcp_tools still read CDP columns through
cdpSourceCoordinates, whose own >= 0 check turned a negative column into
null and reported invalid_protocol_value. WebMCP discovery also dropped the
tool's whole registration_source.

cdpResourceColumnSchema now lives with the domain's CDP observation schemas.
Runtime admission, the passive coordinate helper and the runtime, console,
initiator and WebMCP output schemas all use it. Lines keep their
non-negative rule.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-03 14:50:00 +08:00
Wenyu Chiou e01fcfb4b2 fix(reference): Exclude Git pointer files from source identities (#7667)
* fix(reference): Exclude Git pointer files from source identities

Match .git pointer files in the default import policy after the existing
directory rules, preserving ordinary-checkout exclusion provenance and Git
HEAD discovery. Cover linked-worktree identities, repeated imports, nested
metadata, similarly named source files, and configured-secret precedence.

Independent review: parent independent review APPROVE for Draft CI on patch
61bddf108952c77858fa71c333b4bb8a05ab33e8b03f5c6bccd879cd19c7c379 and source
tree 2d2372a9ec. Review triggers: delegated
shipping output, source inventory/file I/O behavior, and more than 50 lines.

Validation: npm run check passed with Node 24.22.1/npm 11.16.0 on Windows;
tracked Vitest regression 1 passed/3 Windows skips; test discovery passed.
Eight offline Linux production imports passed on cached Node 22.19.0 as
supporting evidence only. Pinned Linux/macOS Vitest cases await hosted CI.

Fixes #7551

* test(reference): Isolate worktree fixtures from host Git settings

Route every fixture Git command through one wrapper with a filtered child
environment, owned empty global config/template/hook paths, and no system
config reads. Keep resource cleanup under the existing temporary owner.
These settings are scoped to synthetic child processes; host Git settings
and this repository's commit/push checks are unchanged.

Targeted independent review: parent review APPROVE on patch
4be9a2af3e40edac3d64f687170e966a87fcf762f1877f2c55016e120bcd90ac and tree
a2ba91ee9c. The reviewer confirmed the native
proof helper hash, complete fixture command coverage, and owned cleanup.
Review triggers: delegated follow-up and fixture file/process boundaries.

Validation: npm run check:fast passed; targeted Vitest 1 passed/3 native
Windows skips. On pinned Node 24.22.1, the actual helper rejects inherited
hooks/templates while commit, worktree creation, HEAD and status checks pass.
The unisolated control copies the injected template and its commit fails.

Refs #7551
2026-11-03 14:49:28 +08:00
Wenyu Chiou f5cfe09c07 fix(environment): disclose truncated comparison differences (#7673)
Preserve the twenty-entry diagnostic prefix and report the full count,
omitted count and truncation fact at the owning report projection. Document
the recovery limitation and cover the public HAR-to-comparison workflow.

Fixes #7621

Validation: 121 source tests; three selected CLI regressions (five other
cases unselected); nine README conformance cases; full --noEmit typecheck;
documentation facts/metadata/schema/licenses; module boundaries and
architecture guards. VitePress rendering is blocked by Windows EPERM on its
worktree-local Vue symlink. Full aggregate coverage remains for Draft CI.

Independent code review: APPROVE, relayed by parent task
01a17716-6299-75fe-87e9-b0398b8c9019. Trigger: >=50 changed lines / >=3 files.
Reviewed tree: ba964efcd6
Reviewed full-index patch SHA256:
972dcfc219dd7b9606175b1848068f92210fa3f7d0a8a244c1ac7ce296ae32d9
2026-11-03 14:48:54 +08:00
Beqaandmorluto f59d884f35 fix(flutter): read unlabeled Dart Version::String() from engines (#7232)
* fix(flutter): read unlabeled Dart Version::String() from engines

Release libflutter.so embeds Dart's Version::String() without the
standalone VM's "Dart VM version:" label, so identify_flutter_build left
dart_version null on real APKs. Accept that channel-bearing form beside
the labeled scanner path.

* fix(flutter): qualify unlabeled version candidates

---------

Co-authored-by: morluto <[email protected]>
2026-11-03 14:48:21 +08:00
ShuaixinHuang 4f4e2552cc fix(verification): compare retained Ghidra function evidence (#8672)
* fix(verification): compare retained Ghidra function evidence

* test(verification): require the exact missing Evidence refusal
2026-11-03 14:47:49 +08:00
Ha1baraA1 b3e22620a5 fix(mcp): keep scoped doctor output parseable (#7648) 2026-11-03 14:47:16 +08:00
Hasan Zayni 53fca98e3a fix(setup): omit manual connectors from automatic selection (#8009)
Retain detected manual connectors and explicit selection while excluding unsupported formats from automatic setup readiness. Fixes #7522.
2026-11-03 14:46:43 +08:00
Jeremy Levartovsky 5ed283d168 fix(har): accept Chrome DevTools bracketed IPv6 server addresses (#7225)
Chrome DevTools HAR exports declare creator WebInspector 537.36 and
report IPv6 serverIPAddress values in brackets: its HAR writer strips
the port with lastIndexOf(':') but its bracket removal,
replace(/\[\]/g, ''), only matches the literal pair '[]'. The pinned
schema then rejects the whole archive at the first such entry.

Extend the existing Playwright 1.58.2 adaptation to this declared
producer. Brackets are removed only in the disposable validation copy
and only around a valid IPv6 literal; reported spellings are retained
and the per-entry limitation names the declared producer.
2026-11-03 14:46:10 +08:00
Ha1baraA1 f251009e0a fix(artifacts): report required bytes for ASAR header limits (#7452) 2026-11-03 14:45:36 +08:00
Beqa d892e7861a fix(docs): drop duplicate native-inspection guide row (#7480)
Remove the incomplete Choose a guide duplicate that omitted kernel-modules.md so the docs index keeps one complete native-inspection entry.

Fixes #7405
2026-11-03 14:45:03 +08:00
morluto 9ebbe67e05 fix(ci): await website worker tests and format Wrangler config 2026-11-03 12:04:33 +08:00
Lewis WangandClaude Fable 5.1 b912ec184c fix(javascript): accept retained empty endpoint seeds (#6462)
Application analysis preserves the complete literal request endpoint,
including the empty string, and the event seed already accepts a
retained empty name. The endpoint seed still required a non-empty
value, so trace-javascript-semantics refused to select a retained ""
endpoint with invalid_request.

The endpoint seed now accepts any string and keeps exact matching.
Boundary-field seeds keep their minimum because boundary nodes are only
produced from method names.

Found while fixing #6458; the event half of that report landed in
cf5e4f6fa.

Co-authored-by: Claude Fable 5.1 <[email protected]>
2026-11-03 10:10:33 +08:00
morluto 193e01abb1 fix(kuna): keep timeout recovery from changing workers (#8687)
* fix(kuna): keep timeout recovery from changing workers

* test(kuna): verify timeout recovery with real provider
2026-11-03 10:10:11 +08:00
morluto d496d803a4 fix(kuna): give actionable lookup recovery (#8656)
* fix(kuna): give actionable lookup recovery

* fix(kuna): keep lookup advice operation neutral

* style(kuna): apply repository formatter
2026-11-03 09:50:10 +08:00
morluto 6259ac41a4 fix(mcp): guide target recovery for scoped tools (#8659)
* fix(mcp): guide target recovery for scoped tools

* test(mcp): cover unscoped target recovery

* fix(mcp): warn before scoped session restart

* refactor(mcp): own scoped target recovery guidance

* fix(mcp): include unknown history in restart guidance
2026-11-03 09:49:49 +08:00
morluto 79766ca96a fix(website): identify all public deployment verification requests 2026-11-03 09:41:49 +08:00
ShuaixinHuang 22c493ad5c fix(native): preserve implementation file locations (#8667)
fix(native): keep UI handler offsets distinct from method records
2026-11-03 09:38:12 +08:00
ShuaixinHuang b7911b2d30 fix(evm): explain limiter configuration and access failures (#8669) 2026-11-03 09:38:01 +08:00
JANG BYUNGKUN 6442ea02cb feat(binary-layout): admit ELF64 AArch64 and name the refused host (#8711)
* feat(binary-layout): admit ELF64 AArch64 and name the refused host

inspect_binary_layout refused every AArch64 ELF, and on a non-x64 host the
refusal read "supports Linux x64 only", which looked like a target refusal
(#8709 ran on arm64 WSL and suspected an e_machine check).

The pwntools adapter now admits little-endian ELF64 AArch64 EXEC/DYN/REL
objects alongside the existing profiles, pwntools must report aarch64/64/
little, and the Evidence subject names arm64. The host refusal now names the
detected platform/arch and says the target was not read; the host check
itself is unchanged.

verify:binary:layout cross-compiles an AArch64 executable and relocatable
object (never executed) and checks sections, symbols and relocations
against GNU readelf, decoding ELF64 r_info as a 32-bit type under a 32-bit
symbol index. The real-binary-layout workflow installs the toolchain.

Refs #8709

* test(binary-layout): refuse RISC-V as the unsupported machine and assert the host refusal path

verify:binary:layout used e_machine 183 (AArch64) as its unsupported
target, which this branch now admits; use EM_RISCV (243) instead.

The unsupported-host provider test now asserts the refusal names this
host's platform/arch and says the target was not read, so reverting the
decoder to the old message fails it.
2026-11-03 09:37:41 +08:00
morluto eef8f44003 feat(website): publish agent discovery and Markdown responses 2026-11-03 09:33:41 +08:00
ShuaixinHuang 510e83eadc fix(firmware): recommend the operation-specific engine (#8668) 2026-11-03 09:17:33 +08:00
2d11fd92d2 fix(javascript): reconcile Node targets with entry assets (#8670)
* fix(javascript): reconcile Node targets with entry assets

* test(inspector): read reconciled evidence payload

* Update tests/boundary/inspector/javascriptRuntimeNodeTarget.test.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* test(inspector): read the workflow evidence payload

* Update tests/boundary/inspector/javascriptRuntimeNodeTarget.test.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* test(javascript): restore Evidence workflow result access

---------

Co-authored-by: morluto <[email protected]>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-11-03 08:54:39 +08:00
morluto 9bea563e30 fix(kuna): qualify parallel project pseudocode (#8694)
* fix(kuna): qualify parallel project pseudocode

* test(kuna): cover parallel caveats through MCP
2026-11-03 03:30:19 +08:00
morluto e749160ab0 fix(mcp): qualify string byte-read guidance (#8704)
* fix(mcp): qualify string byte-read guidance

* fix(mcp): avoid implying complete string references
2026-11-03 03:29:59 +08:00
morluto 920d634e83 ci: exempt designated authors from readiness declaration 2026-11-03 02:18:55 +08:00
morluto 55078b9f57 feat(mcp): page retained incoming reference addresses (#8591) 2026-11-03 02:13:21 +08:00
morluto 75c3cab540 fix(kuna)!: separate verification policy from analysis identity (#8699)
* feat(kuna): verify release 1.989

* fix(kuna)!: separate verification policy from analysis identity

* docs(kuna): show historical evidence recovery

* test(kuna): cover verification warnings through MCP
2026-11-03 01:34:40 +08:00
morluto 3c05b4d3fe fix(ci): remove undeclared dependency and handle PTY chunks 2026-11-03 01:10:56 +08:00
morluto 2cb29693c3 fix(ci): preserve release proposal overflow notes 2026-11-03 00:21:16 +08:00
Kaoru0822andClaude Opus 5.5 26c3e9e413 fix(native): select off-screen windows so the UI lane can complete (#8654)
* fix(native): stop calling an off-screen window absent

The native UI helper looked up its selected window with
`.optionIncludingWindow`, which returned nothing while that window was off
screen. An existing window owned by the selected process was reported as
`window-mismatch`, before any permission check, so the verifier could not reach
its permission boundary. Select from all windows instead; absent and foreign
windows still fail.

The verifier's changed-bytes check also read `error.message`, but since
cc90771af a selection failure carries its helper code in `error.issues`, so the
check could not pass. Read the issue. Together these let the permissions lane
complete on main.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test(native): own UI test processes from the moment they launch

The off-screen test registered cleanup only after parsing the ready record, so a
startup failure left `/usr/bin/open` and its app running. The lane test killed
only the verifier on a timeout, leaving its launcher and detached fixture alive
until they expired.

Register the off-screen owner immediately after the launch; the app inherits the
run token, so the sweep needs no ready record. In the lane test, every stand-in
records itself and leaves on one release the test's cleanup awaits.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-02 23:57:32 +08:00
Kaoru0822andClaude Opus 5.5 144843ccc2 fix(debugger): let REA exit when a debugger could not be stopped (#8652)
* fix(debugger): let REA exit when a debugger could not be stopped

`rea debug-with-gdb` and `rea debug-with-rizin` printed cleanup_incomplete
and set exit status 1, but the retained child and its pipes kept the event
loop alive, so the process never exited. A stdio MCP server whose client
disconnected hung the same way.

closeOwnedDebuggerSessions is the owner both managers finish through. It now
unreferences retained sessions, as supervised providers already do after
incomplete cleanup, restores the references on retry, and names the retained
process IDs. The one-shot commands report those IDs, since no later command
can retry. Debugger ownership, signals and detach behavior are unchanged.

GDB cleanup requests also no longer hold the host until their 120 s command
deadline, because cleanup already bounds its own wait.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(debugger): name retained debuggers when an MCP server shuts down

An MCP server that exits with a retained debugger logged only "REA provider
cleanup failed" and "could not close cleanly", so nothing named the process
left for the caller to check. The PIDs were on OwnedDebuggerCleanupError, but
each shutdown reporter read only the outer aggregate's message, and the debug
failure_cause description dropped the field too.

The failure type now lives in its own dependency-free module with one reader
that walks composed failures. The shutdown report, the server's onclose log,
the debug description and the one-shot CLI all use it, and the shutdown report
writes the same remediation the CLI returns.

The fake debuggers now model each request exactly, with real reply shapes, and
a request they do not model fails the test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test(debugger): compare retained-debugger guidance with its formatter

The new checks froze the wording "Check process ... and its target".
docs/testing.md asks guidance checks to compare with the owning formatter,
alongside independent checks of the facts. They now do, and each still proves
the process ID is named, because a formatter comparison alone would pass if the
formatter dropped it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-02 05:16:34 -08:00
Kaoru0822andClaude Opus 5.5 4a01211f11 fix(javascript): keep static facts after semantic results use the parent budget (#8648)
Static checkpoints were admitted against the same budget as semantic and
application results, half of the parent's free heap. Once those charges used
it, the next checkpoint stopped the analysis and every remaining source lost
its static facts, with most of the parent heap still free.

Semantic and application results still stop at that budget. Static checkpoints
may now continue to three quarters of the free heap while the live heap also
has room for another copy of what it holds. Without that room, admission falls
back to the result budget. The live condition is needed because the limit V8
reports counts young-generation capacity that retained objects cannot use:
without it, the parent aborted on heap exhaustion at small heap limits.

Reserved relationships are given up only when the larger share runs short. A
stop still fails with a typed error, which now reports the limit that applied,
what was available, the live heap in use and what the checkpoint needed.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-02 05:16:16 -08:00
Kaoru0822andClaude Opus 5.5 6a0ccf7eeb fix(doctor): report provider settings a client registration does not record (#8645)
* fix(doctor): report provider settings a client registration does not record

Provider checks read the terminal running doctor, so a registration with no
env block looked ready while its server reported not_configured. Compare what
an available provider relied on with the registration's own file and report the
setting names in a non-blocking registration_environment check. The client's
effective environment stays unknown, so health and the exit status are unchanged.

Fixes #8202

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test(doctor): write the Cordis fixture at its literal path

The test resolved the path through supportedClients, whose default environment
argument CodeQL traces to the fixture's writes even though the test passes {}.
It only needed the path, so spell it like the other fixtures in this file and
drop a leftover directory it never used.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-02 05:14:50 -08:00
Kaoru0822andClaude Opus 5.5 710fad8acf feat(evidence): discover missing Evidence links without transferring records (#8633)
* feat(evidence): discover missing Evidence links without transferring records

An agent could only learn that a retained record's source was absent by reading
complete records or by having a downstream analysis refuse. The summary said
nothing about links, and complete-record retention was easy to mistake for
source closure.

- Add detail: links to get_evidence_bundle and CLI inspect-evidence-bundle:
  paged link rows with retained or missing status and no record payloads, for
  selected records against the whole session or file.
- Add retained-closure traversal, the walk composed workflows require, reporting
  complete or incomplete. Direct reads make no closure claim.
- Count direct and missing direct links in the summary.

Fixes #8454

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(evidence): count a repeated Evidence link once in every delivery

The Evidence schema does not deduplicate evidence_links, so the summary counted
a repeated link twice while link rows and counts reported it once. Both now read
one helper for a record's distinct direct links.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test(evidence): validate link delivery against the advertised schemas

The test never called tools/list, so neither the shared validator nor the MCP
client checked its replies against what each profile advertises. A schema that
rejected the links result could have passed. Fetch the advertised definition and
validate every request and reply against it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-02 05:14:34 -08:00
Kaoru0822andClaude Opus 5.5 93b8b82db7 feat(native): deliver batch decompile as a retained summary (#8631)
* feat(native): select retained batch decompile results through analysis views

A completed batch_decompile could only be read whole. When it did not fit the
MCP response budget, or an agent wanted one function, inspect_analysis_view
refused the parent as unsupported and the only recovery was exporting the
session.

- Own the batch result schema in the domain, replacing the contract's copy and
  the workflow's hand-written item types.
- Add summary and batch-decompile views: body-free rows keyed by original index
  with exact filters and opt-in bodies, and UTF-16 pages of one body.
- Report the decompiling provider from the upstream analysis profile, since the
  Evidence provider is the composing workflow.
- Keep batch limitations visibly aggregated rather than attributing them to one
  successful item.

Fixes #8498

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* feat(native): deliver batch decompile as a retained summary

A batch that fits still floods the caller's context with every body, and one
that does not fit only named an export. Selecting a body that cannot fit named
an export too, even though its text pages can.

- Accept detail: summary on batch_decompile: retain the complete batch first and
  return its counts, identities and sizes. The default stays complete, and the
  selection never reaches the Evidence parameters.
- Offer a ready-to-call body-free first page when a complete batch overflows.
- When a selected batch page overflows, continue from the same retained parent
  with fewer rows, then with one body's text pages.

Refs #8498

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(native): count every unavailable entry label in a batch summary

label_unavailable only counted failed lookups, so a batch whose provider
answered null for every label reported zero. The identity contract defines a
null name as an unavailable label in both cases.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(native): continue an oversized batch view from the caller's own source

A batch view read from an Evidence file or inline record advertised a
retained-evidence continuation for a parent the session never retained, so
following it failed. A view's continuation now repeats the caller's source and
only narrows the selection. An inline record offers none, because it cannot be
repeated within the reply it overflowed. Non-batch views skip the selection
parse, since delivery asks about every view it returns.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test(native): require advertised batch schemas through the shared validator

A local validator map dropped any tool without an output schema, so removing an
advertised schema would have left these contract tests green. Require both named
schemas and validate through createMcpSchemaValidation, as docs/testing.md asks.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(native): offer file view continuations on servers that retain nothing

A fixed-target server reads an Evidence file through inspect_analysis_view but
records nothing, so its overflow skipped the continuation and returned generic
CLI guidance. A view's continuation names the caller's source and needs no
session, so it no longer depends on recording. Retained references still do.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-02 00:50:42 -08:00
Kaoru0822andClaude Opus 5.5 3f4aff2f22 fix(cli): keep captured source Evidence when one-shot delivery never commits (#8632)
* fix(cli): keep captured source Evidence when one-shot delivery never commits

A completed observation that was cancelled at final close, failed cleanup, or
never reached bundle commit returned its evidence links but dropped the source
records the invocation had already captured. The caller could neither read those
records nor tell whether they were missing, captured or written.

Report the closure at publishCliAnalysisEvidence, the shared boundary every
--evidence_output command uses. details.source_closure carries authentic captured
records within 64 KiB and explicit omissions: not_captured for a link this
invocation holds no record for, and not_inlined for a captured record outside this
response. A committed bundle keeps its receipt instead, nothing is written after
cancellation, and successful delivery is unchanged.

Fixes #8589

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(cli): keep the typed failure when collecting source closure cannot finish

A captured source with a data-sized evidence_links array made
pending.push(...links) exceed V8's call-argument limit. On final-close
cancellation or a refused publication that RangeError replaced the original typed
failure, which is the one outcome this delivery step exists to preserve.

Walk the closure through a Set, which is a deduplicating breadth-first queue, so
no link becomes a call argument and each link costs one lookup however often it
repeats. undeliveredSourceClosure now owns authentication and never throws: if
examining the closure fails, it reports collection_failure with the bounded error
type and an unknown closure state, and the original failure is still returned.

Fault the final-close tests on the scope the command gave to matching, after
matching completed, and assert that close was reached, so a moved close fails
instead of passing on an earlier one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-02 00:50:19 -08:00
highmachs 8b89eee767 fix(native): retain ELF dynamic symbol visibility in export evidence and views (#8617)
Preserve the dynamic symbol's visibility attribute (default or protected) when extracting ELF dynamic exports, and expose it across complete Evidence and projected analysis views.

Historical Evidence records lacking visibility normalize safely to 'unknown', and projected views omitting visibility do not inject default values into partial row projections.

Fixes #8517
2026-11-02 16:47:37 +08:00
吴佳浩Alben 59f5502a40 fix(javascript): emit no event operation for a literal non-function listener (#8621)
* fix(javascript): emit no event operation for a literal non-function listener

`EventEmitter` validates a listener before registering or removing anything:

    if (typeof listener !== 'function')
      throw new ERR_INVALID_ARG_TYPE('listener', 'function', listener);

The throw is synchronous, before any listener exists, but the analysis reported a
`register` or `remove` operation for those calls.

Measured on Node 24.22.1:

    emitter.on(1)                      -> THREW ERR_INVALID_ARG_TYPE
    emitter.on("x", 1)                 -> THREW ERR_INVALID_ARG_TYPE
    emitter.on("x", null)              -> THREW ERR_INVALID_ARG_TYPE
    emitter.on("x", true)              -> THREW ERR_INVALID_ARG_TYPE
    emitter.on("x", {}) / ([])         -> THREW ERR_INVALID_ARG_TYPE
    emitter.once / addListener / prependListener / prependOnceListener /
    off / removeListener with a literal -> THREW ERR_INVALID_ARG_TYPE
    --- accepted ---
    emitter.on("x", () => {})          -> ok
    emitter.on("x", function () {})    -> ok

`rejectsEventListener` drops the operation only for an exactly readable literal
that cannot be a function. An identifier, a call result or a spread may still
supply a function and keeps its operation, and a class expression passes Node's
own `typeof` check.

`emit` and `removeAllListeners` are untouched: `emit` passes its remaining
arguments as data to listeners that already exist, and `removeAllListeners` takes
only an optional event name.

* fix(javascript): price a non-finite listener and keep a shifted spread

Two review findings on the event-listener guard.

**A non-finite numeric literal was missed.** Babel stores `1e999` as a
`NumericLiteral` holding `Infinity`, and the exact-value reader refuses to price a
non-finite number, so the guard never saw it:

    e.on("x", 1e999)  -> THREW ERR_INVALID_ARG_TYPE

A numeric literal is always a number and therefore never callable, so the guard now
decides on the literal node directly.

**A leading spread shifts the listener position.** `e.on(...args, 1)` does not pass
`1` as the listener: Node spreads the array and ignores the trailing argument once
the listener is satisfied. The guard read `arguments[1]` regardless, so it dropped a
real registration. It now leaves the position undecided when argument 0 is a spread.

Five regressions cover both forms; all five fail without the fixes.
2026-11-02 16:47:22 +08:00
Kaoru0822andClaude Opus 5.5 2ac6d3efe2 fix(process): re-read unreadable tokens before signaling and name the process (#8553)
* fix(process): re-read unreadable tokens before signaling and name the process

Darwin KERN_PROCARGS2 samples a process's argument length and stack top
before it reads the address space, and exec publishes the new length
before it copies the new strings out (xnu sysctl_procargsx,
exec_copyout_strings). A read during exec can return a zero-filled or
stale strings area, and exec keeps the PID and start time that bracket
the read. The final sweep already re-read such tokens, but the
pre-signal group check read each member's token once, so one transient
read of a live group member failed the whole cleanup.

Move the bounded re-read into one shared fact-reading helper and use it
for both the sweep and the pre-signal group check. The group check binds
re-reads to the generation it first observed, so a reused PID stays
unresolved; the sweep keeps classifying the PID's current process.

An unreadable-token failure now carries the native identity read after
the token and the summary names the candidate by its native name,
parent, group and start identity. The Darwin helper and Linux procfs
reader report the short native process name (p_comm/comm) for display.

Fixes #8337

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(process): refresh other pre-signal members after token re-read waits

The pre-signal group check re-read only members whose token was
unavailable, so a readable member kept the relation it had before the
retry waits. A member that moved to another process group during those
waits could be validated on its old group, and the old group signaled
while the moved member kept running.

After any retry wait, the shared re-read helper now reads every other
live pre-signal fact once more with the same generation binding. A moved
member then takes the existing changed-group path, which retains its
ownership evidence and fails cleanup. An inconclusive refresh replaces
the stale fact so it fails closed. Nothing changes when no re-read ran,
and the sweep keeps classifying current processes because every group
it finds is revalidated before signaling.

Also cover PID reuse that happens before the re-read starts, where both
re-read identities agree and only the first-read generation rejects the
replacement's token.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(process): keep known identity changes final across token re-reads

The pre-signal re-read treated a first read without a same-generation
identity as unbound and replaced it with the re-read. On Linux the first
read can already establish PID reuse (an ESRCH token whose exit check sees
another generation); the re-read then classified the replacement, so a
zombie replacement could let cleanup succeed.

A pre-signal check now re-reads only same-generation facts. Changed,
exited, exiting and foreign-token facts are final. Each attempt also
re-reads the group's owned members instead of one separate refresh after
the loop, so a member that moves groups or turns unreadable while another
waits is seen within the same bound. The caller's run token selects the
owned members, and the sweep keeps classifying current processes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-02 00:21:23 -08:00
Kaoru0822andClaude Opus 5.5 365c3245e8 fix(setup): refuse Hopper on macOS below the release's minimum (#8548)
* fix(setup): refuse Hopper on macOS below the release's minimum

Hopper setup planning accepted any macOS 12+ host as supported, and the
installer never compared the host with the selected release. The current
publisher-signed Hopper 6.6.1-demo requires macOS 12.4 (feed
deployment_target and the app's LSMinimumSystemVersion), so setup on
12.0-12.3 installed an app the host cannot run.

The installer now takes the minimum from the release that owns it: the
feed's deployment_target before the package download, and the signed
app's LSMinimumSystemVersion (decoded in-process after signature
verification) before copying. A host below either fails with the new
incompatible_host_version code and an installer detail naming both
versions; an incomparable app value is a bundle failure. Planning keeps
REA's own macOS 12 floor but reports support as unknown, since the
release minimum is read only after approval, and the plan discloses the
check. Info.plist decoding is shared with app-bundle resolution.

Fixes #8502

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test(setup): compare the macOS Hopper plan detail with its owning disclosure

The planning test froze an explanatory sentence from the setup plan. The
macOS install detail now lives in macHopperInstallDisclosure beside the
commands and origins the installer owns, the plan uses it, and the test
compares the plan with that constant while keeping its independent
support-state assertions. An installer test also pins that the signed
app's requirement is read only after both signature checks pass.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test(setup): compare macOS Hopper refusal details with their owners

The installer's refusal details were template literals inside
installMacHopper, so tests copied whole sentences. The installer now
formats them through exported macHopperHostVersionDetail and
macHopperIncomparableMinimumDetail. Tests compare each diagnostic and the
setup remediation with those owners and SetupInstallFailure's recovery,
and separately assert the URL, versions, setting name, failure code and
request order. The Info.plist type refusal is checked by error type, key
and path instead of a sentence fragment.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-02 00:05:30 -08:00
Kaoru0822andClaude Opus 5.5 aba56df689 fix(ghidra): reject uncommitted handshake reports before the first query (#8630)
The handshake check ran only inside startSession, which a query reached
only when the profile committed a startup input, for PE or for an existing
project. Any other session started lazily inside callTool, so analysis seeds,
disabled analyzers, extension results, MIPS soft-float storage or a RISC-V
calling convention that the profile did not commit were never checked and
the query returned its result.

Every query now runs the checked start first. The client shares one startup
attempt, so there is still one launch, and a provider restarted within the
session is checked again. That leaves startSession as the only start in the
provider client, so the PE data-type path reuses its session info.

Recovery guidance is unchanged: the former eager-start condition now selects
only whether a completed handshake counts as a running provider.

Fixes #8612

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-02 00:00:24 -08:00
Kaoru0822andClaude Opus 5.5 dec8d2d588 feat(native): select retained batch decompile results through analysis views (#8629)
* feat(native): select retained batch decompile results through analysis views

A completed batch_decompile could only be read whole. When it did not fit the
MCP response budget, or an agent wanted one function, inspect_analysis_view
refused the parent as unsupported and the only recovery was exporting the
session.

- Own the batch result schema in the domain, replacing the contract's copy and
  the workflow's hand-written item types.
- Add summary and batch-decompile views: body-free rows keyed by original index
  with exact filters and opt-in bodies, and UTF-16 pages of one body.
- Report the decompiling provider from the upstream analysis profile, since the
  Evidence provider is the composing workflow.
- Keep batch limitations visibly aggregated rather than attributing them to one
  successful item.

Fixes #8498

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(native): count every unavailable entry label in a batch summary

label_unavailable only counted failed lookups, so a batch whose provider
answered null for every label reported zero. The identity contract defines a
null name as an unavailable label in both cases.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test(native): require advertised batch schemas through the shared validator

A local validator map dropped any tool without an output schema, so removing an
advertised schema would have left these contract tests green. Require both named
schemas and validate through createMcpSchemaValidation, as docs/testing.md asks.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-01 23:58:54 -08:00
hhangandhhang 0e6943c9da fix(session): guide retained cleanup before reopening targets (#8628)
* fix(session): guide retained cleanup before reopening targets

* test(cli): isolate worker completion deadline from startup

---------

Co-authored-by: hhang <[email protected]>
2026-11-02 15:56:09 +08:00
hhangandhhang 20a282a181 fix(javascript): report SVG script references and skipped bodies (#8627)
Co-authored-by: hhang <[email protected]>
2026-11-02 15:55:56 +08:00
Kaoru0822andClaude Opus 5.5 f95bdbce76 fix(native): stop value traces at unconfirmed child cleanup (#8576)
After a validated seed, trace_native_values turned a cleanup-incomplete
failure from a call-target read or a followed callee dossier into a
plain-text unknown, kept dispatching reads, and returned successful
Evidence without the cleanup_incomplete status, cleanup reason or owned
resources.

The trace now classifies child failures with the shared stopsTraversal
predicate that call graph traversals use. A cancelled child still ends
the trace with its cancellation. A child whose provider cannot confirm
cleanup stops the trace: the provider's own error is the outcome, so its
resources and bound cleanup retry stay intact, and the work admitted
before the stop is returned as the trace's normal shape, with traversal
truncated by cleanup_incomplete and a located unknown at the stopping
read. That stop stays primary over a concurrent caller abort and over
the output-size check. Ordinary child failures remain unknowns.

MCP already retains such partials as workflow Evidence. The one-shot CLI
dropped them, so runDirectAnalysis now retains the partial workflow
Evidence on the failure through the same shared owner.

Fixes #8371

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-01 22:56:02 -08:00
gensokyo-dev 8250bf2320 perf(test): bind deferred runtime calls to advertised schemas (#8622) 2026-11-02 11:55:40 +08:00
morluto 00c4f44a1c fix(ci): repair verifier fixtures and formatting failures 2026-11-02 10:59:40 +08:00
gensokyo-dev d3ab0faf52 refactor(ci): parse readiness fences with a static expression (#8586) 2026-11-02 03:14:29 +08:00
gensokyo-dev 6d60db1951 fix(test): restrict storage fixture to loopback (#8582) 2026-11-02 02:52:20 +08:00
morluto 0354145a2a docs: clarify test audits and organize verification lanes 2026-11-02 02:21:40 +08:00
morluto 7bcefead69 test: strengthen and prune audited test coverage (#8573)
* test: strengthen and prune audited test coverage

* test: require delivered DeepSeek skill instructions

* test: keep schema fixture types private

* test: verify Gemini receives skill instructions

* test: bind Qwen skill evidence to its tool result

* test: report NativeAOT verifier checks precisely

* fix: verify Ghidra cleanup paths reliably

* fix: verify Command Code skill activation

* test: tighten cleanup and fixture checks

* test: verify packet capture evidence is recorded

* fix: report HTTP3 capture provenance accurately

* test: preserve resource coverage through MCP

* test: prune repeated assertions and retain cancellation identity

* fix: validate client verifier fixture paths

* test: skip Copilot verifier cases on Windows

* test: keep verifier setup target relative
2026-11-02 02:13:32 +08:00
gensokyo-dev ce9095d727 fix(test): contain browser fixture HTTP request targets (#8566) 2026-11-02 01:48:46 +08:00
ShuaixinHuang 46c13b9981 fix(electron): preserve runtime rejection and exception policies (#8542)
* fix(electron): preserve runtime rejection and exception policies

* fix(electron): retain rejection handlers for revoked proxies
2026-11-02 01:15:08 +08:00
Kaoru0822andClaude Opus 5.5 85013da0ce fix(cli): describe one-shot timeouts for an invocation whose session has ended (#8535)
* fix(cli): describe one-shot timeouts for an invocation whose session has ended

A one-shot CLI analysis that reached a provider deadline told its caller
that the Ghidra session remained usable and reported a runtime-editable
limit applying to the next operation, although the command had already
released its provider session and runtime.

The shared error projection now takes an explicit caller lifetime.
Providers keep guidance that holds only for a continuing caller (session
reuse, same-session runtime updates) apart from the request outcome, and
the presentation adds it only for a continuing caller. One-shot owners
(runDirectAnalysis, runProviderAnalysis, finishAnalysisCommand and the
JavaScript application command) project for the invocation: details.limit
reports scope rea-invocation, applies_at next_invocation and the launch
environment seed, and the remediation leads with that setting for a new
invocation. MCP output keeps its runtime-update and same-session guidance.

Covers the Ghidra decompile timeout, decompiler failure, regex stack
exhaustion and startup timeout, the shared regex deadline, the Kuna
decompile timeout and the Hopper startup timeout.

Fixes #8311

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(cli): project composed child failures for the caller lifetime

Cleanup composition embedded its primary and cleanup failures as
continuing-caller projections, and baked the primary's message and the
cleanup's remediation into the composed error. A one-shot command whose
Ghidra startup timed out while shutdown also failed therefore still
reported a rea-runtime limit and update_analysis_settings in
details.diagnostics.primary_error.

childFailureDiagnostics now snapshots each embedded child at composition
for both lifetimes when its projection depends on the caller, so cleanup
history keeps its composition-time state and the final projection selects
the caller's form. composedCallerGuidance re-attributes message and
remediation with their continuing-only parts kept separate. Error copies
and redaction keep the invocation diagnostics. The Hopper startup timeout
now carries its continuing guidance on the error like the other producers.

Invocation limits keep agent_editable and rea_restart_required, with
values that hold for a new invocation, so only scope and applies_at
change and environment is added.

In existing-project mode a decompile timeout or a cancelled session now
says the next session copies and opens REA_GHIDRA_PROJECT_PATH again
rather than repeating import and analysis.

Tests compare delivered guidance with the owning formatters and constants
instead of freezing prose.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-01 08:57:32 -08:00
Kaoru0822andClaude Opus 5.5 cbc69537cd fix(discovery): carry provider repair instructions into capability discovery (#8532)
* fix(discovery): carry provider repair instructions into capability discovery

Provider readiness reported only a failure description, so capability
discovery copied that description into tool_availability remediation and
dropped repairs the owners already had: the JEB discovery-deadline setting,
the ADB/Apktool execute-permission repair kept in diagnostics, and the IDA
and Kuna configuration recovery.

Unavailable ProviderAvailability now requires the owner's remediation
next to its reason, and readiness-derived unavailable capability
descriptors carry it too. Every producer supplies it (ADB, Apktool, JADX,
JEB, Ghidra, Hopper, IDA, Kuna, pcap, owned-process prerequisites, profile
rejection); the inventory uses it verbatim, falling back to the reason
only for static capability facts that declare no repair. ADB/Apktool
configuration failures own remediation as a field instead of diagnostics,
and execution refusals project it as remediation.action. JADX doctor reads
the availability's remediation instead of re-deriving it.

binary_session complete candidate availability and unavailable capability
groups expose remediation (CLI capabilities/providers too); summary omits
it and lists it in omitted_fields. The superseded IDA
configuration_remediation and windows_native_failure.remediation
diagnostics are removed.

Fixes #8506

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(discovery): keep owner repairs through provider selection and capability maps

Address review findings on the readiness-remediation contract:

- Provider selection rejections carry the availability owner's
  remediation, CLI/MCP error details project it, and an explicit
  selection refused by readiness uses it as remediation.action. IDA's
  unreadable registration and Ghidra's Windows readiness repair no
  longer degrade to generic selection advice now that the diagnostics
  copies are gone.
- Ghidra's Windows readiness and existing-project capability maps carry
  the same repair as the availability.
- pcap readiness keeps the limiter owner's repair (now a typed
  remediationAction) and falls back to Wireshark guidance only when the
  failure has none.
- ADB/Apktool repairs are owner constants that are complete sentences.
  The execution refusal keeps its previous message-plus-repair recovery,
  which the compiled CLI contract asserts.
- Regression tests compare with the owning formatter or constant and
  add public MCP and CLI coverage for an explicit IDA selection.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-01 08:57:11 -08:00
Kaoru0822andClaude Opus 5.5 eb39a07966 feat(evidence): read selected complete records without the whole bundle (#8528)
* feat(evidence): read selected complete records without the whole bundle

get_evidence_bundle could discover a record by exact ID, but reading it
required the complete canonical bundle: complete + filters was rejected,
and inspect_analysis_view refuses parents without a view, such as
annotations, pseudocode sources and native traces.

- Add detail: records, a page (offset, limit; default 0, 8) of the
  complete matching records in canonical bundle order. Records keep their
  original IDs, subjects, providers and readback; missing_evidence_links
  names direct links of the returned records that are not retained.
- The selection is labelled as historical and not an importable bundle;
  complete keeps returning the canonical bundle, and offset/limit
  require detail: records.
- Filters keep one owner shared by summary and records delivery, for the
  MCP session and the CLI's inspect-evidence-bundle.
- An oversized record page now advises a smaller limit or one
  evidence_id; whole-bundle overflow guidance mentions record reads.

Fixes #8307

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test(evidence): compare record-selection guidance with its owners

Assert the record-selection limitations and oversized-page guidance
against their owning constants, and check the reported limit facts
separately, instead of pinning explanatory phrases.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-01 08:56:58 -08:00
Kaoru0822andClaude Opus 5.5 4b840aa1fb fix(setup): report the failed Hopper request URL and HTTP status (#8526)
* fix(setup): report the failed Hopper request URL and HTTP status

Hopper download callers collapsed every transport failure into a bare
`ok: false`, so HTTP 404 and 503 from the macOS feed and DMG, the Linux
publisher feed, the public key and the launch-time package download produced
identical generic advice without the failed URL.

The shared downloader now returns a typed failure (url plus http_status,
timeout, network, response_too_large or url_refused) and keeps caller
cancellation as a rejection. Installers attribute it to the request
(release_feed, package or public_key); HopperReleaseError carries it, and
setup projects it once into `download` and a remediation naming the URL,
outcome and next step. Replaces the Linux-only "Package download returned
HTTP" special case.

Fixes #8503

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(setup): refuse credentialed Hopper URLs and keep known HTTP outcomes

Native Fetch rejects a URL with userinfo using a TypeError that echoes the
whole URL, so a credentialed redirect reached `download.detail`, setup
remediation and publisher messages as a network failure. The downloader now
refuses request and redirect URLs carrying credentials as
`url_refused: credentials` before requesting them, and reports the caller URL
through the shared userinfo-removing `sanitizeBrowserUrl`.

Releasing an unadmitted body no longer replaces the outcome it was released
for: an errored (truncated) body after HTTP 503, or a failed cancel at the
size limit, keeps `http_status` or `response_too_large`.

Tests compare delivered remediation with the owning formatters and assert
request, URL, status and recovery class independently.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-01 08:56:38 -08:00
Kaoru0822andClaude Opus 5.5 423059a4de feat(javascript): page retained feature traces through analysis views (#8522)
* feat(javascript): page retained feature traces through analysis views

A bidirectional trace_application_feature result can exceed one MCP
response, and inspect_analysis_view refused trace parents as unsupported,
so the only recovery was exporting the whole session.

- Add a shared feature-trace view owner: a summary with the trace's
  identity, producer counts and coverage, and pages of paths, nodes,
  edges, seed_matches and native_handoffs in retained order with exact
  filters. Node rows are compact unless observations are requested.
  Page coverage never upgrades trace coverage.
- Accept an optional MCP view on trace_application_feature that retains
  the complete trace before returning the selected facts; the CLI keeps
  complete JSON and reads saved traces with inspect-analysis-view.
- Offer a ready-to-call first paths page when a retained complete trace
  exceeds the MCP response budget.
- Share the distinct authority/state/confidence basis helper with
  module references.

Fixes #8296

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(javascript): name the retained parent when a selected view overflows

Review follow-up for the feature-trace views.

- Share one delivery owner between trace_application_feature and
  compare_source_to_bundle: stage the selected view against the
  completed parent, keep the established contract that a cancelled
  request commits no session facts, then commit sources and the parent.
- A selected view that exceeds the response budget now references the
  retained parent and offers the same selection at half its limit,
  instead of naming the view record, which cannot be a view parent.
- Cover two native handoffs paged one at a time, the oversized selected
  page, and cancellation after the trace completes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(javascript): bind feature-trace pages to their collection rows

Advertise each feature-trace page with exactly its own row schema, so a
paths page cannot validate with node, edge, seed-match or handoff rows.
The native-trace view in #8533 received the same review finding; the two
sibling views now share one strict page contract.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test(javascript): mix valid rows from another collection into a trace page

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-01 08:56:05 -08:00
Kaoru0822andClaude Opus 5.5 44b6384009 fix(native): find procedures by the secondary names exact lookup accepts (#8520)
* fix(native): find procedures by the secondary names exact lookup accepts

search_procedures matched only each procedure's primary name, while exact
lookup also accepts Kuna inventory aliases (versioned-symbol and generated
sub_ names) and Ghidra symbols at an exact function entry. A search for a
name procedure_address resolves returned nothing, and trace_feature
silently missed the procedure.

Both providers now match the same names their exact lookup accepts. A
procedure still appears once under its primary name; the optional
matched_aliases field lists the secondary names that matched, so aliases
never become procedures of their own and list_procedures is unchanged.
The shared domain helper owns that row rule for Kuna and for the Ghidra
host-side Unicode folding pass. The Ghidra analysis profile records the
new search semantics so older snapshots cannot replay alias-blind results.

Fixes #8330

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(ghidra): match leaf spellings of procedure names in search

procedure_address accepts each entry symbol's leaf or namespace-qualified
spelling, but procedure search tested only qualified names, so a regex
such as ^audit_alias$ missed a secondary symbol ns::audit_alias (and ^same$
missed a primary alpha::same) that exact lookup selects.

Lookup and search now share one bridge definition of the names that select
a procedure, so they cannot drift. Matching secondary symbols are still
reported by their qualified names; because a leaf ends its qualified
spelling, the host's Unicode literal pass stays exact over those names.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-01 08:53:29 -08:00
Kaoru0822andClaude Opus 5.5 3f61303939 fix(session): clear provider-inferred unavailability after recovery (#8518)
* fix(session): clear provider-inferred unavailability after recovery

A provider adapter or process failure marked every routed operation of
that provider unavailable, but a later success cleared only the operation
that succeeded. Sibling operations, and composed tools built on them, kept
advertising the stale failure until each was retried individually.

Move runtime availability into one owner that records whether each entry
was observed for the operation or inferred from another operation's
provider-wide failure. A success clears its own failure and that provider's
inferred entries while keeping failures observed on other operations or
providers and declared refusals. Outcomes are ordered by dispatch to the
provider client, so an older in-flight success no longer erases a newer
failure while a request queued behind a failure still clears it.

Fixes #8404

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(session): keep newer successes when older failures complete last

Runtime availability kept one merged entry per operation and forgot
successful dispatches. An older provider-wide failure that completed after
a newer success re-inferred unavailability for siblings the newer success
had already proved working, and a superseded refusal could survive its own
later success.

Record only the newest failure and newest success dispatch per operation
and per provider, and project unavailability from them: an operation is
unavailable while its own newest failure is newer than its own newest
success, or, as inferred, while its provider's newest provider-wide failure
is newer than any success from that provider. The projection no longer
depends on reply completion order.

The recovery tests now bound held-reply acknowledgements, release and join
every held call in finally, and compare remediation with the owning
formatters instead of copying their wording.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-01 08:53:11 -08:00
Kaoru0822andClaude Opus 5.5 35ae7232bf fix(enhanced): stop call graph traversals at unconfirmed provider cleanup (#8516)
* fix(enhanced): stop call graph traversals at unconfirmed provider cleanup

A relationship read whose provider could not confirm cleanup of resources it
owns was projected into a graph node or call-path failure, the traversal kept
dispatching queued reads, and get_call_graph / trace_call_path returned
success with the cleanup_incomplete status buried inside the result.

Both traversals now read relationships through one shared owner,
readCallRelationships, which classifies a cancelled read or a read with
unconfirmed owned cleanup as terminal. On such a cleanup stop the workflow
returns the provider's own error as the outcome, so its cleanup owner, retry
binding and live resolution stay intact, and keeps the work admitted before
the stop as the workflow's normal result shape with a caveat naming the
stopped procedure and the unexpanded count. The composed-workflow layer
turns that admitted result into partial workflow Evidence on the failure,
and the MCP adapter retains it through the shared partial-error delivery.
Ordinary read failures still produce successful partial results.

Fixes #8369

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(enhanced): retain every carried partial Evidence on enhanced failures

Enhanced-tool failures used the shared partial-error delivery only when this
PR's workflow partial was attached. A failing read that already carried its
own partial Evidence, and analyze_function dossier failures, were projected
inline and never recorded with the session writer, so readback and bundle
export could not recover them. Every enhanced execution failure now goes
through one delivery owner that uses toPartialErrorToolResult with the
session writer; it still falls back to inline when no writer or no valid
Evidence exists.

Tests compare the traversal caveat against its owning formatter instead of
copying the sentence.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-01 08:52:53 -08:00
Kaoru0822andClaude Opus 5.5 76cd23d2e8 fix(process): remove unused Darwin helper cache type export (#8511)
Scheduled deep static checks on main fail knip because the
DarwinHelperCache type alias added in #8318 has no consumer. Remove
the alias; callers already use createDarwinHelperCache's inferred
return type.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-01 08:52:42 -08:00
morluto 3e6a5cb20a fix!: preserve address context input errors (#8419)
* fix: retain address context cleanup failures

* fix!: preserve address context input errors

BREAKING CHANGE: Strict clients that cached the previous full inspect_address_context output schema must refresh tools/list and accept the typed error field on unavailable facets.

* test: use session contract for address context
2026-11-02 00:52:27 +08:00
morluto 1d343a88e5 feat(kuna): verify release 1.988 (#8509) 2026-11-02 00:25:06 +08:00
morluto df25bc8068 fix(ghidra): retain relocation symbol observations (#8531)
* fix(ghidra): retain relocation symbol observations

* test(ghidra): verify named relocation symbols through CLI

* fix(ghidra): clarify verification and supervise test commands

* test(ghidra): share load verification guidance
2026-11-01 23:53:56 +08:00
598 changed files with 26206 additions and 4204 deletions

No files matched your search

@@ -377,8 +377,9 @@ unless the group overrides them. The grouped name union is the complete tool
inventory; use `tools/list` for canonical tool schemas. The default summary keeps
each provider candidate's availability and target-support status, code and
reason; `omitted_fields` lists what it leaves out. Request `{"detail": "complete"}`
only when you need provider diagnostics, candidate capability sets, or tool
surface, effect and annotation flags; in those closed flag vocabularies, listed
only when you need an unavailable candidate's remediation, provider
diagnostics, candidate capability sets, or tool surface, effect and annotation
flags; in those closed flag vocabularies, listed
flags are true and omitted known flags are false. Provider candidate facts
describe possible providers, while top-level session capabilities describe the
current route and `analysis_provider_binding` names the selected provider. Do
@@ -431,7 +432,10 @@ output also streams without a single MCP frame.
When the retained parent ID is unknown, use `get_evidence_bundle` with
`detail: "summary"` and exact filters for operation, target SHA-256,
analysis-profile digest or canonical procedure address. Select its returned
Evidence ID for the next view instead of requesting the complete bundle.
Evidence ID for the next view instead of requesting the complete bundle. When
that record has no supported view (for example an annotation), read it with
`detail: "records"` and an `evidence_id` filter; `missing_evidence_links`
names linked sources this session does not retain.
`retention: "complete-record"` means the whole record is stored; native
value-flow truncation and unknown metadata remain separate. For an exported
portable bundle, use `rea inspect-evidence-bundle` with JSON containing `path`,
@@ -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.
@@ -169,7 +169,10 @@ leaving it, and `both` when the question needs both. Trace traversal includes
containment relationships, so even a narrow seed can expand many nodes; a
bidirectional trace can reach unrelated sibling modules. A module item or page
is preferable when its observations already answer the question. Do not treat
all reached nodes as participants in the feature.
all reached nodes as participants in the feature. For a broad trace, pass
`view: {"kind": "feature-trace", "collection": "paths"}` (or `summary`) to
retain the complete trace and read terminal paths first; page `nodes` or `edges`
with exact `where.node_ids` through `inspect_analysis_view` as needed.
Reuse retained parent Evidence when the server advertises it, or supply complete
Evidence inline. Include Hopper or Ghidra Evidence only when its artifact digest
@@ -346,8 +346,9 @@ the complete tool inventory grouped by current status. Expand each group's
overrides them; availability, reasons, remediation, and modes remain inline.
Provider candidates keep availability and target-support status, code, and
reason. `omitted_fields` names what the summary leaves out; `{"detail":
"complete"}` adds provider diagnostics, candidate capability sets, and tool
surface, `effects`, and `annotations`. Those arrays name true flags from fixed
"complete"}` adds each unavailable candidate's `remediation`, provider
diagnostics, candidate capability sets, and tool surface, `effects`, and
`annotations`. Those arrays name true flags from fixed
vocabularies, so an omitted known flag is false. `tools/list` remains the
canonical source for full tool schemas when target or provider state changes.
+2 -1
View File
@@ -106,7 +106,8 @@ limits, security/privacy/containment implications, and meaningful proof gaps. --
## Author readiness
<!-- The human owner completes these declarations before requesting review.
<!-- The human owner completes these declarations before requesting review,
unless the PR author is exempt as listed in CONTRIBUTING.md.
The PR author is the owner by default. A different owner must explicitly accept
ownership on this PR.
AI agents must leave these declarations to a human to dictate to them or there could be catastrophic consequences for many users of REA.
+2
View File
@@ -807,6 +807,8 @@ jobs:
tests/boundary/mcp/nativeDemangleMcp.test.ts
tests/boundary/mcp/nativeMachoSummaryMcp.test.ts
tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts
tests/boundary/providers/native/nativeUiOffscreenWindow.test.ts
tests/boundary/process/nativeUiVerifierLane.test.ts
tests/boundary/cli/nativeSignatureCli.test.ts
tests/boundary/cli/nativeMachoArm64_32Cli.test.ts
tests/boundary/cli/nativeMachoEncryptionCli.test.ts
+3 -3
View File
@@ -55,12 +55,12 @@ jobs:
- uses: ./.github/actions/portable-build-cache
with:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
- name: Install ELF32 cross toolchains for source-owned fixtures
- name: Install AArch64 and ELF32 cross toolchains for source-owned fixtures
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends \
gcc-i686-linux-gnu gcc-arm-linux-gnueabi gcc-mips-linux-gnu gcc-mipsel-linux-gnu \
libc6-dev-i386-cross libc6-dev-armel-cross libc6-dev-mips-cross libc6-dev-mipsel-cross
gcc-aarch64-linux-gnu gcc-i686-linux-gnu gcc-arm-linux-gnueabi gcc-mips-linux-gnu gcc-mipsel-linux-gnu \
libc6-dev-arm64-cross libc6-dev-i386-cross libc6-dev-armel-cross libc6-dev-mips-cross libc6-dev-mipsel-cross
- name: Acquire exact upstream libraries in an isolated runner environment
shell: bash
run: |
+4
View File
@@ -21,6 +21,10 @@ jobs:
python-version: "3.12"
cache: pip
cache-dependency-path: website/requirements.txt
- name: Set up Node.js for the website Worker checks
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
- name: Install sharing-image dependencies
run: |
sudo apt-get update
+50 -8
View File
@@ -54,6 +54,10 @@ jobs:
python-version: "3.12"
cache: pip
cache-dependency-path: website/requirements.txt
- name: Set up Node.js for the website Worker checks
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
- name: Install sharing-image dependencies
run: |
sudo apt-get update
@@ -75,10 +79,21 @@ jobs:
PY
- name: Configure Pages
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
# upload-pages-artifact excludes dot directories, including .well-known.
- name: Package the website including agent discovery
run: |
tar --dereference --hard-dereference --directory website/_site \
--exclude=.git --exclude=.github -cf "$RUNNER_TEMP/artifact.tar" .
tar -tf "$RUNNER_TEMP/artifact.tar" \
./.well-known/ard.json ./.well-known/ai-catalog.json \
./.well-known/agent-skills/index.json > /dev/null
- name: Upload the website
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
path: website/_site
name: github-pages
path: ${{ runner.temp }}/artifact.tar
retention-days: 1
if-no-files-found: error
deploy:
needs: prepare
@@ -130,14 +145,24 @@ jobs:
PUBLISH_TARGET: ${{ inputs.target }}
run: |
python3 -u - <<'PY'
import hashlib
import json
import os
import sys
import time
from urllib.error import URLError
from urllib.parse import urljoin
from urllib.parse import urljoin, urlsplit
from urllib.request import Request, urlopen
def open_public(url, accept=None):
headers = {
"Cache-Control": "no-cache",
"User-Agent": "REA-Website-Verifier/1.0 (+https://github.com/morluto/rea)",
}
if accept:
headers["Accept"] = accept
return urlopen(Request(url, headers=headers), timeout=10)
expected = os.environ["GITHUB_SHA"]
pages_url = os.environ["PAGES_URL"]
if not pages_url:
@@ -153,11 +178,7 @@ jobs:
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:
with open_public(url) as response:
actual = json.load(response).get("commit")
if actual == expected:
print(f"{name} serves {expected}: {base}")
@@ -172,6 +193,27 @@ jobs:
time.sleep(5)
if pending:
sys.exit("Could not verify published revision on: " + ", ".join(pending))
for name, base in hosts.items():
with open_public(urljoin(base, ".well-known/agent-skills/index.json")) as response:
skill, = json.load(response)["skills"]
# Fetch from each selected host, including the /rea/ mirror.
artifact_url = urljoin(base, urlsplit(skill["url"]).path.lstrip("/"))
with open_public(artifact_url) as response:
digest = "sha256:" + hashlib.sha256(response.read()).hexdigest()
if digest != skill["digest"]:
sys.exit(name + " serves a skill archive with the wrong digest")
for manifest in ("ard.json", "ai-catalog.json"):
with open_public(urljoin(base, ".well-known/" + manifest)) as response:
if not json.load(response).get("entries"):
sys.exit(name + " serves an empty discovery manifest")
if name == "Cloudflare":
with open_public(base, accept="text/markdown") as response:
vary = {value.strip().lower() for value in response.headers.get("Vary", "").split(",")}
if response.headers.get_content_type() != "text/markdown" or "accept" not in vary:
sys.exit("Cloudflare did not negotiate Markdown with Vary: Accept")
if 'rel="ard"' not in response.headers.get("Link", ""):
sys.exit("Cloudflare is missing its discovery Link header")
print(name + " serves the discovery manifests and verified skill archive")
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")
+2 -2
View File
@@ -128,8 +128,8 @@ owner to complete. Never attest to human review on their behalf. Draft status
is optional; preserve the PR's current draft status unless the user asks to change
it. Later pushes do not invalidate the readiness declaration or require another
human sign-off. Keep the PR body and affected validation current as work changes.
The readiness check validates
the filled declaration; CI and automated review results do not establish that
The readiness check validates the filled declaration or the author exemption
listed in CONTRIBUTING.md; CI and automated review results do not establish that
human review occurred, maintainer scope approval, or merge permission.
<!-- BEGIN:turborepo-agent-rules -->
+8 -4
View File
@@ -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
+2
View File
@@ -20,6 +20,8 @@
**[Website](https://rea.tools/) · [Guides](https://rea.tools/guides/) · [Showcases](https://rea.tools/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Join the REA community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[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)
<code>npx rea-agents setup</code>
+1 -1
View File
@@ -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.986/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.986/NOTICE) | Bring your own `kuna`; REA verified 1.986 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. |
+60 -21
View File
@@ -693,6 +693,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
}
row.addProperty("status", relocation.getStatus().toString());
row.addProperty("type", relocation.getType());
row.add("symbol", GSON.toJsonTree(relocation.getSymbolName()));
row.add("values", GSON.toJsonTree(relocation.getValues() == null ? new long[0] : relocation.getValues()));
byte[] original = relocation.getBytes();
row.add("original_bytes_hex", original == null ? JsonNull.INSTANCE : GSON.toJsonTree(bytesHex(original)));
@@ -2274,9 +2275,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
throw new RequestFailure("invalid_request", "mode must be literal or regex");
}
boolean caseSensitive = requireBoolean(params, "case_sensitive");
List<InventoryItem> inventory = procedureSearch
? procedures().stream().map(FunctionEntry::item).toList()
: strings();
ValueMatcher matcher;
if (mode.equals("literal") && !caseSensitive) {
if (!params.has("literal_ascii_candidate"))
@@ -2290,7 +2288,26 @@ public final class ReaGhidraBridge extends HeadlessScript {
: regexMatcher(expression, caseSensitive);
}
JsonArray items = new JsonArray();
for (InventoryItem item : inventory) {
if (procedureSearch) {
// Search the names exact lookup accepts, keeping one row per
// procedure under its primary name.
for (FunctionEntry entry : procedures()) {
monitor.checkCancelled();
ProcedureNameMatch match = matchProcedureNames(entry, matcher);
if (!match.matched()) {
continue;
}
JsonObject item = resultItem(entry.item, null);
if (!match.aliases().isEmpty()) {
JsonArray aliases = new JsonArray();
for (String alias : match.aliases()) aliases.add(alias);
item.add("matched_aliases", aliases);
}
items.add(item);
}
return items;
}
for (InventoryItem item : strings()) {
monitor.checkCancelled();
if (!matcher.matches(item.value)) {
continue;
@@ -2300,6 +2317,36 @@ public final class ReaGhidraBridge extends HeadlessScript {
return items;
}
/**
* The one definition of the names that select a procedure, shared by exact
* lookup and search: its primary name and every other symbol at its exact
* entry, each in leaf or namespace-qualified spelling. A label inside the
* body never names the procedure. Matching secondary symbols are reported
* by their qualified names, sorted.
*/
private ProcedureNameMatch matchProcedureNames(FunctionEntry entry, ValueMatcher matcher) throws Exception {
Function function = entry.function;
boolean primary = spellingMatches(function.getName(), entry.item.value, matcher);
Set<String> aliases = null;
for (Symbol symbol : currentProgram.getSymbolTable().getSymbols(function.getEntryPoint())) {
monitor.checkCancelled();
String qualified = symbol.getName(true);
if (!spellingMatches(symbol.getName(), qualified, matcher)) continue;
if (qualified.equals(entry.item.value)) {
primary = true;
continue;
}
if (aliases == null) aliases = new TreeSet<>();
aliases.add(qualified);
}
return new ProcedureNameMatch(primary, aliases == null ? List.of() : List.copyOf(aliases));
}
/** A leaf is the suffix of its qualified spelling, so a literal found in it is found in both. */
private static boolean spellingMatches(String leaf, String qualified, ValueMatcher matcher) {
return matcher.matches(qualified) || (!leaf.equals(qualified) && matcher.matches(leaf));
}
private List<InventoryItem> names() throws Exception {
if (nameInventory != null) {
return nameInventory;
@@ -2402,26 +2449,13 @@ public final class ReaGhidraBridge extends HeadlessScript {
// exact symbol name, even when every character is a hexadecimal digit.
boolean explicitAddress = isExplicitReaAddress(value);
if (!explicitAddress) {
// Imported labels (for example Mach-O _main) can share a function
// entry without becoming its primary symbol; search accepts the same.
ValueMatcher selector = value::equals;
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 (matchProcedureNames(entry, selector).matched()) matches.add(entry.function);
}
if (matches.size() > 1)
throw new RequestFailure("ambiguous", "Ghidra procedure name is ambiguous: " + value +
@@ -5138,6 +5172,11 @@ public final class ReaGhidraBridge extends HeadlessScript {
private record AddressNameConflict(Namespace namespace, String leaf, List<Address> requesters, List<Address> holders) {}
private record AddressNameFailure(String reason, AddressNameConflict conflict) {}
private record FunctionEntry(Function function, InventoryItem item) {}
private record ProcedureNameMatch(boolean primary, List<String> aliases) {
boolean matched() {
return primary || !aliases.isEmpty();
}
}
private record FunctionAnalysisObservation(JsonObject dossier, List<String> limitations, JsonObject relationshipCoverage, boolean decompilerFailed) {}
private record CallBoundaryObservation(List<String> limitations, Integer unrepresentedCalleeCalls, JsonArray unresolvedCalls) {}
private record SwiftStringLiteralDecode(JsonArray literals, JsonArray omissions) {}
+4 -1
View File
@@ -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,
+15 -1
View File
@@ -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
)
}
+4 -4
View File
@@ -16,10 +16,10 @@ PROFILE = "[email protected];[email protected];[email protected]"
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):
+4 -4
View File
@@ -257,12 +257,12 @@ beyond already-connected devices (`adb connect`) are not exposed yet.
## Real verification
`npm run verify:adb` exercises every operation against a live device. It
`npm run verify:adb` exercises observation and transfer operations against a live device. It
requires `adb` (or `REA_ADB_PATH`) and any attached emulator or phone;
`--serial` selects one when several are attached, `--pull PACKAGE` pulls a
real package and verifies the returned digests against the files on disk,
and `--install-apk PATH` drives a full install → resolve → uninstall
lifecycle with a lane-owned probe.
real package and verifies the returned digests against the files on disk.
The optional `--install-apk` lifecycle is unavailable until the selected APK
identity and device cleanup ownership can be established safely.
Record: verified against adb 34.0.5 on Linux against an Android 14
(API 34) x86_64 emulator — all operations above, a 224-package inventory,
+6 -1
View File
@@ -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 |
| --------------- | ------------------------------------------------------------------------------------------------------------ |
+9 -5
View File
@@ -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
+1 -1
View File
@@ -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.
+31
View File
@@ -561,6 +561,25 @@ be verified. See [MCP contracts](mcp-contracts.md) for session lifecycle and
[Ghidra first-query deadlines](mcp-contracts.md#ghidra-first-query-deadlines-and-recovery)
for import, client timeouts and recovery.
A one-shot command releases its provider session and REA runtime before it
prints a failure, so it omits the same-session reuse and
`update_analysis_settings` guidance an MCP connection receives. Its
`details.limit` reports `scope: "rea-invocation"` and
`applies_at: "next_invocation"`; set the named `environment` (or the per-call
`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
@@ -1286,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
+4
View File
@@ -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
+22 -1
View File
@@ -222,6 +222,25 @@ Error copying, attribution, redaction, and delivery must preserve these facts.
Human-readable remediation explains the same failure; clients should not need
to parse that sentence to identify an editable setting.
`scope` and `applies_at` describe a continuing caller, such as an MCP
connection. A one-shot invocation, such as a CLI command, has released its
provider session and exited its runtime before its caller reads the failure,
so its adapter projects with an explicit `"invocation"` caller lifetime.
`details.limit` then keeps every other field and reports
`scope: "rea-invocation"`, `applies_at: "next_invocation"` and the launch
`environment` seed when one exists. `agent_editable: true` still holds, because
the caller chooses that environment value or per-call `parameter` for the new
invocation; `rea_restart_required: false` means no running REA process needs a
restart. A producer keeps guidance that holds only while its caller retains the
session, such as "the Ghidra session remains usable" or a same-session runtime
update, in `continuingCallerGuidance`; the shared presentation adds it only for
a continuing caller. The producer's own message and remediation must stay true
after the session is released. A composed error records each embedded child
failure through `childFailureDiagnostics`, which snapshots it at composition
for both lifetimes when they differ, so cleanup history keeps its state and
the final projection selects the caller's form. Failures retained inside
Evidence keep the continuing form, so CLI and MCP Evidence stay identical.
Cache policy is part of enforcement. Successful reusable analysis can survive
a budget update. Transient Ghidra decompile failures remain uncached so a raised
deadline can recover on the same Program. Admission checks still apply to reused
@@ -286,7 +305,9 @@ whether live repair is possible.
## What agents do
Use `details.limit` for a direct runtime update when it names an editable
setting. Follow `remediation.action` for per-call, fixed and external controls.
setting with `scope: "rea-runtime"`. For `scope: "rea-invocation"`, set its
`environment` or `parameter` for a new invocation instead.
Follow `remediation.action` for per-call, fixed and external controls.
Inspect settings only when bounds or application details are missing. Restart only when it says the value is
read at startup. A sentence that a control does not raise the limit is not a
request to change it.
+3
View File
@@ -201,6 +201,9 @@ neither result means the requested host operation occurred. The existing
`openExternal`, `openPath`, and `trashItem` blocks reject their promises.
The `trashItem` block prevents that call from reaching the OS trash operation;
it does not intercept other ways the application can remove files.
Process rejection and uncaught-exception events are observed at dispatch without
adding exception handlers. The runtime and application retain their warning,
handler, and fatal-exit policies, including for rejected shell-block promises.
CLI SIGINT/SIGTERM requests cancellation and awaits owned-process cleanup.
Window IDs, URLs, and cached titles come from one main-process Electron snapshot,
so a crashed renderer does not require a successful renderer-side title query.
+9
View File
@@ -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
+3
View File
@@ -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 <version>` 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
+14 -7
View File
@@ -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 (`<version> (<channel>) (<commit time>)` with an
optional ` on "<os>_<arch>"` 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
+5
View File
@@ -172,6 +172,11 @@ have no file backing. ELF mappings are observed
without independent format-specific load-image verification, as the result's
status and limitations state.
Relocation rows retain Ghidra's `symbol` value, including an empty string or
null when returned by Ghidra. It is a provider observation, not proof of the
runtime-resolved address. Older Evidence may not have recorded this field;
a relocation page requesting it reports that uncertainty.
For large relocation inventories, select MCP `inspect_native_load_image` with
`detail: "summary"`. It returns verification state, observed identity and counts,
while retaining complete Evidence. Use `normalized_result.parent_evidence_id`
+2 -1
View File
@@ -43,7 +43,8 @@ rea decompile /absolute/path/to/program main --json
If the registration cannot be read or validated, provider diagnostics retain its
selected `registration_path` and the configuration owner's setting/constraint
facts in `configuration_details.settings`, plus `configuration_remediation`.
facts in `configuration_details.settings`. The availability's `remediation`, and
each operation it makes unavailable, carry the configuration owner's repair.
`configured` means a path was selected, not that it is valid or an IDA connection
was established; failed registrations remain unavailable. Correct
`REA_IDA_MCP_CONFIG` in the next CLI invocation's environment or the MCP server's
-1
View File
@@ -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) |
+38 -6
View File
@@ -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:<client>` 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 <client-id> --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
@@ -689,12 +704,19 @@ for installation. Setup cannot prompt when its output is redirected or during a
container image build. To approve Hopper installation without prompts, run
`rea setup --yes --install-hopper`.
If the Linux package download receives an HTTP failure, setup includes the
observed HTTP status in its `download_failed` diagnostic. The status does not
establish the underlying cause or guarantee that retrying will succeed.
If a Hopper request fails on macOS or Linux, the setup result's `download`
field names the request (`release_feed`, `package` or `public_key`), its `url`,
and the observed outcome `kind`: `http_status` with `httpStatus`, `timeout`,
`network`, `response_too_large`, or `url_refused`. The remediation names the
same URL and outcome. HTTP 404 or 410 means the file is missing, so an
immediate retry is unlikely to help; 5xx, 408 and 429 indicate a temporary
service failure. The status does not establish the underlying cause or guarantee
that retrying will succeed. Doctor and Linux launches report the same request
and status when publisher verification cannot download the current package.
Hopper metadata and package downloads require HTTPS throughout the redirect
chain; a redirect to HTTP is refused before that destination is requested.
chain; a redirect to HTTP or to a URL carrying credentials is refused before
that destination is requested, and those credentials are not reported.
On macOS, approved setup downloads the official DMG, checks its published size
and digest, validates the application bundle, and atomically installs it to
@@ -702,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
@@ -1314,7 +1345,8 @@ fails with `provider_timeout` and that message; other Ghidra tools stay
available. If the caller also cancels while the timeout settles, the session
still closes and the message says so. Session reuse concerns an active MCP or
library session; a standalone CLI command still closes its own session when it
finishes. In MCP, `ghidra.decompile_timeout_seconds` can override the launch
finishes, so its timeout names `REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS` for a new
invocation instead. In MCP, `ghidra.decompile_timeout_seconds` can override the launch
value for newly accepted operations on the existing Program; explicit `null`
removes the deadline. A transient timeout remains uncached so the same function
can be retried after an update. See the
+8 -1
View File
@@ -173,7 +173,14 @@ Results follow retained node order and default to offset 0 and limit 64. Supply
to examine relationships from that node. Other typed seeds do not search labels.
The result contains the matching basis, complete reachable subgraph, terminal
paths, authority summaries, and native handoffs. A handoff binds
paths, authority summaries, and native handoffs. A bidirectional trace of a
large application can exceed one MCP response. Over MCP, pass
`view: {"kind":"summary"}` or a `feature-trace` page of `paths`, `nodes`,
`edges`, `seed_matches` or `native_handoffs` to retain the complete trace in
the session and return only selected rows with the trace's identity and
coverage. Page the retained trace with `inspect_analysis_view`; an oversized
complete reply offers its first `paths` page as a ready-to-call continuation.
See [MCP contracts](mcp-contracts.md) for filters. A handoff binds
the exact native artifact digest and requested exports from the application
graph. Trace direction selects the reachable addons; requested
exports for each selected addon come from the complete supplied graph, so an
+22 -3
View File
@@ -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 `<script>` bodies
without a `src` attribute, is not analyzed. Each HTML file that has one gets an
without a `src` attribute, is not analyzed. Embedded SVG scripts use `href`
(or `xlink:href` when `href` is absent) instead: an existing reference, even
an empty one, suppresses the inline body. SVG's `src` and HTML-only `language`
attributes do not suppress its JavaScript body. SVG references and inline
bodies share the same inert parsing and source coordinates as HTML scripts;
template content stays inert. Each HTML file that has an inline body gets an
unknown `parse-html-inline-scripts` scope with the exact body count and the
script type and source range of the first 100 bodies; beyond that, coverage is
truncated under the `html_inline_script_locations_per_file` limit, and the
+3 -2
View File
@@ -65,8 +65,9 @@ confidence and basis, and does not prove a backend endpoint or reachability.
Pages obey the shared 1 MiB projection budget; MCP transport applies its own
wire-size guard. A byte-limited page returns a continuation; an indivisible
oversized record or metadata returns a typed resource error. Select fewer
fields or retrieve the complete retained parent through `get_evidence_bundle`,
`export_evidence_bundle`, or the CLI's exported bundle. Full Evidence is never
fields or retrieve the complete retained parent through `get_evidence_bundle`
(`detail: "records"` with its `evidence_id`), `export_evidence_bundle`, or the
CLI's exported bundle. Full Evidence is never
silently truncated.
Collections are `summary`, `routes`, `occurrences`, `sources`, and `unknowns`.
@@ -93,6 +93,10 @@ coverage. It does not print captured source text.
Runtime targets, frames, scripts, and workers become capture-scoped graph
nodes. REA evaluates compatible static candidates conservatively:
V8 Inspector targets whose protocol type is `node` use JavaScript asset/module
candidates for their verified entry location. A target-only correspondence does
not mark that script loaded; load-state evidence requires a script observation.
1. A captured-source SHA-256 plus one matching mapped location yields
a content-and-location match.
2. When no static candidate exists at a mapped location, one unique exact
+26 -1
View File
@@ -1,9 +1,27 @@
# Kuna provider
[Kuna](https://github.com/Noelo-Lab/kuna) is an Apache-2.0 decompiler distributed as one `kuna` executable. For a new installation, use Kuna 1.986, REA's recommended verified release. REA uses a `kuna` you supply; it does not download, install, update or bundle Kuna. REA accepts other releases, which it reports as unverified in `rea doctor` and in each analysis profile. The 1.986 real-provider checks ran on macOS arm64; Linux and other hosts have not been verified with that release.
[Kuna](https://github.com/Noelo-Lab/kuna) is an Apache-2.0 decompiler distributed as one `kuna` executable. For a new installation, use Kuna 1.989, REA's recommended verified release. REA uses a `kuna` you supply; it does not download, install, update or bundle Kuna. REA accepts other releases; `rea doctor` and analysis Evidence report them as unverified. Verification compares the reported version; it does not authenticate the executable. Analysis profiles retain the observed version and executable digest, independently of REA's current verified-release policy. The 1.989 real-provider checks ran on macOS arm64; Linux and other hosts have not been verified with that release.
Kuna is static: it reads the selected image and never executes it.
This profile change makes earlier Kuna analysis snapshots and resumable directory
exports incompatible, even with the same Kuna executable. Keep the old files:
their recorded Evidence remains historical. To continue analysis, open the
binary without the old snapshot and save a new one, or choose a new export
directory. Existing snapshot Evidence can be extracted from its
`evidence_bundle` JSON member and imported as a separate Evidence bundle; that
does not restore cached workflow entries or resume the old directory export.
For example, using `jq` and a new output path:
```bash
jq -e '.evidence_bundle | select(type == "object")' old-snapshot.json > history.json
rea evidence-import history.json --json
```
The CLI command validates the bundle in a one-shot session. To use the records
in an MCP session, call `import_evidence_bundle` with the extracted file path.
This validates the embedded bundle, not the rest of the snapshot container.
## Configure
```bash
@@ -30,6 +48,10 @@ completes. Cleanup retries keep both the observations and the exact file owner.
| `REA_KUNA_TIMEOUT_MS` | 120 s | Deadline for one single-function decompile, and for the inventory run REA makes when the export ends without one. |
| `REA_KUNA_JOBS` | 1 | `decompile-project --jobs`. 1 is Kuna's reference answer; more jobs finish sooner but lose callee-first prototypes, and change about 40–60% of function bodies on the measured binaries. |
Pseudocode read from a parallel project export includes a prototype-context
limitation in its Evidence, including in function dossiers and composed
workflows. Early single-function results have their own source limitation.
A single-function decompile, inventory, string, graph or xref run that exceeds its deadline fails as `provider_timeout`, naming the setting that owns the deadline and that raising it takes a restart. A project export that exceeds `REA_KUNA_PROJECT_TIMEOUT_MS` is an export failure instead: requests that need the export report `capability_unavailable` with the export's own retry guidance.
Windows is not supported yet. Supported targets are ELF and thin Mach-O executables for x86-64, AArch64 and ARM; PE and universal Mach-O are rejected with a typed reason.
@@ -47,6 +69,8 @@ Windows is not supported yet. Supported targets are ELF and thin Mach-O executab
| `xrefs` | Exact-address incoming reference sites, with forwarding aliases filtered explicitly |
| `analyze_function` | Pseudocode and assembly from the export, plus call relationships; typed references, comments, strings and CFG remain unavailable |
Kuna's inventory gives some entries aliases, such as ELF versioned-symbol names and generated `sub_` names. Name selectors accept an alias that names one entry and list every entry a shared alias names. `search_procedures` matches aliases too: each entry appears once under its primary name, with the matching aliases in `matched_aliases`. `list_procedures` shows primary names only.
Typed procedure references, basic blocks, raw-memory tools, annotations and type recovery are not available from this provider. Use another provider for those operations.
## How answers are produced
@@ -123,6 +147,7 @@ that rollback is resolved; a clean rollback permits another setup attempt.
## Provenance and limitations
- If a completed project export reports a failed function, `procedure_pseudo_code` with `analysis: "independent_fresh"` or `rea decompile --analysis independent_fresh` requests a new single-function attempt. It bypasses the project result and prior single-function and snapshot results; it does not replace the original observation. On a cold session it obtains an inventory without starting a project export. The independent attempt can use different analysis context and may also fail. Identical results can share an Evidence ID because Evidence is content-addressed.
- Addresses and bytes are Kuna's observations of the image. Procedure entries and extents are its derivations: extents are upper bounds clipped at the next entry, and Kuna can split one function at an entry its own analysis infers. Pseudocode is its reconstruction, never original source.
- Pseudocode and assembly are exact Kuna text; text from Kuna and from another provider is not comparable as equal or different.
- Results are re-read rather than replayed from REA's analysis snapshots, because a cold answer can differ from the export's.
+142 -6
View File
@@ -292,6 +292,12 @@ limitation and reason IDs index that page's `items.messages` dictionary. Expand
those references to recover exact text, repeated messages, and absent versus
empty optional fields. Failed nodes retain their original error.
Retained `xrefs` Evidence also supports `native-observation` address pages and
`native-context` source caveats. `xrefs` still returns complete Evidence by
default and does not accept `detail`; when its MCP response exceeds the budget,
follow the returned continuation to page the retained result. These addresses
remain untyped; paging does not add reference kinds or establish completeness.
These views reference complete source context through `source_context.digest`
and caveat counts. Read it using the same parent with
`view: {"kind":"native-context","facet":"overview"}`. The other facets are
@@ -318,6 +324,10 @@ offered for completed `analyze_function` results only after session recording is
acknowledged; the complete result and its retention reference remain intact.
Oversized native inventories likewise include a ready-to-call first
`native-inventory` page. Reduce its limit or select fields when a row is large.
An oversized `trace_application_feature` result includes a first `feature-trace`
page of its retained terminal `paths`; page `nodes` or `edges` from there.
An oversized `batch_decompile` result includes a first body-free
`batch-decompile` page of its retained `items`; read bodies from there.
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.
@@ -331,7 +341,26 @@ a provider. `retention: "complete-record"` describes stored Evidence, while
`native_dossier.value_flow` independently reports truncation, unavailable or
unknown metadata. Missing omission counts remain null. Summary output carries
every matching record; actual transport admission still applies. Omitted
`detail` preserves complete canonical bundle delivery; filters require summary.
`detail` preserves complete canonical bundle delivery; filters require summary
or records.
To read a discovered record without transferring the whole bundle, call
`get_evidence_bundle` with `detail: "records"` and the same exact `filters`.
The result (`kind: "evidence-record-selection"`) pages the matching complete
records in canonical bundle (Evidence ID) order with `offset` (default 0) and
`limit` (default 8), and reports `matching_records`, `total_retained_records`
and `next_offset`. Each record is the retained Evidence itself, with an
unchanged ID, subject digest, provider, profile and readback, so it works for
any operation, including records that `inspect_analysis_view` does not support
such as annotations. `missing_evidence_links` lists direct `evidence_links` of
the returned records that this session or bundle does not retain; links are not
traversed. A selection is not an Evidence bundle: it omits manifests and
residual unknowns, cannot be imported, and describes historical observations
rather than current provider or database state. `offset` and `limit` require
`detail: "records"`. If one page exceeds the response budget, select fewer
records or one `evidence_id`; a single record larger than one response still
needs `export_evidence_bundle`, or `inspect_analysis_view` pages where its parent
supports views.
Residual-unknown mutations reject missing Evidence or unknown references and
unproven verified resolutions with nonretryable `invalid_request` diagnostics.
@@ -367,8 +396,26 @@ that revision. If a list response overflows, retry on the same connection with
the same filters and offset and a smaller limit; export if one head still cannot
fit.
To find missing sources before a downstream analysis refuses them, read
`missing_direct_links` from the summary, or call `get_evidence_bundle` with
`detail: "links"`. The result (`kind: "evidence-link-inspection"`) transfers no
record payload. `filters` select the starting records, while membership is
checked against every record in the session or file, so a filtered-out source is
`retained`, not missing. Rows (`evidence_id`, `linked_evidence_id`, `status`) are
in canonical order, default to `link_status: "missing"` and page with `offset` and
`limit` (default 64); `links` counts every inspected link regardless of that
selection. `traversal: "direct"` (the default) reads the selected records' own
links and sets `retained_closure` to null, because it makes no closure claim.
`traversal: "retained-closure"` also follows retained links, as composed workflows
such as `build_call_path` do, and reports `complete` or `incomplete`; anything
beyond a missing link remains unknown. Missing means absent from this session or
file, not nonexistent, and neither status describes analysis coverage. Importing
a missing source changes this membership without changing any Evidence ID.
For a portable exported bundle, the CLI shares this projection:
`rea inspect-evidence-bundle '{"path":"/tmp/evidence.json","detail":"summary","filters":{"operation":"analyze_function"}}' --format json`.
`"detail":"records"` with an `evidence_id` filter returns that complete record,
and `"detail":"links"` reads link membership.
The CLI validates and materializes that local bundle under its existing memory
budget; summary delivery does not make arbitrary large files cheap to import.
@@ -572,6 +619,69 @@ the working directory). Save its complete `--json` Evidence, then use
`inspect-analysis-view` with an `evidence-file` source and the same view. Neither
adapter executes the target or repeats analysis for retained queries.
`trace_application_feature` follows the same delivery contract. Omitting `view`
returns the complete trace. `view: {"kind":"summary"}` or a `feature-trace` page
retains the complete trace in the session first and returns only the selected
facts; query further pages with `inspect_analysis_view` and the returned
`parent_evidence_id`. The summary carries the trace identity (`trace_id`,
`source_evidence_id`, `source_graph_id`, seed and direction), the producer's
`summary` counts, `trace_coverage`, the traced subgraph's `graph_coverage`,
`evidence_links` and any no-match `semantic_discovery`. A `feature-trace` page
selects one `collection` in retained order and repeats that context:
- `paths`: exact terminal paths; `where.node_ids` selects paths visiting any of
those nodes and `where.end_kinds` selects their terminal node kinds.
- `nodes`: compact rows with identity, `seed`, `observation_count` and distinct
authority/state/confidence `basis`; `where.node_ids` and `where.kinds` filter
exactly, and `include_observations: true` adds each node's complete retained
observations.
- `edges`: exact relationships; `where.node_ids` matches either endpoint and
`where.relations` the relation.
- `seed_matches` and `native_handoffs`: the producer's exact rows.
Omitted filters select all; empty arrays select none. `offset` and `limit`
default to 0 and 64. Page `coverage` counts the selected collection only; it
never upgrades `trace_coverage`, and graph connectivity does not prove runtime
reachability. The CLI keeps complete trace JSON; save it and use
`inspect-analysis-view` with an `evidence-file` source.
### Selected batch decompile results
`batch_decompile` returns every pseudocode body by default. `detail: "summary"`
retains the complete batch in the session first and returns its producer counts,
`source_provider` (the composing workflow), `source_analysis_profile_digest`,
`decompiling_provider`, procedure identity counts, total and largest
`pseudocode` size, and `limitation_count`. A server that retains no Evidence
refuses the summary before analysis. Further views use the returned
`parent_evidence_id` through `inspect_analysis_view`, or a saved Evidence file or
portable bundle, and never repeat decompilation.
A `batch-decompile` view selects one `collection` and repeats the batch counts
and provider identities:
- `items`: rows in retained order with their original `index`, so duplicate
selectors stay distinct. A row keeps the selector in `address`, its `procedure`
identity and `status`, plus `pseudocode_code_units` or its exact `error` and
`limitations`. `where.indices`, `where.addresses`, `where.procedure_addresses`
and `where.statuses` filter exactly and combine; an item with unknown identity
never matches `procedure_addresses`. Omitted filters select all, and empty
arrays select none. `offset` and `limit` default to 0 and 64.
`include_pseudocode: true` adds each selected body.
- `pseudocode`: one `index`'s text in UTF-16 code-unit pages. `offset` and `limit`
default to 0 and 16,384, and `coverage` measures that body. An offset inside a
surrogate pair is refused, and a limit ending inside one stops before it. An
error item has no text and is refused with a pointer to `items`.
Page `coverage` counts the selection only: exhausting it establishes nothing
about functions outside the batch. Evidence `limitations` aggregate every item's
provider observations and are not attributed to one successful item; only error
items keep their own. A page over the response budget offers a smaller selection
from the same `source`: fewer rows, or one body's `pseudocode` pages when a single
row does not fit. An inline source offers none, because the record cannot be
repeated in that reply; resend it with a smaller selection.
`export_evidence_bundle` remains the complete alternative. The CLI has no batch command; save the Evidence and use
`inspect-analysis-view` with an `evidence-file` source or `--evidence-bundle`.
Completed `inspect_artifact` and atomic `inventory_artifact` Evidence also
support summary and inventory views. A summary reports the manifest's expected
counts separately from available record counts, observed entry kinds, inventory
@@ -742,13 +852,27 @@ requirements. Compare the union of group names with `tools/list` when checking
that a client received the full catalog. An operation absent from all groups is
missing from the complete inventory; it is not an implicit unavailable entry.
Availability also reflects the active provider client's dispatched requests. A
refusal or provider failure observed for an operation keeps that operation
unavailable until a request for it dispatched later succeeds. A provider adapter
or process failure also marks that provider's other declared-available
operations unavailable until any request to that provider dispatched later
succeeds; their remediation states that the status was inferred rather than
observed for them. Recovery is not proof that each operation works, and failures
observed on other operations or providers remain. Outcomes are ordered by
dispatch to the provider client rather than reply completion, so a late reply
from an older request does not override a newer outcome. Request deadlines and
cancellations neither mark nor clear availability. A new provider client starts
without these outcomes.
`detail` selects the projection and the result echoes it. The default
`summary` keeps open state, target identity, the binding, current-route
`capabilities`, run, activity and health status, and each provider candidate's
identity, selection, and availability and target-support `status`, `code`, and
`reason`. It lists the fields it leaves out in `omitted_fields`: provider
`diagnostics`, candidate `capabilities`, and each tool group's static `surface`,
`effects`, and `annotations`. Omitted fields are not empty or false.
`reason`. It lists the fields it leaves out in `omitted_fields`: an unavailable
candidate's `remediation`, provider `diagnostics`, candidate `capabilities`, and
each tool group's static `surface`, `effects`, and `annotations`. Omitted fields
are not empty or false.
`detail: "complete"` returns them inline, groups tools by those flags too, and
has no `omitted_fields`. The output schema keys each result's candidate,
tool-group and omission fields by its `detail`. CLI `capabilities` and
@@ -801,7 +925,9 @@ set, `shared_limitations` is the ordered common prefix; append a group's ordered
`effects` arrays use the same true-only closed-vocabulary rule as tool
availability. Expand every group operation while retaining its provider
identity, availability, reason, rejection code, effects, and ordered
limitations.
limitations. An unavailable group carries `remediation` when its readiness
owner supplied a repair instruction; `tool_availability` uses that instruction
rather than the failure description.
`binary_session.analysis_provider_candidates` is authoritative for deep-engine
discovery. Each candidate's capability sets, returned with complete detail,
@@ -876,6 +1002,14 @@ is `unavailable` with a reason when ownership could not be revalidated, or
at that bounded check. These are historical snapshots, not live process
inventories, and do not claim that no short-lived descendant existed.
After a failed close retires the target, `binary_session.cleanup_retry` names
`close_binary` only while that session still retains the cleanup bound to the
close error. Target-dependent availability then gives the same conditional
cleanup guidance before suggesting `open_binary`. The hint disappears after
cleanup succeeds. A pending flag alone does not establish this retry: active
targets, an in-flight initial close, and external-mutation blockers keep their
own recovery and do not receive this hint.
`binary_session.cleanup_pending` reports whether the session retains a provider
client, session file resource, or provider-candidate resource whose cleanup
has not succeeded. It can be true with
@@ -1351,7 +1485,9 @@ cursor outside a procedure is represented as
procedure resolution returns typed unreadable output; provider errors never
become absence from their message text. `inspect_address_context` requires an explicit address and
returns name, procedure, comment, inline-comment, and bookmark facets;
unsupported facets are local `unavailable` outcomes. Use `current_document`,
unsupported facets are local `unavailable` outcomes. A facet whose input is
rejected includes its canonical `error` projection, including any structured
issues, alongside the other facet outcomes. Use `current_document`,
`current_address`, and `current_procedure` for direct single-field lookups; use
the aggregate tools when you need related context together.
+37 -6
View File
@@ -68,7 +68,10 @@ 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.
its address when selecting the containing function. `search_procedures` matches
the same names, in leaf or qualified spelling: the function appears once with
its primary name as `value`, and `matched_aliases` lists the qualified names of
the secondary symbols that matched.
Overloads can share a fully qualified name; ambiguity errors return every
matching entry address so the caller can select the intended function directly.
@@ -198,6 +201,11 @@ remains valid. This tool
has no dedicated CLI command; the existing `decompile` command remains the
single-procedure CLI operation.
`detail: "summary"` retains the complete batch and returns only its counts,
identities and sizes. `inspect_analysis_view` then reads rows or one body from
the retained record, an Evidence file or a portable bundle without repeating
decompilation: see [selected batch results](mcp-contracts.md#selected-batch-decompile-results).
Each batch schedules one provider read at a time, but has no shared input-count
or aggregate retained-output allowance. It retains every requested row and full
pseudocode body through Evidence construction. The MCP response budget checks
@@ -219,7 +227,17 @@ caveats. Failure details retain at most 32 rows within 2,048 JSON-encoded
UTF-8 bytes; names may be omitted to keep complete addresses and reasons. The
index retains every failure and its complete observed name.
Each function's `decompile-index.json` record, whatever its status, keeps the
complete limitations of the observations it used. The returned Evidence keeps
complete limitations of the observations it used.
When available, `provider_source` records the source of that attempt's
pseudocode observation: a file path, SHA-256 and byte range, or a
single-function decompile. The latter describes the method, not a unique run. A file
descriptor records the source as observed; the file may since have changed or
been removed, so compare its current digest before using it. The descriptor
does not authenticate the journal. `status: "unknown"` distinguishes
an unavailable source from an existing matching output whose original producer
is unknown. Older journal records can omit this field; omission is unknown.
Failed attempts retain their source in the append-only journal when the
provider supplied one, even if a later attempt succeeds. The returned Evidence keeps
at most eight first-observed distinct per-function provider limitations within an 8,000-byte JSON-encoded UTF-8 text budget and counts omitted observations. Repeated omitted text is counted per observation, not as a distinct caveat. This summary does not retain every omitted string in memory. A fresh output directory must not already exist. `--resume`
continues an export for the same target SHA-256, provider, analysis profile,
and layout; it requires the directory to exist, and an export without a known
@@ -380,6 +398,11 @@ sites remain distinct. A static route does not establish runtime reachability.
Swift/closure dispatch requires resolved static references; unavailable ABI
forms remain unknown.
Encoded Objective-C method records retain their own locations in dispatch
Evidence. A handler node uses the implementation address with an unknown file
offset unless that function already has an independently located graph node;
the method-record offset does not locate the handler's code.
Incomplete reference kinds do not discard known typed calls or partial
provider call references. A `provider_call` with `observed` resolution retains
the provider-reported call site and endpoint without claiming generic direct,
@@ -425,7 +448,12 @@ Within one value trace, each exact procedure selector is attempted at most once,
including failed analyses. A failed seed returns the provider's original typed
failure and corrective guidance; it cannot establish a trace or target identity.
Failures while expanding a validated seed remain unknown observations and do not
establish canonical function identity. A successful seed without a native def-use
establish canonical function identity. A cancelled expansion read returns the
cancellation. An expansion read whose provider cannot confirm cleanup of
resources it owns stops the trace: the result is that provider's own
`cleanup_incomplete` failure with its resources and any bound cleanup retry, and the work
admitted before the stop is retained as partial Evidence with a located unknown
at the stopping read. A successful seed without a native def-use
model still returns its procedure and an explicit coverage unknown. A new trace
can retry failed selectors.
@@ -434,8 +462,9 @@ page omits retained nodes, and the edges they source, before `offset` or after
`next_offset`. Every page repeats `traversal`: `complete`, or `truncated` with
`reasons` naming each observed cause: the `max_depth`, `max_functions`,
`max_call_sites`, `max_nodes` or `max_edges` budget, the fixed
`node_payload_bytes` allowance, `provider_pcode_truncated`, or
`ambiguous_call_candidates`. Each cause also has a located unknown. A budget is
`node_payload_bytes` allowance, `provider_pcode_truncated`,
`ambiguous_call_candidates`, or, only in a stopped trace's partial Evidence,
`cleanup_incomplete`. Each cause also has a located unknown. A budget is
reported only after it rejects work, so an exactly filled budget is not
truncation; a depth or candidate limit is reported only when no other path
analyzes or queues that destination. `total_nodes` and `total_edges` count
@@ -990,7 +1019,9 @@ with a path-specific gap and incomplete capture coverage. Null alone does not
establish that an accessibility value was successfully read or absent.
An initial PID or window mismatch is `invalid_request` on `pid` or
`window_id` and leaves provider availability intact. An unavailable executable
`window_id` and leaves provider availability intact. A selected window that
exists off screen is not a mismatch: selection succeeds and the permission and
capture checks report their own outcome. An unavailable executable
URL or unresolved executable path leaves identity unknown and reports
`capability_unavailable`. Identity or window loss
after the initial capture remains an observation failure with collected facts.
+2
View File
@@ -20,6 +20,8 @@
**[الموقع](https://rea.tools/ar/) · [الأدلة](https://rea.tools/ar/guides/) · [دراسات الحالة](https://rea.tools/ar/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="انضم إلى مجتمع REA على Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[البدء السريع](#البدء-السريع) · [كيف يعمل REA](#كيف-يعمل-rea) · [ما الذي يمكنك تحليله](#ما-الذي-يمكنك-تحليله) · [دراسات الحالة](#دراسات-الحالة) · [إنشاء بيئات التعلم المعزز](#build-rl-environments) · [الأسئلة الشائعة](#الأسئلة-الشائعة) · [أدوات GNU objdump وRizin](../reverse-engineering-tools.md) · [التوثيق](#التوثيق)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[Website](https://rea.tools/de/) · [Anleitungen](https://rea.tools/de/guides/) · [Fallstudien](https://rea.tools/de/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Der REA-Community auf Discord beitreten" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Schnellstart](#schnellstart) · [So funktioniert REA](#so-funktioniert-rea) · [Was du analysieren kannst](#was-du-analysieren-kannst) · [Fallstudien](#fallstudien) · [RL-Umgebungen erstellen](#build-rl-environments) · [Häufige Fragen](#häufige-fragen) · [Dokumentation](#dokumentation)
<code>npx rea-agents setup</code>
+2
View File
@@ -19,6 +19,8 @@
**[Ιστότοπος](https://rea.tools/) · [Οδηγοί](https://rea.tools/guides/) · [Μελέτες περίπτωσης](https://rea.tools/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Έλα στην κοινότητα REA στο Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Γρήγορη εκκίνηση](#γρήγορη-εκκίνηση) · [Πώς λειτουργεί το REA](#πώς-λειτουργεί-το-rea) · [Τι μπορείς να αναλύσεις](#τι-μπορείς-να-αναλύσεις) · [Μελέτες περίπτωσης](#μελέτες-περίπτωσης) · [Δημιουργία περιβαλλόντων RL](#build-rl-environments) · [Συχνές ερωτήσεις](#συχνές-ερωτήσεις) · [Τεκμηρίωση](#τεκμηρίωση)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[Sitio web](https://rea.tools/es/) · [Guías](https://rea.tools/es/guides/) · [Casos prácticos](https://rea.tools/es/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Únete a la comunidad de REA en Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[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) · [Crear entornos de RL](#build-rl-environments) · [Preguntas frecuentes](#preguntas-frecuentes) · [Documentación](#documentación)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[وب‌سایت](https://rea.tools/fa/) · [راهنماها](https://rea.tools/fa/guides/) · [نمونه‌های عملی](https://rea.tools/fa/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="به انجمن REA در دیسکورد بپیوندید" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[شروع سریع](#شروع-سریع) · [REA چگونه کار می‌کند؟](#rea-چگونه-کار-می‌کند) · [چه چیزهایی را میتوان تحلیل کرد؟](#چه-چیزهایی-را-می‌توان-تحلیل-کرد) · [نمونه‌های عملی](#نمونه‌های-عملی) · [ساخت محیط‌های RL](#build-rl-environments) · [پرسش‌های متداول](#پرسش‌های-متداول) · [مستندات](#مستندات)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[Site web](https://rea.tools/fr/) · [Guides](https://rea.tools/fr/guides/) · [Études de cas](https://rea.tools/fr/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Rejoignez la communauté REA sur Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[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) · [Créer des environnements RL](#build-rl-environments) · [Questions fréquentes](#questions-fréquentes) · [Documentation](#documentation)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[Situs Web](https://rea.tools/id/) · [Panduan](https://rea.tools/id/guides/) · [Contoh Penggunaan](https://rea.tools/id/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Bergabunglah dengan komunitas REA di Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Mulai Cepat](#mulai-cepat) · [Cara Kerja REA](#cara-kerja-rea) · [Hal yang Dapat Anda Analisis](#hal-yang-dapat-anda-analisis) · [Contoh Penggunaan](#contoh-penggunaan) · [Membangun lingkungan RL](#build-rl-environments) · [FAQ](#faq) · [Dokumentasi](#dokumentasi)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[Sito web](https://rea.tools/it/) · [Guide](https://rea.tools/it/guides/) · [Showcase](https://rea.tools/it/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Unisciti alla community REA su Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Avvio rapido](#avvio-rapido) · [Come funziona REA](#come-funziona-rea) · [Cosa puoi analizzare](#cosa-puoi-analizzare) · [Showcase](#showcase) · [Creare ambienti RL](#build-rl-environments) · [FAQ](#faq) · [Documentazione](#documentazione)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[ウェブサイト](https://rea.tools/ja/) · [ガイド](https://rea.tools/ja/guides/) · [事例](https://rea.tools/ja/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="DiscordのREAコミュニティに参加" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[クイックスタート](#クイックスタート) · [REA の仕組み](#rea-の仕組み) · [分析できる対象](#分析できる対象) · [事例](#事例) · [RL 環境を構築する](#build-rl-environments) · [よくある質問](#よくある質問) · [ドキュメント](#ドキュメント)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[웹사이트](https://rea.tools/ko/) · [가이드](https://rea.tools/ko/guides/) · [사례](https://rea.tools/ko/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Discord에서 REA 커뮤니티에 참여하세요" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[빠른 시작](#빠른-시작) · [REA의 작동 방식](#rea의-작동-방식) · [분석할 수 있는 대상](#분석할-수-있는-대상) · [사례](#사례) · [RL 환경 만들기](#build-rl-environments) · [자주 묻는 질문](#자주-묻는-질문) · [문서](#문서)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[വെബ്സൈറ്റ്](https://rea.tools/) · [ഗൈഡുകൾ](https://rea.tools/guides/) · [ഉദാഹരണങ്ങൾ](https://rea.tools/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Discord-ൽ REA കമ്മ്യൂണിറ്റിയിൽ ചേരുക" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[ദ്രുതാരംഭം](#ദ്രുതാരംഭം) · [REA എങ്ങനെ പ്രവർത്തിക്കുന്നു](#rea-എങ്ങനെ-പ്രവർത്തിക്കുന്നു) · [എന്തൊക്കെ വിശകലനം ചെയ്യാം](#എന്തൊക്കെ-വിശകലനം-ചെയ്യാം) · [ഉദാഹരണങ്ങൾ](#ഉദാഹരണങ്ങൾ) · [RL പരിതസ്ഥിതികൾ നിർമ്മിക്കുക](#build-rl-environments) · [പതിവുചോദ്യങ്ങൾ](#പതിവുചോദ്യങ്ങൾ) · [ഡോക്യുമെന്റേഷൻ](#ഡോക്യുമെന്റേഷൻ)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[Strona internetowa](https://rea.tools/pl/) · [Przewodniki](https://rea.tools/pl/guides/) · [Przykłady zastosowań](https://rea.tools/pl/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Dołącz do społeczności REA na Discordzie" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[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ń) · [Tworzenie środowisk RL](#build-rl-environments) · [FAQ](#faq) · [Dokumentacja](#dokumentacja)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[Site](https://rea.tools/pt-BR/) · [Guias](https://rea.tools/pt-BR/guides/) · [Casos de uso](https://rea.tools/pt-BR/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Participe da comunidade REA no Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[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) · [Criar ambientes de RL](#build-rl-environments) · [Perguntas frequentes](#perguntas-frequentes) · [Documentação](#documentação)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[Site web](https://rea.tools/) · [Ghiduri](https://rea.tools/guides/) · [Studii de caz](https://rea.tools/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Alătură-te comunității REA pe Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Pornire rapidă](#pornire-rapidă) · [Cum funcționează REA](#cum-funcționează-rea) · [Ce poți analiza](#ce-poți-analiza) · [Studii de caz](#studii-de-caz) · [Construiește medii RL](#build-rl-environments) · [Întrebări frecvente](#întrebări-frecvente) · [Documentație](#documentație)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[Сайт](https://rea.tools/ru/) · [Руководства](https://rea.tools/ru/guides/) · [Примеры](https://rea.tools/ru/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Присоединяйтесь к сообществу REA в Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Быстрый старт](#быстрый-старт) · [Как работает REA](#как-работает-rea) · [Что можно анализировать](#что-можно-анализировать) · [Примеры](#примеры) · [Создание RL-окружений](#build-rl-environments) · [Частые вопросы](#частые-вопросы) · [Документация](#документация)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[เว็บไซต์](https://rea.tools/th/) · [คู่มือ](https://rea.tools/th/guides/) · [ตัวอย่างการใช้งาน](https://rea.tools/th/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="เข้าร่วมชุมชน REA บน Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[เริ่มต้นอย่างรวดเร็ว](#เริ่มต้นอย่างรวดเร็ว) · [REA ทำงานอย่างไร](#rea-ทำงานอย่างไร) · [สิ่งที่วิเคราะห์ได้](#สิ่งที่วิเคราะห์ได้) · [ตัวอย่างการใช้งาน](#ตัวอย่างการใช้งาน) · [สร้างสภาพแวดล้อม RL](#build-rl-environments) · [คำถามที่พบบ่อย](#คำถามที่พบบ่อย) · [เอกสาร](#เอกสาร)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[Web sitesi](https://rea.tools/tr/) · [Rehberler](https://rea.tools/tr/guides/) · [Örnek çalışmalar](https://rea.tools/tr/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Discord'da REA topluluğuna katılın" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[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) · [RL ortamları oluşturun](#build-rl-environments) · [Sık sorulan sorular](#sık-sorulan-sorular) · [GNU objdump ve Rizin araçları](../reverse-engineering-tools.md) · [Belgeler](#belgeler)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[Сайт](https://rea.tools/uk/) · [Посібники](https://rea.tools/uk/guides/) · [Приклади](https://rea.tools/uk/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Приєднуйтеся до спільноти REA в Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Швидкий старт](#швидкий-старт) · [Як працює REA](#як-працює-rea) · [Що можна аналізувати](#що-можна-аналізувати) · [Приклади](#приклади) · [Створення RL-середовищ](#build-rl-environments) · [Поширені запитання](#поширені-запитання) · [Документація](#документація)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[Website](https://rea.tools/) · [Guides](https://rea.tools/guides/) · [Showcases](https://rea.tools/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Discord par REA community mein shamil hon" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Jaldi shuru karein](#jaldi-shuru-karein) · [REA kaise kaam karta hai](#rea-kaise-kaam-karta-hai) · [Kya kuch analyze kar sakte hain](#kya-kuch-analyze-kar-sakte-hain) · [Amli misaalein](#amli-misaalein) · [RL environments banayein](#build-rl-environments) · [FAQ](#faq) · [Documentation](#documentation)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[Trang web](https://rea.tools/vi/) · [Hướng dẫn](https://rea.tools/vi/guides/) · [Các nghiên cứu điển hình](https://rea.tools/vi/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Tham gia cộng đồng REA trên Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[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) · [Xây dựng môi trường RL](#build-rl-environments) · [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>
+2
View File
@@ -20,6 +20,8 @@
**[網站](https://rea.tools/zh-TW/) · [指南](https://rea.tools/zh-TW/guides/) · [案例](https://rea.tools/zh-TW/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="加入 REA 的 Discord 社群" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[快速開始](#快速開始) · [REA 的工作原理](#rea-的工作原理) · [可以分析什麼](#可以分析什麼) · [案例](#案例) · [建立 RL 環境](#build-rl-environments) · [常見問題](#常見問題) · [文件](#文件)
<code>npx rea-agents setup</code>
+2
View File
@@ -20,6 +20,8 @@
**[网站](https://rea.tools/zh-CN/) · [指南](https://rea.tools/zh-CN/guides/) · [案例](https://rea.tools/zh-CN/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="加入 REA 的 Discord 社区" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[快速开始](#快速开始) · [REA 的工作原理](#rea-的工作原理) · [可以分析什么](#可以分析什么) · [案例](#案例) · [构建 RL 环境](#build-rl-environments) · [常见问题](#常见问题) · [GNU objdump 和 Rizin 工具](../reverse-engineering-tools.md) · [文档](#文档)
<code>npx rea-agents setup</code>
@@ -86,7 +86,10 @@ credentials and resets it between sequences.
environment refused the call), `not-served`, `no-tool` and `unmapped-id`, each with a JSON pointer and short values.
- The report gives agreement overall, per probe and per tool, agreement ignoring item order beside it, the counts per
category, the ids mapped and the values normalised, the records left uncompared, and each step's differences (at most
20 per step).
20 per step). Each step gives `differences_total` before that cap, `differences_omitted`, and
`differences_truncated`, true when entries were omitted. Equality and categories use all differences.
Omitted entries are not retained and `--full-output` cannot recover them; further investigation uses the original
capture and probe Evidence.
## Non-goals
File diff suppressed because it is too large. Load diff
+46 -1186
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -119,7 +119,7 @@ Then rerun this installer.",implemented,verified by shell branch inspection
90,install.sh:version mismatch,Installed rea reports a different version,installed version ACTUAL does not match EXPECTED.,Low risk; gives exact integrity mismatch,Keep current copy,unchanged,verified by exact installer test
90.5,@modelcontextprotocol/server:tool input validation,Caller sends arguments that violate a declared tool schema,"Canonical invalid_request with the SDK diagnostic plus recovery.kind=inspect_input_contract and a bounded instruction to read the advertised schema or call tools/list",Keeps field/type correction while telling the agent how to recover; diagnostics over 4 KiB stay on the existing retained-error path,Normalize ordinary validation failures and retain oversized diagnostics,implemented,verified by real in-memory and stdio MCP client/server calls with wrong argument types
90.6,incur:unknown CLI command,Caller enters an unknown REA command,"'COMMAND' is not a command for 'rea'. Suggested command: rea --help",Low risk; identifies input and exact discovery action,Keep framework-generated copy,unchanged,verified by real packaged dispatcher invocation
90.7,src/ghidra/GhidraProviderClient.ts:projectPrimarySessionError,One procedure's decompile reaches REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS,"Ghidra decompilation of {entry} exceeded the {N}-second limit set by REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS. Only this procedure's decompilation was stopped; the Ghidra session remains usable.",Low risk; names the setting and keeps the session,Keep current copy,implemented,provider projection test and real-provider lane
90.7,src/ghidra/GhidraProviderClient.ts:projectPrimarySessionError,One procedure's decompile reaches REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS,"Ghidra decompilation of {entry} exceeded the {N}-second limit set by REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS. Only this procedure's decompilation was stopped. (MCP adds: The Ghidra session remains usable.)",Low risk; names the setting; only a continuing MCP caller is told the session remains usable,Keep current copy,implemented,"provider projection test, CLI/MCP fixture-bridge boundary test and real-provider lane"
90.8,src/ghidra/GhidraFileOffsetFailure.ts:ghidraFileOffsetUnavailable,"address_to_file_offset receives a well-formed address that is unmapped, uninitialized, without an authoritative target-file source, ambiguous or beyond the exact JSON integer range",{bridge reason}. REA establishes no single exact original-file offset for this well-formed address and does not derive or choose one.,Previous copy called the input invalid and directed schema repair for a valid address,"Keep the bridge reason as the headline, point to inspect_native_load_image, omit recovery.kind=inspect_input_contract and record details.issues[0].observed.file_mapping",implemented,provider projection tests; MCP and CLI analysis path through the real Ghidra client with a simulated bridge; real Ghidra not run
91,src/application/binaryDiagnostics/ByteEntropyService.ts (issues); src/domain/analysisErrorPresentation.ts (recovery),Invalid file or window,Expected a regular file.; Selected file could not be found.; Selected byte window starts beyond the end of the file.; Selected byte window extends beyond the end of the file.; File size exceeds exact numeric offset representation.,Invalid selection cannot produce complete entropy Evidence,"Read the declared input schema or CLI usage for inspect_byte_entropy; if using MCP and the catalog may be stale, call tools/list. Correct the listed fields, then retry. Do not repeat the unchanged call.",unchanged,Executed CLI/stdio MCP: path/offset/length refusals and tiny + system-binary controls; shared canonical recovery; service/MCP tests check classification and fields; remaining listed errors source-inspected
92,src/domain/analysisView/byteEntropyView.ts,Non-summary entropy view,Byte entropy Evidence supports the summary view; retrieve parent Evidence for the complete block series.,Unsupported view selection,Use the summary view or retrieve complete parent Evidence,implemented,typed input rejection
1 harm_rank location trigger current_copy user_risk proposed_replacement implementation_status verification_result
119
120
121
122
123
124
125
+2 -1
View File
@@ -83,7 +83,8 @@ valid: partial results state whether WABT validation is unknown, rejected the
module, or succeeded before a later inspection failure.
CLI errors include this partial Evidence inline. MCP errors can return its
`retained-evidence` reference in `details.partial_observation`; use
`get_evidence_bundle` with `detail: "complete"` to retrieve the matching record.
`get_evidence_bundle` with `detail: "records"` and that `evidence_id` filter to
retrieve the matching record.
Core validation remains authoritative when an optional custom section cannot
be interpreted. WABT warning output is preserved in `custom_section_warnings`;
+6 -2
View File
@@ -97,9 +97,13 @@ it does not invent an empty string or body bytes.
The observed Playwright 1.58.2 profile can report a bracketed IPv6
`serverIPAddress`, forwarded from Chromium's `remoteIPAddress` through
[Playwright's HAR tracer](https://github.com/microsoft/playwright/blob/v1.58.2/packages/playwright-core/src/server/har/harTracer.ts).
Chromium DevTools exports (declared as `WebInspector` `537.36`, the frozen
AppleWebKit version in Chromium's user agent) report the same bracketed form:
[its HAR writer](https://github.com/ChromeDevTools/devtools-frontend/blob/63739e8ec45ca01b9e4be9b8b05946acdadd8f1f/front_end/models/har/Log.ts)
strips the port but not the brackets.
REA preserves the original spelling in reported Evidence and removes brackets
only from its validation copy, only for this declared producer and a valid IPv6
literal. Invalid addresses, bracketed IPv4, ports and zone identifiers remain
only from its validation copy, only for these declared producers and a valid
IPv6 literal. Invalid addresses, bracketed IPv4, ports and zone identifiers remain
validation failures. Producer declarations do not authenticate origin.
Native mitmproxy decoding requires a caller-supplied **mitmdump 12.2.3 on
+1 -1
View File
@@ -22,7 +22,7 @@ Results retain:
- 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. 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.
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. Chrome reports a negative start column for some inline scripts, such as one in a helper frame, and positions on that script's first line include it. REA keeps those columns as reported rather than failing the observation; a negative column does not locate a column in `url`, but the subtraction above still gives the script-relative column.
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.
+2 -1
View File
@@ -87,7 +87,8 @@ example under Smart App Control or an App Control policy, reports failure stage
invocation or invalid inspection uses `runtime_unavailable` and doctor
classification `runtime_failure`; it does not
mean Windows x64 itself is unsupported. Provider diagnostics retain the failure
stage under `windows_native_failure`, together with the corresponding remediation.
stage under `windows_native_failure`; the availability's `remediation` carries
the corresponding repair.
Self-test failures preserve the native operation and OS error text. Check the
process's writable local NTFS temporary directory and the reported filesystem,
DACL, Job Object or cleanup constraint, then rerun doctor after resolving it.
+45
View File
@@ -81,6 +81,51 @@
// In this case, there is no squashed merge commit. This could be a simple
// merge commit, a rebase merge commit, or a direct commit to the branch.
// Fallback to fetching the list of commits from the REST API. In the future
@@ -1154,15 +1154,40 @@
* @returns {string} HTML URL of the new file
*/
async createFileOnNewBranch(filename, contents, newBranchName, baseBranchName) {
- // create or update new branch to match base branch
- await this.forkBranch(newBranchName, baseBranchName);
- // use the single file upload API
+ // Preserve existing notes until the replacement file is committed.
+ // A failed write must not leave an open proposal pointing at a missing file.
+ if (!(await this.getBranchSha(newBranchName))) {
+ const baseSha = await this.getBranchSha(baseBranchName);
+ if (!baseSha) {
+ throw new Error(`Unable to find base branch: ${baseBranchName}`);
+ }
+ await this.createNewBranch(newBranchName, baseSha);
+ }
+ let existingSha;
+ try {
+ const { data } = await this.octokit.repos.getContent({
+ owner: this.repository.owner,
+ repo: this.repository.repo,
+ path: filename,
+ ref: newBranchName,
+ });
+ if (Array.isArray(data) || data.type !== 'file' || !data.sha) {
+ throw new Error(`Expected a file at ${filename} on branch: ${newBranchName}`);
+ }
+ existingSha = data.sha;
+ }
+ catch (error) {
+ if (!(error && error.status === 404)) {
+ throw error;
+ }
+ }
const { data: { content }, } = await this.octokit.repos.createOrUpdateFileContents({
owner: this.repository.owner,
repo: this.repository.repo,
path: filename,
// contents need to be base64 encoded
- content: Buffer.from(contents, 'binary').toString('base64'),
+ content: Buffer.from(contents, 'utf8').toString('base64'),
+ ...(existingSha ? { sha: existingSha } : {}),
message: 'Saving release notes',
branch: newBranchName,
});
--- a/build/src/github.d.ts
+++ b/build/src/github.d.ts
@@ -47,6 +47,8 @@
+2 -1
View File
@@ -3,6 +3,7 @@ export interface ReadinessPullRequest {
readonly state: "open" | "closed";
readonly draft: boolean;
readonly body: string | null;
readonly user: { readonly id: number } | null;
readonly head: { readonly sha: string };
readonly base: { readonly ref: string };
}
@@ -53,7 +54,7 @@ export interface ReadinessGitHub {
>;
}
export function evaluateReadiness(
pull: Pick<ReadinessPullRequest, "body" | "draft">,
pull: Pick<ReadinessPullRequest, "body" | "draft" | "user">,
template: string,
): ReadinessResult;
export function runReadinessCheck(input: {
+18 -4
View File
@@ -2,6 +2,14 @@ import { readFile } from "node:fs/promises";
const result = (state, description) => ({ state, description });
// GitHub account IDs remain stable when a login changes.
const EXEMPT_AUTHOR_IDS = new Set([
76467478, // morluto
208281322, // kaoru0822-kitauji
161784452, // N0zoM1z0
342580529, // gensokyo-dev
]);
const OPEN_HEADS_QUERY = `query ReadinessOpenHeads($owner: String!, $name: String!, $after: String) {
repository(owner: $owner, name: $name) {
pullRequests(first: 100, after: $after, states: OPEN, orderBy: {field: CREATED_AT, direction: ASC}) {
@@ -60,11 +68,11 @@ function readinessLines(markdown) {
let comment = false;
for (const source of lines) {
if (fence) {
const closing = /^ {0,3}(`+|~+)\s*$/u.exec(source);
if (
new RegExp(
`^ {0,3}${fence.character}{${fence.length},}\\s*$`,
"u",
).test(source)
closing !== null &&
closing[1][0] === fence.character &&
closing[1].length >= fence.length
)
fence = undefined;
continue;
@@ -105,6 +113,11 @@ export function evaluateReadiness(pull, template) {
"pending",
"Draft PR: complete Author readiness before requesting review.",
);
if (EXEMPT_AUTHOR_IDS.has(pull.user?.id))
return result(
"success",
"PR author is exempt from the readiness declaration.",
);
const expected = readinessLines(template).flatMap((line) => {
const match = /^- \[ \] (.+)$/u.exec(line);
return match ? [match[1]] : [];
@@ -210,6 +223,7 @@ export async function runReadinessCheck({ github, context, core }) {
latest.head.sha !== pull.head.sha ||
latest.body !== pull.body ||
latest.draft !== pull.draft ||
latest.user?.id !== pull.user?.id ||
latest.base.ref !== pull.base.ref
)
verdict = result(
+8 -1
View File
@@ -76,6 +76,7 @@ const PATH_SCOPES = {
"src/frida/frida-bindings.d.ts",
"src/objdump/**",
"src/cli/reverseEngineeringCommands.ts",
"src/process/ownedDebuggerCleanupError.ts",
"src/process/ownedDebuggerSession.ts",
"src/rizin/**",
"src/contracts/*ToolContracts.ts",
@@ -103,6 +104,7 @@ const PATH_SCOPES = {
"tests/boundary/providers/RizinInferiorTty.test.ts",
"tests/boundary/providers/RizinTestInferiorTty.ts",
"tests/boundary/process/ownedDebuggerSession.test.ts",
"tests/boundary/cli/debuggerOneShotExit.test.ts",
"tests/boundary/cli/gdbStartupCleanupCli.test.ts",
"tests/boundary/cli/reverseEngineeringCommands.test.ts",
"tests/boundary/mcp/cutter*.test.ts",
@@ -163,6 +165,9 @@ const PATH_SCOPES = {
"tests/boundary/mcp/nativeDemangleMcp.test.ts",
"tests/boundary/mcp/nativeMachoSummaryMcp.test.ts",
"tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts",
"tests/boundary/providers/native/nativeUiOffscreenWindow.test.ts",
"tests/boundary/process/nativeUiVerifierLane.test.ts",
"tests/conformance/native/ui-offscreen.swift",
"tests/boundary/cli/nativeSignatureCli.test.ts",
"tests/boundary/cli/nativeMachoArm64_32Cli.test.ts",
"tests/boundary/cli/nativeMachoEncryptionCli.test.ts",
@@ -699,7 +704,9 @@ export const CI_LANES = {
agentenv: sameJobs(PATH_SCOPES.agentenv, ["agentenv"]),
nativeaot: sameJobs(PATH_SCOPES.nativeaot, ["nativeaot"]),
ghidra_bridge: sameJobs(PATH_SCOPES.ghidra_bridge, ["ghidra_bridge"]),
website: sameJobs(PATH_SCOPES.website, ["website"]),
website: sameJobs(PATH_SCOPES.website, ["website"], {
earlyInputs: [".agents/skills/reverse-engineer-anything/**"],
}),
};
// An input owner omitted from the registry must never silently lose a lane.
for (const [name, paths] of Object.entries(PATH_SCOPES))
+25 -16
View File
@@ -78,7 +78,6 @@ let correlationSequence = 0;
const shape = (value, depth = 0) => {
if (depth > 2 || value === null) return value === null ? "null" : "nested";
if (arrayIsArray(value)) return "array";
switch (typeof value) {
case "string":
return "string";
@@ -91,7 +90,12 @@ const shape = (value, depth = 0) => {
case "function":
return "function";
case "object":
return "object";
try {
return arrayIsArray(value) ? "array" : "object";
} catch {
// Revoked proxies retain their typeof identity but reject IsArray.
return "object";
}
default:
return "unknown";
}
@@ -1321,20 +1325,25 @@ try {
boundaries.patchApplicationEffects(electron.app);
boundaries.patchNativeAddonLoading();
boundaries.patchChildProcess();
process.once("uncaughtException", (cause, origin) => {
recordRuntime("process-lifecycle", "uncaught-exception", "observed", {
argument_shape_values: [cause, origin],
error: true,
});
throw cause;
});
process.once("unhandledRejection", (reason, promise) => {
recordRuntime("process-lifecycle", "unhandled-rejection", "observed", {
argument_shape_values: [reason, promise],
error: true,
});
throw reason;
});
// Observe dispatch without adding listeners: their presence changes Node's
// rejection fallback and Electron's uncaught-exception listener-count policy.
const processEmit = process.emit;
process.emit = function observedProcessEmit(event, ...args) {
if (this === process) {
const kind =
event === "unhandledRejection"
? "unhandled-rejection"
: event === "uncaughtExceptionMonitor"
? "uncaught-exception"
: null;
if (kind !== null)
recordRuntime("process-lifecycle", kind, "observed", {
argument_shape_values: args,
error: true,
});
}
return applyFunction(processEmit, this, arguments);
};
} catch (cause) {
globalThis.__reaElectronActiveHookError = true;
recordRuntime(
@@ -3,19 +3,19 @@
"snapshot_hash": "65817c30a78bb44c3dc3771876b6010a",
"engines": {
"x86_64": {
"dart_version": null,
"dart_version": "2.14.0-151.0.dev (dev) (Tue May 25 10:58:39 2021 -0700)",
"toolchain_lines": [
"Android (5900059 based on r365631c) clang version 9.0.8 (https://android.googlesource.com/toolchain/llvm-project 207d7abc1a2abf3ef8d4301736d6a7ebc224a290) (based on LLVM 9.0.8svn)"
]
},
"armeabi-v7a": {
"dart_version": null,
"dart_version": "2.14.0-151.0.dev (dev) (Tue May 25 10:58:39 2021 -0700)",
"toolchain_lines": [
"Android (5900059 based on r365631c) clang version 9.0.8 (https://android.googlesource.com/toolchain/llvm-project 207d7abc1a2abf3ef8d4301736d6a7ebc224a290) (based on LLVM 9.0.8svn)"
]
},
"arm64-v8a": {
"dart_version": null,
"dart_version": "2.14.0-151.0.dev (dev) (Tue May 25 10:58:39 2021 -0700)",
"toolchain_lines": [
"Android (5900059 based on r365631c) clang version 9.0.8 (https://android.googlesource.com/toolchain/llvm-project 207d7abc1a2abf3ef8d4301736d6a7ebc224a290) (based on LLVM 9.0.8svn)"
]
+26
View File
@@ -0,0 +1,26 @@
import { createServer } from "node:http";
/** Interpret request targets once, before a fixture routes or forwards them. */
export const createBrowserFixtureHttpServer = (handleRequest) =>
createServer((request, response) => {
let target;
try {
// A leading // is an origin-form path, not a replacement authority.
const requested = new URL(
request.url?.startsWith("/")
? `http://fixture.invalid${request.url}`
: request.url,
);
if (requested.protocol !== "http:" && requested.protocol !== "https:")
throw new TypeError("Unsupported fixture request protocol");
target = { pathname: requested.pathname, search: requested.search };
} catch {
response.writeHead(400, {
"Content-Type": "text/plain; charset=utf-8",
"X-Content-Type-Options": "nosniff",
});
response.end("Invalid HTTP request target");
return;
}
handleRequest(request, response, target);
});
@@ -1,11 +1,12 @@
import assert from "node:assert/strict";
import { once } from "node:events";
import { createServer, request } from "node:http";
import { request } from "node:http";
import { connect } from "node:net";
import { createBrowserScenarioProvider } from "../../dist/composition/browserScenario.js";
import { browserScenarioSchema } from "../../dist/domain/browserScenario.js";
import { PlaywrightScenarioServiceWorkers } from "../../dist/browser/PlaywrightScenarioServiceWorkers.js";
import { createBrowserFixtureHttpServer } from "./browser-fixture-http.mjs";
const beforeDeadline = async (promise, label, timeoutMs = 2_000) => {
let timer;
@@ -26,7 +27,7 @@ const beforeDeadline = async (promise, label, timeoutMs = 2_000) => {
// Real loopback proxy: Playwright's first transport is forwarded to Chrome,
// then only the policy owner's discovery/handshake is deliberately withheld.
const startProxy = async (endpoint, mode, ownerOnly = false) => {
export const startProxy = async (endpoint, mode, ownerOnly = false) => {
const upstream = new URL(endpoint);
const sockets = new Set();
let versions = 0;
@@ -47,38 +48,44 @@ const startProxy = async (endpoint, mode, ownerOnly = false) => {
socket.once("close", () => closed());
entered();
};
const server = createServer((incoming, response) => {
const versionRequest = incoming.url.replace(/\/$/, "") === "/json/version";
if (versionRequest) versions += 1;
if (
enabled &&
mode === "http-discovery" &&
versions === 2 &&
versionRequest
) {
stall(incoming.socket);
return;
}
const outgoing = request(new URL(incoming.url, upstream), (received) => {
const chunks = [];
received.on("data", (chunk) => chunks.push(chunk));
received.on("end", () => {
const content = Buffer.concat(chunks).toString("utf8");
response.writeHead(received.statusCode, {
"content-type": "application/json",
const server = createBrowserFixtureHttpServer(
(incoming, response, target) => {
const versionRequest =
target.pathname.replace(/\/$/, "") === "/json/version";
if (versionRequest) versions += 1;
if (
enabled &&
mode === "http-discovery" &&
versions === 2 &&
versionRequest
) {
stall(incoming.socket);
return;
}
const destination = new URL(upstream);
destination.pathname = target.pathname;
destination.search = target.search;
const outgoing = request(destination, (received) => {
const chunks = [];
received.on("data", (chunk) => chunks.push(chunk));
received.on("end", () => {
const content = Buffer.concat(chunks).toString("utf8");
response.writeHead(received.statusCode, {
"content-type": "application/json",
});
response.end(
content.replaceAll(
`127.0.0.1:${upstream.port}`,
`127.0.0.1:${server.address().port}`,
),
);
});
response.end(
content.replaceAll(
`127.0.0.1:${upstream.port}`,
`127.0.0.1:${server.address().port}`,
),
);
});
});
outgoing.on("error", () => response.destroy());
response.on("close", () => outgoing.destroy());
outgoing.end();
});
outgoing.on("error", () => response.destroy());
response.on("close", () => outgoing.destroy());
outgoing.end();
},
);
server.on("connection", (socket) => {
sockets.add(socket);
socket.on("close", () => sockets.delete(socket));
@@ -1,6 +1,5 @@
import assert from "node:assert/strict";
import { once } from "node:events";
import { createServer } from "node:http";
import { setTimeout as delay } from "node:timers/promises";
import { chromium } from "playwright-core";
@@ -11,6 +10,7 @@ import { createBrowserScenarioProvider } from "../../dist/composition/browserSce
import { browserScenarioSchema } from "../../dist/domain/browserScenario.js";
import { runScenarioCli } from "./browser-scenario-verifier.mjs";
import { verifyScenarioPolicySetup } from "./browser-scenario-service-worker-setup-e2e.mjs";
import { createBrowserFixtureHttpServer } from "./browser-fixture-http.mjs";
const diagnosticError = (error) =>
error instanceof Error
@@ -26,38 +26,38 @@ const diagnosticError = (error) =>
}
: { kind: typeof error };
const startSite = async () => {
export const startSite = async () => {
let otherOrigin;
const server = createServer((request, response) => {
const path = new URL(request.url, "http://localhost").pathname;
if (path === "/sw.js") {
response.setHeader("content-type", "application/javascript");
response.end(`self.addEventListener("install", () => self.skipWaiting());
const server = createBrowserFixtureHttpServer(
(_request, response, { pathname: path }) => {
if (path === "/sw.js") {
response.setHeader("content-type", "application/javascript");
response.end(`self.addEventListener("install", () => self.skipWaiting());
self.addEventListener("activate", event => event.waitUntil(clients.claim()));
self.addEventListener("fetch", event => {
if (new URL(event.request.url).pathname === "/probe")
event.respondWith(new Response("worker"));
});`);
} else if (path === "/probe") {
response.end("network");
} else if (path === "/failed-navigation") {
response.destroy();
} else if (path === "/stalled-image.png") {
response.writeHead(200, { "content-type": "image/png" });
response.flushHeaders();
} else if (path === "/pending-image" || path === "/pending-child") {
response.setHeader("content-type", "text/html");
response.end(
'<!doctype html><img id="pending-image" src="/stalled-image.png">',
);
} else if (path === "/pending-frame") {
response.setHeader("content-type", "text/html");
response.end(
'<!doctype html><iframe id="pending-frame" src="/pending-child"></iframe>',
);
} else {
response.setHeader("content-type", "text/html");
response.end(`<!doctype html><p id="state">ready</p>
} else if (path === "/probe") {
response.end("network");
} else if (path === "/failed-navigation") {
response.destroy();
} else if (path === "/stalled-image.png") {
response.writeHead(200, { "content-type": "image/png" });
response.flushHeaders();
} else if (path === "/pending-image" || path === "/pending-child") {
response.setHeader("content-type", "text/html");
response.end(
'<!doctype html><img id="pending-image" src="/stalled-image.png">',
);
} else if (path === "/pending-frame") {
response.setHeader("content-type", "text/html");
response.end(
'<!doctype html><iframe id="pending-frame" src="/pending-child"></iframe>',
);
} else {
response.setHeader("content-type", "text/html");
response.end(`<!doctype html><p id="state">ready</p>
<a id="navigate" href="${otherOrigin}/">Navigate</a>
${path === "/frame" ? `<iframe src="${otherOrigin}/"></iframe>` : ""}
${
@@ -72,8 +72,9 @@ const startSite = async () => {
</script>`
: ""
}`);
}
});
}
},
);
server.listen(0, "127.0.0.1");
await once(server, "listening");
return {
+23 -22
View File
@@ -1,5 +1,4 @@
import assert from "node:assert/strict";
import { createServer } from "node:http";
import { once } from "node:events";
import { setTimeout as delay } from "node:timers/promises";
@@ -11,6 +10,7 @@ import {
scenarioProfiles,
} from "./browser-scenario-verifier.mjs";
import { withVerifierCleanup } from "./verifier-cleanup.mjs";
import { createBrowserFixtureHttpServer } from "./browser-fixture-http.mjs";
const scenarioFor = (browser, origin) =>
browserScenarioSchema.parse({
@@ -149,28 +149,29 @@ const storagePage = (kind, port) => {
</script></body></html>`;
};
const startStorageSite = async () => {
export const startStorageSite = async () => {
let port = 0;
const server = createServer((request, response) => {
const path = new URL(request.url ?? "/", "http://127.0.0.1").pathname;
const kind =
path === "/storage-main"
? "main"
: path === "/storage-frame"
? "frame"
: path === "/storage-debugger"
? "debugger"
: path === "/storage-top"
? "top"
: undefined;
if (kind === undefined) {
response.writeHead(404).end();
return;
}
response.setHeader("content-type", "text/html; charset=utf-8");
response.end(storagePage(kind, port));
});
server.listen(0, "::");
const server = createBrowserFixtureHttpServer(
(_request, response, { pathname: path }) => {
const kind =
path === "/storage-main"
? "main"
: path === "/storage-frame"
? "frame"
: path === "/storage-debugger"
? "debugger"
: path === "/storage-top"
? "top"
: undefined;
if (kind === undefined) {
response.writeHead(404).end();
return;
}
response.setHeader("content-type", "text/html; charset=utf-8");
response.end(storagePage(kind, port));
},
);
server.listen(0, "127.0.0.1");
await once(server, "listening");
port = server.address().port;
return {
+16 -21
View File
@@ -253,20 +253,9 @@ async function verifyNativeValueAdapters(
functionEvidence.analysis_profile,
);
assert.deepEqual(functionProfile, upstreamProfile);
const comparison = await client.callTool(
{
name: "compare_functions",
arguments: {
left: analyzed.structuredContent,
right: analyzed.structuredContent,
},
},
{ timeout: 180000 },
);
assert.notEqual(comparison.isError, true, JSON.stringify(comparison));
assert.notEqual(comparison.structuredContent, undefined);
const comparisonEvidence = parseEvidence(
requireMcpOutput(comparison, "compare_functions"),
const comparisonEvidence = await compareRetainedFunctionEvidence(
client,
functionEvidence,
);
assert.equal(
comparisonEvidence.normalized_result.status,
@@ -395,21 +384,27 @@ async function verifyUnresolvedCallEvidence(client) {
assert.equal(trace.goal_status, "unknown");
assert.equal(trace.truncated, true);
assert.ok(trace.residual_unknowns.length > 0);
const compared = await compareRetainedFunctionEvidence(client, evidence);
assert.equal(compared.normalized_result.status, "unknown");
}
/** Compare an already admitted function record without retransmitting its payload. */
export async function compareRetainedFunctionEvidence(client, evidence) {
const reference = {
kind: "retained-evidence",
evidence_id: evidence.evidence_id,
};
const compared = await client.callTool(
{
name: "compare_functions",
arguments: {
left: analyzed.structuredContent,
right: analyzed.structuredContent,
left: reference,
right: reference,
},
},
{ timeout: 180000 },
);
assert.notEqual(compared.isError, true, JSON.stringify(compared));
assert.notEqual(compared.structuredContent, undefined);
assert.equal(
parseEvidence(requireMcpOutput(compared, "compare_functions"))
.normalized_result.status,
"unknown",
);
return parseEvidence(requireMcpOutput(compared, "compare_functions"));
}
+39
View File
@@ -87,11 +87,50 @@ export async function verifyGhidraEntryAliases({
(await call("procedure_info", { procedure: alias.value })).entrypoint,
alias.address,
);
// Search matches the names exact lookup accepts, as one procedure row
// under its primary name.
const escaped = alias.value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
for (const query of [
{ pattern: alias.value },
{ pattern: `^${escaped}$`, mode: "regex", case_sensitive: true },
]) {
assert.deepEqual(
(await call("search_procedures", query))
.filter(({ address }) => address === alias.address)
.map(({ value, matched_aliases }) => ({ value, matched_aliases })),
[{ value: canonical.value, matched_aliases: [alias.value] }],
`Procedure search missed the entry alias for ${JSON.stringify(query)}`,
);
}
assert.ok(
(await call("list_procedures")).every(
(row) => row.matched_aliases === undefined,
),
"Listings must not report search matches",
);
const traced = await call("trace_feature", { query: alias.value });
assert.ok(
traced.matches.some(
({ type, address, matched_aliases }) =>
type === "procedure" &&
address === alias.address &&
matched_aliases?.includes(alias.value) === true,
),
"trace_feature missed the procedure its entry alias names",
);
const interior = names.find(
({ value }) => value === `${prefix}rea_interior`,
);
assert.ok(interior);
assert.notEqual(interior.address, alias.address);
assert.ok(
(await call("search_procedures", { pattern: interior.value })).every(
({ address, matched_aliases = [] }) =>
address !== interior.address &&
!matched_aliases.includes(interior.value),
),
"An interior label must not match procedure search",
);
assert.equal(
interior.symbol.type,
"label",
@@ -68,6 +68,21 @@ export async function verifyGhidraNamespaceAnnotations({
assert.equal(ambiguous.code, "invalid_request");
for (const { address } of entries)
assert.ok(JSON.stringify(ambiguous.details.issues).includes(address));
// Search accepts the leaf spelling exact lookup accepts, keeping each
// procedure as one row under its qualified primary name.
const leafMatches = await call("search_procedures", {
pattern: "^same$",
mode: "regex",
case_sensitive: true,
});
for (const { namespace, address } of entries)
assert.deepEqual(
leafMatches
.filter((row) => row.address === address)
.map(({ value }) => value),
[`${namespace}::same`],
`Leaf-name search missed ${namespace}::same`,
);
for (const { namespace, address } of entries) {
const leaf = await call("annotate_native_function", {
procedure: address,
@@ -0,0 +1,92 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { parseEvidence } from "../../dist/domain/evidence.js";
import { PrivateRuntimeRoot } from "../../dist/process/PrivateRuntimeRoot.js";
import { withVerifierCleanup } from "./verifier-cleanup.mjs";
import { createVerifierCommands } from "./verify-commands.mjs";
const source = fileURLToPath(
new URL(
"../../tests/conformance/ghidra/relocation-symbol.c",
import.meta.url,
),
);
/** Verify named and unnamed ELF relocations through the shipped CLI and Ghidra. */
export async function verifyGhidraRelocationSymbols({ clang, lld, env }) {
const commands = await createVerifierCommands();
let root;
let commandsClosed = false;
return withVerifierCleanup(async () => {
root = await PrivateRuntimeRoot.create({ prefix: "rea-relocation-proof-" });
const object = join(root.path, "relocation-symbol.o");
const target = join(root.path, "relocation-symbol.so");
const commandEnv = {
...env,
TMPDIR: root.path,
TMP: root.path,
TEMP: root.path,
};
const run = (command, args) =>
commands.exec(command, args, {
env: commandEnv,
timeout: 120000,
maxBuffer: 16 * 1024 * 1024,
});
await run(clang, [
"--target=x86_64-linux-gnu",
"-fPIC",
"-O0",
"-c",
source,
"-o",
object,
]);
await run(lld, ["-shared", object, "-o", target]);
const hash = async () =>
createHash("sha256")
.update(await readFile(target))
.digest("hex");
const before = await hash();
const { stdout } = await run(process.execPath, [
"scripts/rea.mjs",
"inspect-native-load-image",
target,
"--provider",
"ghidra",
"--json",
]);
const evidence = parseEvidence(JSON.parse(stdout));
assert.equal(evidence.subject?.digest.sha256, before);
assert.equal(evidence.provider.id, "ghidra");
const report = evidence.normalized_result;
assert.equal(report.status, "unsupported");
const relocations = report.observations.relocations;
assert.ok(
relocations.some(({ symbol }) => symbol === "rea_external"),
"The Ghidra relocation must retain its imported symbol",
);
assert.ok(
relocations.some(({ symbol }) => symbol === ""),
"The provider's empty symbol must remain distinct from an unrecorded field",
);
assert.equal(await hash(), before);
return {
source,
target_sha256: before,
named_symbol: "rea_external",
empty_symbol: true,
};
}, [
async () => {
await commands.close();
commandsClosed = true;
},
async () => {
if (commandsClosed && root !== undefined) await root.close();
},
]);
}
+2
View File
@@ -1,5 +1,7 @@
export function applyReleasePleasePatch(directory?: string): Promise<void>;
export function describeReleaseProposalError(error: unknown): string;
export function releaseProposalSearchDepth(
run: (command: string, args: string[]) => Promise<string>,
sourceSha: string,
+9
View File
@@ -13,6 +13,15 @@ const patch = fileURLToPath(
new URL("../../patches/release-please+17.3.0.patch", import.meta.url),
);
export function describeReleaseProposalError(error) {
if (!(error instanceof Error)) return String(error);
const description = error.message || error.name || "Unknown error";
const status = error.status;
return Number.isInteger(status) && status >= 400 && status <= 599
? `${description} (HTTP ${status})`
: description;
}
export async function applyReleasePleasePatch(
directory = dirname(require.resolve("release-please/package.json")),
) {
+5
View File
@@ -10,5 +10,10 @@ export function compareTestDiscovery(
readonly file: string;
readonly projects: readonly string[];
}[];
readonly wrongProjects: readonly {
readonly file: string;
readonly expected: string;
readonly actual: string;
}[];
readonly discovered: number;
};
+9
View File
@@ -1,4 +1,5 @@
import { relative, resolve } from "node:path";
import { projectForTest } from "./test-projects.mjs";
/** Compare actual Vitest discovery with repository-owned test paths. */
export const compareTestDiscovery = (expectedPaths, discovery, root) => {
@@ -36,6 +37,14 @@ export const compareTestDiscovery = (expectedPaths, discovery, root) => {
const projects = projectsByFile.get(file);
return projects.length > 1 ? [{ file, projects: sorted(projects) }] : [];
}),
wrongProjects: sorted(expected).flatMap((file) => {
const projects = projectsByFile.get(file);
if (!projects || projects.length !== 1) return [];
const expectedProject = projectForTest(file).name;
return projects[0] === expectedProject
? []
: [{ file, expected: expectedProject, actual: projects[0] }];
}),
discovered: projectsByFile.size,
};
};
+3
View File
@@ -7,6 +7,7 @@ import xml.etree.ElementTree as ET
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
from website import PageMetadata, page_url
from website_agents import prepare_discovery, prepare_markdown
EXAMPLE_FILES = (
@@ -81,6 +82,8 @@ def prepare_assets(site):
prepare_sharing_image(site)
prepare_sitemap(site)
prepare_notes_example(site)
prepare_discovery(Path(__file__).resolve().parent.parent, site)
prepare_markdown(site)
if __name__ == "__main__":
+2 -1
View File
@@ -4,6 +4,7 @@ import { appendFile } from "node:fs/promises";
import { parseArgs, promisify } from "node:util";
import {
applyReleasePleasePatch,
describeReleaseProposalError,
releaseProposalSearchDepth,
} from "./lib/release-proposal.mjs";
@@ -63,7 +64,7 @@ async function main() {
await main().catch((error) => {
process.stderr.write(
`Release proposal failed: ${error instanceof Error ? error.message : String(error)}\n`,
`Release proposal failed: ${describeReleaseProposalError(error)}\n`,
);
process.exitCode = 1;
});
+109
View File
@@ -0,0 +1,109 @@
"""Check the public build artifacts and Markdown content contract."""
import hashlib
import json
from pathlib import Path
import tempfile
import unittest
from zipfile import ZipFile
from bs4 import BeautifulSoup
import yaml
from website_agents import prepare_discovery, prepare_markdown
ROOT = Path(__file__).resolve().parent.parent
class AgentPublishingChecks(unittest.TestCase):
def test_published_skill_digest_covers_every_reference_and_catalogs_resolve(self):
site = ROOT / "website/_site"
index = json.loads((site / ".well-known/agent-skills/index.json").read_text())
self.assertEqual(index["$schema"], "https://schemas.agentskills.io/discovery/0.2.0/schema.json")
skill, = index["skills"]
self.assertEqual(skill["name"], "reverse-engineer-anything")
self.assertEqual(skill["type"], "archive")
artifact = site / skill["url"].removeprefix("https://rea.tools/")
self.assertEqual(skill["digest"], "sha256:" + hashlib.sha256(artifact.read_bytes()).hexdigest())
source = ROOT / ".agents/skills/reverse-engineer-anything"
with ZipFile(artifact) as archive:
expected = {f"{source.name}/{p.relative_to(source).as_posix()}": p.read_bytes()
for p in source.rglob("*.md")}
self.assertEqual(set(archive.namelist()), set(expected))
for name, data in expected.items():
self.assertEqual(archive.read(name), data)
manifest = archive.read(f"{source.name}/SKILL.md").decode()
metadata = yaml.safe_load(manifest[4:].split("\n---\n", 1)[0])
self.assertEqual(skill["description"], metadata["description"])
catalog = json.loads((site / ".well-known/ai-catalog.json").read_text())
self.assertEqual(catalog["specVersion"], "1.0")
entry, = catalog["entries"]
self.assertEqual(entry["identifier"], "urn:air:rea.tools:skill:reverse-engineer-anything")
self.assertEqual(entry["url"], skill["url"])
self.assertNotIn("data", entry)
ard = json.loads((site / ".well-known/ard.json").read_text())
resource, = ard["entries"]
self.assertEqual(resource["url"], skill["url"])
self.assertTrue(2 <= len(resource["representativeQueries"]) <= 5)
def test_reference_changes_invalidate_digest_and_yaml_scalars_are_parsed(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
source = root / ".agents/skills/reverse-engineer-anything"
source.mkdir(parents=True)
(source / "SKILL.md").write_text(
'---\nname: reverse-engineer-anything\ndescription: >-\n Inspect programs:\n preserve evidence.\n---\n# REA\n'
)
(source / "references").mkdir()
reference = source / "references/evidence.md"
reference.write_text("First revision")
site = root / "site"
def build():
prepare_discovery(root, site)
return json.loads((site / ".well-known/agent-skills/index.json").read_text())["skills"][0]
before = build()
self.assertEqual(before["description"], "Inspect programs: preserve evidence.")
self.assertEqual(build(), before)
reference.write_text("Second revision")
self.assertNotEqual(build()["digest"], before["digest"])
def test_markdown_preserves_content_and_resources_without_publishing_excluded_pages(self):
with tempfile.TemporaryDirectory() as directory:
site = Path(directory)
page = site / "zh-CN/guides/test/index.html"
page.parent.mkdir(parents=True)
page.write_text('''<html><head><title>REA: test</title></head><body>
<nav>Outside navigation</nav><main><aside>Shortened display paths.</aside>
<h1>Inspect a program</h1><nav>Contents navigation</nav>
<ol><li><span class="flow-label">Read</span><span class="flow-node"><code>input.exe</code></span><span class="flow-note">Keep evidence.</span></li></ol>
<p><a href="../other/?q=a%2Fb#code">Another guide</a></p>
<pre><code>const value = a &lt; b ? "x_y" : "&amp;";\n keepIndent();</code></pre>
<details><summary>Evidence</summary><p>Hidden evidence remains available.</p></details>
<img src="../../../assets/diagram.svg" alt="Investigation diagram">
<button>Copy code</button><script>doNotPublish()</script></main></body></html>''')
fixture = site / "examples/notes-web/index.html"
fixture.parent.mkdir(parents=True)
fixture.write_text("<main>Target application</main>")
fallback = site / "zh-CN/fallback/index.html"
fallback.parent.mkdir(parents=True)
fallback.write_text('<head><meta name="robots" content="noindex, follow"></head><main>English fallback notice.</main>')
prepare_markdown(site)
markdown = page.with_suffix(".md").read_text()
self.assertIn('# Inspect a program', markdown)
self.assertIn('const value = a < b ? "x_y" : "&";\n keepIndent();', markdown)
self.assertIn("Hidden evidence remains available.", markdown)
self.assertIn("Shortened display paths.", markdown)
self.assertNotIn("Read`input.exe`", markdown)
self.assertIn("https://rea.tools/zh-CN/guides/other/?q=a%2Fb#code", markdown)
self.assertIn("https://rea.tools/assets/diagram.svg", markdown)
for text in ("Outside navigation", "Contents navigation", "Copy code", "doNotPublish"):
self.assertNotIn(text, markdown)
self.assertFalse(fixture.with_suffix(".md").exists())
self.assertFalse(fallback.with_suffix(".md").exists())
html = BeautifulSoup(page.read_text(), "html.parser")
self.assertEqual(html.find("link", rel="alternate")["href"], "https://rea.tools/zh-CN/guides/test/index.md")
if __name__ == "__main__":
unittest.main()
+4 -2
View File
@@ -539,11 +539,13 @@ class WebsiteMaintenanceChecks(unittest.TestCase):
self.assertIn(b"color: black", response.read())
with self.assertRaises(HTTPError) as error:
urlopen(base + prefix + "missing")
self.assertEqual(error.exception.code, 404)
with contextlib.closing(error.exception) as response:
self.assertEqual(response.code, 404)
if prefix != "/":
with self.assertRaises(HTTPError) as error:
urlopen(base + "/guide/")
self.assertEqual(error.exception.code, 404)
with contextlib.closing(error.exception) as response:
self.assertEqual(response.code, 404)
finally:
server.shutdown()
worker.join()
+9 -78
View File
@@ -15,8 +15,10 @@ if (options.help) {
process.stdout.write(
"Usage: npm run verify:adb [-- --serial SERIAL --pull PACKAGE]\n" +
"Requires a caller-selected adb binary (REA_ADB_PATH or PATH) and a\n" +
"connected device or emulator. Verifies all five operations against\n" +
"the live device; --pull acquires a real package and re-digests the\n" +
"connected device or emulator. Verifies observation and transfer operations\n" +
"against the live device. --install-apk is unavailable until APK identity\n" +
"and device cleanup ownership can be established safely.\n" +
"--pull acquires a real package and re-digests the\n" +
"files on disk against the returned SHA-256 values.\n",
);
process.exit(0);
@@ -356,79 +358,6 @@ try {
`versionCode=${String(details.version_code)}`,
);
}
if (options.installApk !== null && options.installApk !== undefined) {
const probePackage = "com.rea.adb.probe";
const installedProbe = await run("install_adb_package", {
serial,
apk_path: options.installApk,
replace: false,
});
if (installedProbe !== null) {
check(
"install reports the APK digest",
typeof installedProbe.sha256 === "string" &&
installedProbe.sha256.length === 64,
`${installedProbe.bytes} bytes`,
);
const resolved = await run("resolve_adb_packages", {
serial,
query: "rea.adb.probe",
});
if (resolved !== null)
check(
"resolve finds the installed probe uniquely",
resolved.matches.length === 1 &&
resolved.matches[0]?.package_name === probePackage,
`${resolved.matches.length} match(es)`,
);
const started = await run("start_adb_app", {
serial,
package: probePackage,
});
if (started !== null)
check(
"app start resolves and launches the launcher activity",
typeof started.component === "string" &&
started.component.startsWith(probePackage),
String(started.component),
);
const observedProcesses = await run("list_adb_processes", { serial });
if (observedProcesses !== null)
check(
"started app appears in the process listing",
observedProcesses.processes.some(
(process) => process.name === probePackage,
),
);
const stopped = await run("stop_adb_app", {
serial,
package: probePackage,
});
if (stopped !== null)
check(
"force-stop completes the dynamic-analysis run",
stopped.package_name === probePackage,
);
const removed = await run("uninstall_adb_package", {
serial,
package: probePackage,
});
if (removed !== null)
check(
"uninstall completes the lifecycle",
removed.package_name === probePackage,
);
const afterRemoval = await run("resolve_adb_packages", {
serial,
query: "rea.adb.probe",
});
if (afterRemoval !== null)
check(
"resolve reports zero matches after removal",
afterRemoval.matches.length === 0,
);
}
}
} finally {
await rm(transferRoot, { recursive: true, force: true });
}
@@ -459,7 +388,7 @@ if (failures > 0) {
}
function parseArguments(argv) {
const parsed = { help: false, serial: null, pull: null, installApk: null };
const parsed = { help: false, serial: null, pull: null };
for (let index = 0; index < argv.length; index += 1) {
const argument = argv[index];
if (argument === "--help" || argument === "-h") parsed.help = true;
@@ -470,8 +399,10 @@ function parseArguments(argv) {
parsed.pull = argv[index + 1] ?? null;
index += 1;
} else if (argument === "--install-apk") {
parsed.installApk = argv[index + 1] ?? null;
index += 1;
process.stderr.write(
"--install-apk is unavailable: APK identity and device cleanup ownership cannot be established safely\n",
);
process.exit(1);
} else {
process.stderr.write(`unknown argument: ${argument}\n`);
process.exit(2);
+21 -16
View File
@@ -38,15 +38,16 @@ if (
!isAbsolute(strace ?? "")
)
throw new Error(
"verify:binary:layout requires Linux x64, absolute REA_PWNTOOLS_PYTHON (pwntools 4.15.0/pyelftools 0.33/Unicorn 2.1.2) and REA_VERIFY_STRACE_COMMAND. gcc, ld, strip and the i686/ARM/MIPS/MIPSel linux-gnu cross gcc and readelf must be available. No target is executed.",
"verify:binary:layout requires Linux x64, absolute REA_PWNTOOLS_PYTHON (pwntools 4.15.0/pyelftools 0.33/Unicorn 2.1.2) and REA_VERIFY_STRACE_COMMAND. gcc, ld, strip and the AArch64/i686/ARM/MIPS/MIPSel linux-gnu cross gcc and readelf must be available. No target is executed.",
);
const run = createVerifierRun();
const execute = promisify(execFile);
const ELF32_TOOLCHAINS = [
["i686-linux-gnu", "EM_386", "little"],
["arm-linux-gnueabi", "EM_ARM", "little"],
["mips-linux-gnu", "EM_MIPS", "big"],
["mipsel-linux-gnu", "EM_MIPS", "little"],
const CROSS_TOOLCHAINS = [
["aarch64-linux-gnu", "EM_AARCH64", 64, "little"],
["i686-linux-gnu", "EM_386", 32, "little"],
["arm-linux-gnueabi", "EM_ARM", 32, "little"],
["mips-linux-gnu", "EM_MIPS", 32, "big"],
["mipsel-linux-gnu", "EM_MIPS", 32, "little"],
];
for (const command of [
"gcc",
@@ -54,7 +55,7 @@ for (const command of [
"strip",
"/usr/bin/prlimit",
strace,
...ELF32_TOOLCHAINS.flatMap(([prefix]) => [
...CROSS_TOOLCHAINS.flatMap(([prefix]) => [
`${prefix}-gcc`,
`${prefix}-readelf`,
]),
@@ -347,14 +348,15 @@ try {
cases++;
}
}
// ELF32 profiles: cross-compiled, never executed, checked against GNU readelf.
// Cross profiles: cross-compiled, never executed, checked against GNU readelf.
for (const [
prefix,
machine,
bits,
byteOrder,
kind,
flags,
] of ELF32_TOOLCHAINS.flatMap((toolchain) => [
] of CROSS_TOOLCHAINS.flatMap((toolchain) => [
[...toolchain, "executable", ["-fPIE", "-pie"]],
[...toolchain, "relocatable", ["-c", "-fPIC"]],
])) {
@@ -423,16 +425,18 @@ try {
`readelf relocation count: ${name}`,
);
for (const [entryIndex, [, offset, info]] of rows.entries()) {
// ELF32 r_info uses the low byte for type and the upper 24 bits for
// the symbol index. readelf has already decoded the file's byte order.
// ELF32 r_info packs an 8-bit type under a 24-bit symbol index;
// ELF64 packs a 32-bit type under a 32-bit index. readelf has already
// decoded the file's byte order.
const decodedInfo = BigInt(`0x${info}`);
const typeBits = bits === 32 ? 8n : 32n;
expectedRelocations.push({
section_index: section.index,
entry_index: entryIndex,
reported_offset: BigInt(`0x${offset}`),
type: Number(decodedInfo & 0xffn),
type: Number(decodedInfo & ((1n << typeBits) - 1n)),
symbol_table_index: section.link,
symbol_index: Number(decodedInfo >> 8n),
symbol_index: Number(decodedInfo >> typeBits),
});
}
}
@@ -445,7 +449,7 @@ try {
const value = await inspect(mode, path);
assert.deepEqual(value.architecture, {
machine,
bits: 32,
bits,
byte_order: byteOrder,
});
assert.equal(
@@ -1312,7 +1316,8 @@ try {
}
}
}
unsupported.writeUInt16LE(183, 18);
// EM_RISCV: AArch64 (183) is now an admitted profile.
unsupported.writeUInt16LE(243, 18);
const core = Buffer.from(object);
core.writeUInt16LE(4, 16);
for (const [name, bytes, category] of [
@@ -1337,7 +1342,7 @@ try {
"invalid_input",
],
["undersized-program-header", undersizedProgramHeader, "invalid_input"],
["arm64", unsupported, "unsupported_target"],
["riscv64", unsupported, "unsupported_target"],
["core", core, "unsupported_target"],
]) {
const path = join(root.path, name);
+3 -2
View File
@@ -8,7 +8,7 @@ import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
import { createVerifierCommands } from "./lib/verify-commands.mjs";
const root = await PrivateRuntimeRoot.create({ prefix: "rea-bun-verifier-" });
const commands = await createVerifierCommands();
let commands;
const run = (command, args, options = {}) =>
commands.exec(command, args, {
timeout: 120_000,
@@ -19,6 +19,7 @@ const run = (command, args, options = {}) =>
// only after every owned command family has settled.
let commandsClosed = false;
await withVerifierCleanup(async () => {
commands = await createVerifierCommands();
const source = join(root.path, "hello.ts");
await writeFile(source, 'console.log("REA_BUN_VERIFICATION");\n');
const docker = process.argv.includes("--docker");
@@ -104,7 +105,7 @@ await withVerifierCleanup(async () => {
);
}, [
async () => {
await commands.close();
if (commands !== undefined) await commands.close();
commandsClosed = true;
},
async () => {
+5 -2
View File
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { isAbsolute, join } from "node:path";
import { fileURLToPath } from "node:url";
import Ajv2020 from "ajv/dist/2020.js";
import { createCatalogIdentity } from "../dist/catalogIdentity.js";
@@ -15,6 +15,7 @@ import {
TOOL_CONTRACTS,
toolContract,
} from "../dist/contracts/toolContracts.js";
import { isPathWithinRoot } from "../dist/domain/localPath.js";
import { createVerifierCommands } from "./lib/verify-commands.mjs";
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
import { saveVerifierCommandOutput } from "./lib/verifier-command-output.mjs";
@@ -306,7 +307,9 @@ await withVerifierCleanup(async () => {
assert(
parsed.plannedActions.every(
(a) =>
a.target === configPath || a.target.startsWith(skillDirectory),
a.target === configPath ||
(isAbsolute(a.target) &&
isPathWithinRoot(skillDirectory, a.target)),
),
);
}
+31 -8
View File
@@ -6,6 +6,7 @@ import { isAbsolute, join, relative } from "node:path";
import { fileURLToPath } from "node:url";
import Ajv2020 from "ajv/dist/2020.js";
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
import { isPathWithinRoot } from "../dist/domain/localPath.js";
import { createVerifierCommands } from "./lib/verify-commands.mjs";
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
import { saveVerifierCommandOutput } from "./lib/verifier-command-output.mjs";
@@ -54,6 +55,7 @@ const expectedNames = TOOL_CONTRACTS.map(
).sort();
let clientVersion = null;
let expectedSkill;
let skillCallId;
let inventoryChecked = false;
let catalogSize = null;
let skillLoaded = false;
@@ -180,10 +182,24 @@ await withVerifierCleanup(async () => {
finalSeen = true;
return { role: "assistant", content: "REA_CLIENT_COMPATIBILITY_OK" };
}
if (stage === 0)
return call("activate_skill", { name: "reverse-engineer-anything" });
if (stage === 0) {
const request = call("activate_skill", {
name: "reverse-engineer-anything",
});
skillCallId = request.tool_calls[0].id;
return request;
}
assert(
strings.some((text) => text.includes(expectedSkill)),
skillCallId !== undefined &&
expectedSkill !== undefined &&
body.messages.some(
(message) =>
message.role === "tool" &&
message.tool_call_id === skillCallId &&
textValues(message.content).some((text) =>
text.includes(expectedSkill),
),
),
"The model must receive the complete activated skill body",
);
skillLoaded = true;
@@ -322,8 +338,8 @@ await withVerifierCleanup(async () => {
result.plannedActions.every(
(action) =>
action.target === configPath ||
action.target === skillDirectory ||
action.target.startsWith(skillDirectory + "/"),
(isAbsolute(action.target) &&
isPathWithinRoot(skillDirectory, action.target)),
),
"Refuse setup writes outside the disposable account's configuration and skill",
);
@@ -342,9 +358,16 @@ await withVerifierCleanup(async () => {
assert.deepEqual(repeated.appliedActions, []);
assert.equal(await readFile(configPath, "utf8"), configured);
assert.equal(await readFile(`${configPath}.rea.backup`, "utf8"), original);
expectedSkill = (await readFile(join(skillDirectory, "SKILL.md"), "utf8"))
.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "")
.trim();
const installedSkill = await readFile(
join(skillDirectory, "SKILL.md"),
"utf8",
);
const frontmatter = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/u.exec(
installedSkill,
);
assert(frontmatter, "Installed skill must have authored frontmatter");
expectedSkill = installedSkill.slice(frontmatter[0].length).trim();
assert(expectedSkill.length > 0, "Installed skill body must not be empty");
const response = await saveVerifierCommandOutput(
() =>
commands.exec(
+3 -2
View File
@@ -12,6 +12,7 @@ import {
import { advertisedInputJsonSchema } from "../dist/contracts/toolSchemaMetadata.js";
import { presentAdvertisedInputSchema } from "../dist/contracts/compactInputSchema.js";
import { compactPresentationForMcpInputSchemaProfile } from "../dist/config/mcpInputSchemaProfile.js";
import { isPathWithinRoot } from "../dist/domain/localPath.js";
import { createVerifierCommands } from "./lib/verify-commands.mjs";
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
import { saveVerifierCommandOutput } from "./lib/verifier-command-output.mjs";
@@ -300,8 +301,8 @@ await withVerifierCleanup(async () => {
parsed.plannedActions.every(
(a) =>
a.target === configPath ||
a.target === skillDirectory ||
a.target.startsWith(skillDirectory + "/"),
(isAbsolute(a.target) &&
isPathWithinRoot(skillDirectory, a.target)),
),
);
}
+37 -9
View File
@@ -56,6 +56,7 @@ let catalog;
let patternCount = 0;
let skillRequested = false;
let skillLoaded = false;
let expectedSkillBody;
let evidenceSeen = false;
let finalSeen = false;
let artifactDigest;
@@ -169,11 +170,34 @@ await withVerifierCleanup(async () => {
const toolMessages = (body.messages ?? []).filter(
(message) => message.role === "tool",
);
const serialized = JSON.stringify(toolMessages);
skillLoaded ||=
serialized.includes(
'<skill_content name=\\"reverse-engineer-anything\\">',
) && serialized.includes(dirname(skillPath));
skillLoaded ||= toolMessages.some((message) => {
if (
typeof message.content !== "string" ||
expectedSkillBody === undefined
)
return false;
const open =
'<skill_content name="reverse-engineer-anything">\n<skill_resources>\n';
const start = message.content.indexOf(open);
if (start < 0) return false;
const resourcesStart = start + open.length;
const instructions = `\n</skill_resources>\n\n<skill_instructions>\n${expectedSkillBody}\n</skill_instructions>\n</skill_content>`;
const resourcesEnd = message.content.indexOf(
instructions,
resourcesStart,
);
if (resourcesEnd < 0) return false;
const expectedPath = dirname(skillPath)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;");
const resources = message.content.slice(resourcesStart, resourcesEnd);
return (
!resources.includes("</skill_content>") &&
!resources.includes("</skill_resources>") &&
resources.includes(expectedPath)
);
});
for (const message of toolMessages) {
const found = findClientResult(
message.content,
@@ -281,11 +305,15 @@ await withVerifierCleanup(async () => {
const configured = await readFile(configPath, "utf8");
assert.deepEqual(withoutRea(configured), withoutRea(original));
assert(configured.includes("!!js process.cwd()"));
assert(
(await readFile(skillPath, "utf8")).includes(
"name: reverse-engineer-anything",
),
const installedSkill = await readFile(skillPath, "utf8");
assert(installedSkill.includes("name: reverse-engineer-anything"));
const frontmatter = /^---\r?\n[\s\S]*?^---(?:\r?\n|$)/mu.exec(
installedSkill,
);
assert(frontmatter, "Installed skill must have its authored frontmatter");
expectedSkillBody = installedSkill.slice(frontmatter[0].length).trim();
assert(expectedSkillBody.length > 0);
lifecycle.push("setup");
const repeated = await runRea(
["setup", "--client", "dsh", "--yes"],
+4
View File
@@ -104,6 +104,10 @@ await withVerifierCleanup(
}
// The same checks apply to the default selection and each remaining ABI.
const preferred = await run("inspect_dart_aot", { path: apk });
assert.ok(
abis.some((abi) => abi.abi === preferred.abi),
"Default AOT selection must be one of the identified ABIs",
);
for (const abi of abis) {
const aot =
abi.abi === preferred.abi
+5 -1
View File
@@ -2,7 +2,7 @@ import { basename } from "node:path";
import { FridaInstrumentationService } from "../dist/application/frida/FridaInstrumentationService.js";
import { FridaInstrumentationManager } from "../dist/frida/FridaInstrumentationManager.js";
import { isValidFridaRemoteAddress } from "../dist/contracts/fridaRemoteAddress.js";
import { isValidFridaRemoteAddress } from "../dist/domain/fridaRemoteAddress.js";
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
const options = parseArguments(process.argv.slice(2));
@@ -80,6 +80,10 @@ try {
!Array.isArray(observed.messages)
)
throw new Error("Frida returned no message observation");
if (!Number.isSafeInteger(observed.pid) || observed.pid <= 0)
throw new Error("Frida returned no valid target PID");
if (remote !== undefined && observed.pid !== options.pid)
throw new Error("Frida observed a different target PID");
const messageObserved = observed.messages.some(
(message) =>
typeof message === "object" &&
Loaded 100 of 598 files, more files were not shown because too many files have changed in this diff. Show more