Compare commits
141
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6d8bb65d3 | ||
|
|
69e097a8ca | ||
|
|
3bdc8730bd | ||
|
|
3bcc68c037 | ||
|
|
83a4445f44 | ||
|
|
783fcb06a4 | ||
|
|
e55ef72135 | ||
|
|
48c352b6bf | ||
|
|
6fb3a345fe | ||
|
|
1c64fe3eff | ||
|
|
538f8cc722 | ||
|
|
0d78c99c11 | ||
|
|
21eff3bae1 | ||
|
|
b7f563e15f | ||
|
|
4a4d36f881 | ||
|
|
d0249282c6 | ||
|
|
a1b5d51a8d | ||
|
|
bd74c7af2b | ||
|
|
9f67e44e5d | ||
|
|
ee50ed121e | ||
|
|
c0904b4c1a | ||
|
|
d9d2a17381 | ||
|
|
04089d5bec | ||
|
|
880a603fe8 | ||
|
|
64b71b180a | ||
|
|
2ff850de68 | ||
|
|
42b0ed126d | ||
|
|
b8fab74297 | ||
|
|
edf46248f2 | ||
|
|
5dfb9d050c | ||
|
|
b38429cb6a | ||
|
|
87b9793c53 | ||
|
|
d93fbda390 | ||
|
|
6611bcf6e8 | ||
|
|
e19879328a | ||
|
|
b5ededd0f9 | ||
|
|
aada0ca812 | ||
|
|
f889a69fb9 | ||
|
|
adde8a8092 | ||
|
|
091cb81fc3 | ||
|
|
ed6af7eb8c | ||
|
|
976c2c083e | ||
|
|
eaf88aca2a | ||
|
|
1febc22907 | ||
|
|
4b653fe6ad | ||
|
|
0d11f71e0d | ||
|
|
43f9c39dfb | ||
|
|
a149d183ef | ||
|
|
f28f338283 | ||
|
|
e9fe6437a7 | ||
|
|
6395e8d300 | ||
|
|
1f17ac8228 | ||
|
|
89f5e489ad | ||
|
|
7285e15839 | ||
|
|
8bd5ba2d23 | ||
|
|
0677468877 | ||
|
|
55b61485e9 | ||
|
|
264615059c | ||
|
|
74d7da6ced | ||
|
|
dcfc5d403a | ||
|
|
9c8511409c | ||
|
|
f1924cf9c1 | ||
|
|
b777e5d4d8 | ||
|
|
1d4c4eb030 | ||
|
|
9eee709f0c | ||
|
|
beb80a07ba | ||
|
|
108b5f9425 | ||
|
|
a8d4984404 | ||
|
|
c002b89e2d | ||
|
|
2260887f20 | ||
|
|
c2407689cb | ||
|
|
39418ea173 | ||
|
|
7d92d93a55 | ||
|
|
eb97da3388 | ||
|
|
290d301ec7 | ||
|
|
5e219de441 | ||
|
|
5ba1642a7d | ||
|
|
804c287e44 | ||
|
|
918713af37 | ||
|
|
a82e37c806 | ||
|
|
c2b56e3e1c | ||
|
|
12fa8fd9f9 | ||
|
|
dc303a8ee8 | ||
|
|
3d83135987 | ||
|
|
09b6828516 | ||
|
|
f64790bfc3 | ||
|
|
c42232f566 | ||
|
|
52e76c5d39 | ||
|
|
1449e8e051 | ||
|
|
7cdb7981c3 | ||
|
|
0ae38e1e31 | ||
|
|
c2cf7dcb86 | ||
|
|
14935e4d47 | ||
|
|
85c1d2587a | ||
|
|
0d8b728d24 | ||
|
|
9878bb7296 | ||
|
|
12ee785e5e | ||
|
|
e59db2f5de | ||
|
|
d802304c54 | ||
|
|
0f4992fb9e | ||
|
|
72489a9445 | ||
|
|
b6651cb99f | ||
|
|
0b2aa69531 | ||
|
|
b5b73019f9 | ||
|
|
d0edeec100 | ||
|
|
3b490a743f | ||
|
|
3298219d8c | ||
|
|
b07e54a10e | ||
|
|
fb43dd9716 | ||
|
|
0485cd3bce | ||
|
|
c32d84622f | ||
|
|
601814ed0e | ||
|
|
dfe31172ca | ||
|
|
6407718f30 | ||
|
|
f8513a42dc | ||
|
|
a95795c6ed | ||
|
|
369cd3dfdc | ||
|
|
2976fac30d | ||
|
|
f598bab488 | ||
|
|
bb508134ef | ||
|
|
1dfb933e47 | ||
|
|
486b56724c | ||
|
|
9d8e42bdd6 | ||
|
|
d28b41ea4e | ||
|
|
e8491e023a | ||
|
|
097f0d54af | ||
|
|
e4ac7bbfc2 | ||
|
|
0b7fc40eee | ||
|
|
7a0140b4aa | ||
|
|
9b3e59804d | ||
|
|
1769417001 | ||
|
|
898d5096f9 | ||
|
|
38539a2f4a | ||
|
|
23d50ea3be | ||
|
|
499ac18011 | ||
|
|
7c207c7951 | ||
|
|
d3e83b0899 | ||
|
|
29d2624b34 | ||
|
|
fa9a7e5ec0 | ||
|
|
efbc25fbea | ||
|
|
ea40f717b2 |
@@ -2,7 +2,7 @@
|
||||
name: reverse-engineer-anything
|
||||
description: Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA. Use shipped-artifact or requested runtime evidence to explain features, compare versions, decompile code, or guide a reconstruction. Skip REA for ordinary source-repository architecture analysis.
|
||||
metadata:
|
||||
version: "38"
|
||||
version: "39"
|
||||
---
|
||||
|
||||
# REA
|
||||
@@ -77,6 +77,12 @@ explicit path or endpoint and do not need it.
|
||||
argument strings and mutability as inferred candidates; this performs no chain
|
||||
lookup or target execution. See the
|
||||
[EVM bytecode guide](https://github.com/morluto/rea/blob/main/docs/evm-bytecode.md).
|
||||
- Embedded Go compiler, module or build-setting metadata: `inspect_go_binary`
|
||||
with the explicit local file. This built-in target-free inspection requires no
|
||||
Go installation or disassembler. Preserve raw module bytes and source ranges;
|
||||
absent metadata does not prove a binary is not Go, and unparsed text records
|
||||
remain explicit. See the
|
||||
[Go binary guide](https://github.com/morluto/rea/blob/main/docs/go-binaries.md).
|
||||
- Explicit Linux ELF file for offline layout, symbols, relocations or static
|
||||
mitigation evidence: `inspect_binary_layout`. This target-free operation uses
|
||||
caller-supplied pwntools without opening a disassembler database. Preserve its
|
||||
@@ -115,8 +121,14 @@ ask when matches are ambiguous. Never choose an example app on the user's behalf
|
||||
In a target-free session, use `open_binary` to bind any archive/package or
|
||||
native target whose analysis tool operates on the active target. Do not call an
|
||||
unadvertised tool; inspect `binary_session` with `{}` and its
|
||||
`result.tool_availability` for availability reasons and remediation. The tool
|
||||
list stays complete; availability depends on the operation, target, and host.
|
||||
`result.tool_availability` for availability reasons and remediation. Expand each
|
||||
group's `names` into entries, inheriting the inventory-level client requirements
|
||||
unless the group overrides them. In its closed effect and annotation vocabularies,
|
||||
listed flags are true and omitted known flags are false. The grouped name union
|
||||
is the complete tool inventory; use `tools/list` for canonical tool schemas.
|
||||
Provider candidate facts describe possible providers, while top-level session
|
||||
capabilities describe the current route and `analysis_provider_binding` names the
|
||||
selected provider. Do not treat those scopes as interchangeable.
|
||||
|
||||
## Work summary-first
|
||||
|
||||
|
||||
@@ -11,14 +11,14 @@ main and an older release expose the same arguments.
|
||||
|
||||
## Choose the remedy from the observed failure
|
||||
|
||||
| Observation | Meaning and next step |
|
||||
| --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| No REA tools in the active session | Inspect registration with `rea doctor --client codex --json`. A skill installation alone does not register MCP. |
|
||||
| Missing, malformed or stale registration | Prepare a scoped setup plan; apply only authorized changes with backups. |
|
||||
| Aligned registration, but no connected tools | Restart/reconnect the client. Doctor checks registration; it cannot establish the current agent's connection. Inspect the client's launch error if reconnection fails. |
|
||||
| `MCP tool call requires approval, but approval policy is never` | Codex rejected the call before REA executed it. Do not install an engine or repeat setup to solve this client-policy decision. See the approval section below. |
|
||||
| REA returns `provider_unavailable` or an unavailable tool entry | Read that operation's provider diagnostics or `binary_session.result.tool_availability`. Resolve only the prerequisite needed for the task; a complete tool catalog does not imply every operation is available. |
|
||||
| `rea` is missing in a child shell, or Node is incompatible | Inspect the actual shell PATH and launcher/runtime. A prefix installation may be absent from a login shell's PATH; this does not establish a broken MCP registration. |
|
||||
| Observation | Meaning and next step |
|
||||
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| No REA tools in the active session | Inspect registration with `rea doctor --client codex --json`. A skill installation alone does not register MCP. |
|
||||
| Missing, malformed or stale registration | Prepare a scoped setup plan; apply only authorized changes with backups. |
|
||||
| Aligned registration, but no connected tools | Restart/reconnect the client. Doctor checks registration; it cannot establish the current agent's connection. Inspect the client's launch error if reconnection fails. |
|
||||
| `MCP tool call requires approval, but approval policy is never` | Codex rejected the call before REA executed it. Do not install an engine or repeat setup to solve this client-policy decision. See the approval section below. |
|
||||
| REA returns `provider_unavailable` or an unavailable tool entry | Read that operation's provider diagnostics or `binary_session.result.tool_availability`. Expand each group's tool names and inherit the shared client requirements unless overridden. Use the operation's availability, reason, and remediation; a complete tool catalog does not imply every operation is available. |
|
||||
| `rea` is missing in a child shell, or Node is incompatible | Inspect the actual shell PATH and launcher/runtime. A prefix installation may be absent from a login shell's PATH; this does not establish a broken MCP registration. |
|
||||
|
||||
If the client cannot launch its shell/sandbox helper or read the installed skill,
|
||||
inspect its launch diagnostic. For example, Codex can refuse helper aliases when
|
||||
@@ -46,7 +46,7 @@ rea setup --client codex --dry-run --json
|
||||
Use the current supported client's ID: `claude_code`, `claude_desktop`, `codex`,
|
||||
`cursor`, `gemini_cli`, `windsurf`, `devin`, `opencode`, `antigravity`,
|
||||
`copilot_cli`, `commandcode`, `qwen_code`, `vscode`, `grok_build`, `omp`, `pi`,
|
||||
`hermes`, or `grok_bot`. If unknown, inspect `doctor --json` before choosing a
|
||||
`hermes`, `dsh`, or `grok_bot`. If unknown, inspect `doctor --json` before choosing a
|
||||
scope. `grok_bot` uses a chat registration step rather than local `mcp.json`.
|
||||
For unsupported clients, use version-pinned manual stdio registration or CLI.
|
||||
|
||||
|
||||
@@ -46,3 +46,9 @@ observed exit signal stays in `exit.signal`, `exit.code` is null for every
|
||||
deadline exit, and the code of a finalizer that exited on its own is in
|
||||
`exit.finalization.exit_code` (null on a signal exit or when no exit was
|
||||
observed).
|
||||
|
||||
Use `inspect_analysis_view` with `view: {kind: "api-surface"}` on retained
|
||||
`inspect_web_network_capture` HAR Evidence to group observed request lines by
|
||||
method, origin and path template. Format matches are inferences, not handler
|
||||
identity or schemas. Group pages cite parent record ordinals; consult record
|
||||
coverage and exclusions before claiming completeness. No requests are replayed.
|
||||
|
||||
@@ -9,7 +9,22 @@ provenance; inputs and recovered code are never executed. Recovery requires a
|
||||
caller-supplied Wakaru on Linux x64; see [the recovery guide](https://github.com/morluto/rea/blob/main/docs/javascript-recovery.md).
|
||||
|
||||
Use `analyze_javascript_application` directly on the operator-supplied ASAR or
|
||||
extracted tree. When size is unknown or large and the connected schema advertises
|
||||
extracted tree. When the question concerns known source paths and the connected
|
||||
schema advertises `source_paths`, select exact inventoried paths such as
|
||||
`["package.json", "main.js", "preload.js"]`. Include supporting manifests or
|
||||
source maps explicitly; dependencies are not implicitly analyzed. REA retains
|
||||
the complete artifact inventory and identity but reads source text and analyzes
|
||||
only selected files. The result and summary record `source_selection`; files
|
||||
outside scope and their relationships remain unknown, so partial application
|
||||
coverage does not mean the selected source failed. Use repeated `--source-path`
|
||||
on either static application CLI route. Omit the selection when the question
|
||||
requires complete application analysis. `detail: "summary"` reduces delivery
|
||||
and does not by itself reduce analysis work.
|
||||
|
||||
Exact `#alias` specifiers resolve through the importer's
|
||||
enclosing package `imports` map to package-local files; nested `node_modules`
|
||||
packages use their own map. Wildcard, recursive, and external-package import
|
||||
targets stay unresolved with that limitation. When size is unknown or large and the connected schema advertises
|
||||
`detail`, request `detail: "summary"`. This returns an `inspect_analysis_view`
|
||||
Evidence record with a distinct ID and a `normalized_result.parent_evidence_id`
|
||||
for the complete retained application analysis. Source-map contents are part of
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# Native, managed, and packaged artifacts
|
||||
|
||||
## Go build metadata
|
||||
|
||||
Use `inspect_go_binary` for an explicit local ELF, PE or thin Mach-O when the
|
||||
question concerns the embedded compiler version, module/dependency versions,
|
||||
local replacements or build settings. No `open_binary`, Go installation or
|
||||
target execution is required. The CLI equivalent is `inspect-go-binary PATH`.
|
||||
Preserve artifact SHA-256, original file ranges, framed module bytes and every
|
||||
unparsed module-text line. A missing record is an unknown, not proof of a
|
||||
non-Go binary; observed metadata does not authenticate its claims. Function,
|
||||
source and type recovery remain a separate native-analysis question.
|
||||
|
||||
## Recorded Linux crashes
|
||||
|
||||
Use `inspect_recorded_crash` with an explicit Linux x86-64 ELF core path to
|
||||
@@ -50,10 +61,19 @@ runs and may change files or use the network. Hardened-runtime targets need the
|
||||
`get-task-allow` entitlement.
|
||||
|
||||
Use `binary_session` with no arguments to check the open target, selected
|
||||
provider, and alignment. Its default `result.tool_availability` includes the
|
||||
complete tool inventory with availability reasons and remediation. When choosing
|
||||
a tool, use its entry in that result; `tools/list` retains the complete catalog
|
||||
when the target or provider state changes.
|
||||
provider, and alignment. Its default `result.tool_availability` carries a
|
||||
complete inventory grouped by shared facts. Expand each group's `names` and
|
||||
inherit the inventory-level client requirements unless a group overrides them;
|
||||
availability, reasons, remediation, modes, and flags remain inline. The
|
||||
`effects` and `annotations` 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.
|
||||
|
||||
Provider capability groups carry provider identity and ordered limitations.
|
||||
Prepend each set's `shared_limitations` to a group's `limitations` to recover the
|
||||
operation's full ordered list. Candidate capabilities describe possible
|
||||
providers. Top-level session capabilities describe the current route, and
|
||||
`analysis_provider_binding` identifies the selected provider.
|
||||
|
||||
## DOS MZ with Ghidra
|
||||
|
||||
@@ -150,6 +170,13 @@ For headerless DOS COM, explicitly open with `format: "dos-com"` (CLI
|
||||
analysis: the entry is `0x10100`, file offset zero, with imposed real-mode segment
|
||||
context. PSP/stack/device state remains unmodeled. See [DOS guide](https://github.com/morluto/rea/blob/main/docs/ghidra-dos.md).
|
||||
|
||||
For other headerless images (embedded firmware without ELF/Mach-O/PE/MZ
|
||||
headers), open with `format: "raw-binary"`, `architecture` (`x86`, `x86_64`,
|
||||
`arm`, or `arm64`), and `entry_point` (`0x` hex). File offset 0 is mapped at
|
||||
that address. Hopper and IDA refuse this interpretation; Ghidra uses
|
||||
`BinaryLoader` with a little-endian generic language. Big-endian, Thumb-only,
|
||||
and Cortex-M variants are unsupported. See [raw binary guide](https://github.com/morluto/rea/blob/main/docs/raw-binary.md).
|
||||
|
||||
### Function annotations in Ghidra
|
||||
|
||||
Use `annotate_native_function` for one function name and/or entry comments, with at least one explicit change. Review its annotation readback and refreshed dossier inline. Changes are atomic and session-scoped; empty comments clear them, omitted fields preserve them. Later MCP calls observe edits until close. CLI `annotate-native-function` returns the updated analysis before discarding the session. Original executable bytes are unchanged; edits invalidate immutable snapshots. Windows P0 does not admit database mutations. For naming only, `set_address_name` / `set_addresses_names` also work with Ghidra: they name function entries or data addresses (primary labels) and return compact per-address success booleans instead of a dossier.
|
||||
|
||||
@@ -493,7 +493,7 @@ jobs:
|
||||
tests/acceptance/webSourceLocationCli.test.ts
|
||||
tests/boundary/cli/referenceSourceImportStatus.test.ts
|
||||
tests/boundary/filesystem/asarInventoryFreshness.test.ts
|
||||
tests/composition/javascript-applications/javascriptArtifactReconstruction.test.ts
|
||||
tests/boundary/filesystem/javascript-applications/javascriptArtifactReconstruction.test.ts
|
||||
tests/conformance/providers/ghidra/windowsCapabilities.test.ts
|
||||
src/ghidra/GhidraInstallation.test.ts
|
||||
tests/boundary/providers/ghidra/ghidraTransport.test.ts
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
name: Real Go binary metadata
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/real-go-binary.yml"
|
||||
- "src/go/**"
|
||||
- "src/domain/go/**"
|
||||
- "src/domain/dosMz.ts"
|
||||
- "src/application/go/**"
|
||||
- "src/contracts/go/**"
|
||||
- "src/composition/go.ts"
|
||||
- "src/cli/goCommands.ts"
|
||||
- "src/server/registerGoTools.ts"
|
||||
- "src/server/createServer.ts"
|
||||
- "src/cli.ts"
|
||||
- "src/cliCommandNames.ts"
|
||||
- "src/contracts/toolContracts.ts"
|
||||
- "src/contracts/toolEffects.ts"
|
||||
- "src/artifacts/readStableArtifact*.ts"
|
||||
- "src/artifacts/ArtifactReader.ts"
|
||||
- "src/process/PrivateRuntimeRoot.ts"
|
||||
- "scripts/verify-go-binary.mjs"
|
||||
- "scripts/lib/mcp-verifier-results.mjs"
|
||||
- "scripts/rea.mjs"
|
||||
- "package.json"
|
||||
- "package-lock.json"
|
||||
- ".nvmrc"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: real-go-binary-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=768
|
||||
HUSKY: "0"
|
||||
|
||||
jobs:
|
||||
real-go-binary:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.26.1"
|
||||
cache: false
|
||||
- run: npm ci
|
||||
- name: Verify compiler-produced ELF, PE and Mach-O through public CLI and MCP
|
||||
run: npm run verify:go:binary
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"_comment": "Test files are intentionally NOT excluded: the gate used to report zero clones because it skipped **/*.test.ts, hiding all test-tree duplication. Current actual is ~2.47% lines across src+ and tests/, down from 3.4% when tests were included. The 2.6% limit leaves modest headroom for ordinary changes; lower it as duplication is removed.",
|
||||
"threshold": 2.6,
|
||||
"_comment": "Test files remain included so the gate measures duplication across both src/ and tests/. The 3.0% limit leaves headroom for ordinary changes while keeping duplication visible.",
|
||||
"threshold": 3.0,
|
||||
"reporters": ["consoleFull"],
|
||||
"ignore": [
|
||||
"node_modules/**",
|
||||
|
||||
@@ -14,10 +14,14 @@ Preserve caller-selected inputs and local evidence, including output, URLs, path
|
||||
|
||||
Dependencies flow inward: domain semantics and contracts, provider adapters, shared application workflows, then CLI/MCP adapters. Keep engine protocols and provider-specific code out of domain/application layers. See [docs/architecture.mermaid](docs/architecture.mermaid) when changing composition.
|
||||
|
||||
Give each interpretation and resource a clear owner. Preserve identity, omission, uncertainty, and source evidence across boundaries. Retain ownership of resources whose cleanup failed. Reuse `src/process/` supervision and identity primitives; a PID and executable path alone do not establish ownership after exit or reuse.
|
||||
Give each interpretation and resource a clear owner. Consumers must use that owner's parsed facts rather than reinterpret raw source. Preserve identity, omission, uncertainty, and source evidence across boundaries. Retain ownership of resources whose cleanup failed. Reuse `src/process/` supervision and identity primitives; a PID and executable path alone do not establish ownership after exit or reuse.
|
||||
|
||||
Keep proof of ownership separate from validity, editability, and readiness. Malformed owned data may still be safely removable. Unresolved identity or applicability must remain unknown rather than be treated as absent, foreign, or aligned.
|
||||
|
||||
Assess brittleness in touched code during implementation, debugging, review, and maintenance. Fix the producing representation or ownership rule before adding downstream repairs. Normalize once at the owning boundary using format-aware parsers. Remove superseded representations, aliases, wrappers, and redundant tests after verifying affected consumers. Introduce abstractions only when they remove observed complexity.
|
||||
|
||||
When fixing a bug, inspect the producing boundary and affected consumers for duplicated interpretation or unclear ownership. Complete any directly related refactor in the same change and remove superseded code. Passing regression tests alone does not establish that the brittleness is resolved. If related cleanup remains, explain what remains and why.
|
||||
|
||||
Parse unknown boundary values and model expected failures with the tagged error algebra and `Result`. Preserve partial facts and meaningful failure reasons. Keep identity and lookup values separate from display formatting; preserve source values and explicit unknowns when normalization loses information. Absolute paths, file URLs, and HTTP paths have different semantics.
|
||||
|
||||
Validate advertised JSON Schemas after SDK conversion against their declared dialect and actual clients. Bind handlers to named contracts with exact types; catalog ordering must never select an operation or schema.
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
|
||||
### One MCP for reverse engineering across binaries, applications, and runtime behavior.
|
||||
|
||||
**See a feature you like. Understand how it works, down to the binary level.**
|
||||
**Understand how software works, down to the binary level — for security research, interoperability, and software you're authorized to analyze.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#what-you-can-analyze)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
See a feature in an app that you want in your own product? Ask your agent to investigate it with REA. It can inspect the app without its source code, explain how the feature works, show the evidence, and build a version for your project.
|
||||
REA helps security researchers, malware analysts, CTF players and developers understand compiled software through their agents. Point your agent at a binary, application or capture you're authorized to analyze; REA inspects it without source code, explains its behavior, and shows the evidence behind each conclusion.
|
||||
|
||||
REA connects your agent to tools for inspecting native binaries, JavaScript and Electron apps, .NET assemblies, and websites. You can also use the same tools from your terminal. Analysis runs locally, and results include the evidence and limitations behind each conclusion.
|
||||
|
||||
@@ -56,6 +55,23 @@ Setup registers REA with your agent and installs matching workflow instructions.
|
||||
|
||||
> **[Visit the REA website](https://rea.tools/)** for setup instructions, illustrated guides, and real case studies.
|
||||
|
||||
## Responsible use
|
||||
|
||||
REA is built for lawful analysis: security research, malware and incident analysis, vulnerability research and coordinated disclosure, CTFs and education, interoperability, software preservation, and debugging or auditing software you own or are authorized to examine.
|
||||
|
||||
The REA project does not support, and will not help with:
|
||||
|
||||
- circumventing DRM, license checks, anti-cheat systems or other technical protection measures;
|
||||
- developing game cheats or tampering with online services;
|
||||
- bypassing authentication, bot protection or access controls on systems you are not authorized to test;
|
||||
- copying proprietary software or reproducing its protected code and assets;
|
||||
- evading the usage policies or safety measures of AI model providers;
|
||||
- reselling or sharing AI accounts or API access.
|
||||
|
||||
You are responsible for obtaining any required authorization and for complying with applicable laws, license agreements and terms of service. Reverse engineering may be restricted in your jurisdiction or by the license of the software you analyze.
|
||||
|
||||
Requests for help with the uses above will be closed without response in issues and discussions, and may result in removal from the REA community.
|
||||
|
||||
## Quick start
|
||||
|
||||
### Set up your agent
|
||||
@@ -78,11 +94,11 @@ manual MCP registration.
|
||||
### Ask your agent
|
||||
|
||||
```text
|
||||
Understand how search works in the Notes app, show me the evidence, and build a
|
||||
similar feature for my project.
|
||||
My app's release build crashes on startup but the debug build doesn't.
|
||||
Inspect ./build/MyApp, find where the two builds diverge, and show me the evidence.
|
||||
```
|
||||
|
||||
Replace Notes with your target app and the feature you want to understand.
|
||||
Point your agent at a target you're authorized to analyze and describe what you want to understand.
|
||||
|
||||
### Use the terminal
|
||||
|
||||
@@ -155,6 +171,7 @@ Additional tools and host support depend on the target:
|
||||
| ---------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Native binaries | Pseudocode, assembly, strings, symbols, calls and references | Hopper, Ghidra or IDA; [native analysis](https://rea.tools/guides/native/) |
|
||||
| Offline ELF layout | Sections, segments, original symbols/relocations and static mitigation candidates | Caller-supplied pwntools on Linux x64; [binary diagnostics](docs/binary-diagnostics.md) |
|
||||
| Go binaries | Embedded compiler version, modules, replacements and build settings | Built-in static ELF/PE/thin Mach-O inspection; no Go installation required |
|
||||
| WASM artifact | Validation, sections, exact import/export WAT forms and decoded WAT | Caller-supplied WABT 1.0.42; `inspect_wasm_artifact` |
|
||||
| EVM bytecode | Dispatch selectors, byte offsets, inferred arguments and mutability | Local raw/hex carrier on Linux x64; [offline EVM guide](docs/evm-bytecode.md) |
|
||||
| Recorded Linux crashes | Raw notes, every recorded thread's registers/signals and optional mapping candidates | Caller-supplied pwntools on Linux x64; optional GDB/pwndbg; [recorded crashes](docs/recorded-crashes.md) |
|
||||
@@ -245,7 +262,9 @@ Ghidra and IDA use your existing installations. See [provider setup](docs/instal
|
||||
<summary><strong>Do I need to start Hopper first?</strong></summary>
|
||||
|
||||
REA starts Hopper when an operation needs it. On macOS, a first-run dialog may
|
||||
ask you to choose demo mode or activate your license. See
|
||||
ask you to choose demo mode or activate your license. Complete that prompt in the
|
||||
same user session before your first CLI/MCP operation or unattended job. Open
|
||||
Hopper manually if needed. See
|
||||
[Hopper startup and troubleshooting](docs/installation.md#launcher-paths-and-troubleshooting).
|
||||
|
||||
</details>
|
||||
@@ -315,7 +334,7 @@ Report vulnerabilities through [SECURITY.md](SECURITY.md).
|
||||
|
||||
## Star history
|
||||
|
||||
🎉 **90,000 GitHub stars — thank you!**
|
||||
🎉 **110,000 GitHub stars — thank you!**
|
||||
|
||||
Thanks to everyone using REA, reporting bugs, requesting features, testing builds, and contributing fixes.
|
||||
|
||||
@@ -329,7 +348,7 @@ Thanks to everyone using REA, reporting bugs, requesting features, testing build
|
||||
|
||||
## Disclaimer
|
||||
|
||||
REA provides tools for lawful reverse-engineering research, analysis, and reconstruction. You are responsible for obtaining any required authorization and complying with applicable laws. The project does not endorse illegal or unauthorized use.
|
||||
See [Responsible use](#responsible-use).
|
||||
|
||||
REA is an open-source software project. We have not issued or endorsed any cryptocurrency or token. Tokens using the REA name are not affiliated with the project.
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### خادم MCP واحد للهندسة العكسية للملفات الثنائية والتطبيقات والسلوك أثناء التشغيل.
|
||||
|
||||
**وجدت ميزة تعجبك؟ افهم طريقة عملها حتى مستوى الشيفرة الثنائية.**
|
||||
**افهم طريقة عمل البرمجيات حتى مستوى الشيفرة الثنائية — لأبحاث الأمن، وقابلية التشغيل البيني، والبرمجيات المصرّح لك بتحليلها.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
هل وجدت في تطبيق ميزة تريد إضافتها إلى منتجك؟ اطلب من وكيلك استقصاءها باستخدام REA. يستطيع فحص التطبيق من دون شيفرته المصدرية، وشرح طريقة عمل الميزة وعرض الأدلة، ثم بناء نسخة تناسب مشروعك.
|
||||
يساعد REA الباحثين الأمنيين ومحللي البرمجيات الخبيثة ولاعبي مسابقات CTF والمطورين على فهم البرمجيات المُصرَّفة عبر وكلائهم. وجّه وكيلك إلى ملف ثنائي أو تطبيق أو تسجيل مصرّح لك بتحليله؛ فيفحصه REA من دون الشيفرة المصدرية، ويشرح سلوكه، ويعرض الأدلة التي يستند إليها كل استنتاج.
|
||||
|
||||
يربط REA وكيلك بأدوات لفحص الملفات الثنائية للشيفرة الأصلية، وتطبيقات JavaScript وElectron، وتجميعات .NET، والمواقع. يمكنك أيضًا استخدام الأدوات نفسها من الطرفية. يجري التحليل محليًا، وتتضمن النتائج الأدلة والقيود التي تستند إليها كل خلاصة.
|
||||
|
||||
@@ -56,6 +55,23 @@
|
||||
|
||||
> **[زر موقع REA](https://rea.tools/ar/)** للاطلاع على تعليمات الإعداد والأدلة المصوّرة ودراسات الحالة الفعلية.
|
||||
|
||||
## الاستخدام المسؤول
|
||||
|
||||
صُمّم REA للتحليل المشروع: أبحاث الأمن، وتحليل البرمجيات الخبيثة والحوادث، وأبحاث الثغرات والإفصاح المنسّق، ومسابقات CTF والتعليم، وقابلية التشغيل البيني، وحفظ البرمجيات، وتصحيح أخطاء البرمجيات أو تدقيقها إذا كنت تملكها أو كان مصرّحًا لك بفحصها.
|
||||
|
||||
لا يدعم مشروع REA ما يلي، ولن يساعد فيه:
|
||||
|
||||
- التحايل على إدارة الحقوق الرقمية (DRM) أو فحوص التراخيص أو أنظمة مكافحة الغش أو غيرها من تدابير الحماية التقنية؛
|
||||
- تطوير أدوات الغش في الألعاب أو العبث بالخدمات عبر الإنترنت؛
|
||||
- تجاوز المصادقة أو الحماية من الروبوتات أو ضوابط الوصول في أنظمة غير مصرّح لك باختبارها؛
|
||||
- نسخ البرمجيات المملوكة أو إعادة إنتاج شيفرتها وأصولها المحمية؛
|
||||
- التهرّب من سياسات الاستخدام أو تدابير السلامة لدى مزوّدي نماذج الذكاء الاصطناعي؛
|
||||
- إعادة بيع حسابات الذكاء الاصطناعي أو الوصول إلى واجهات API أو مشاركتها.
|
||||
|
||||
أنت مسؤول عن الحصول على أي تفويض مطلوب والالتزام بالقوانين واتفاقيات الترخيص وشروط الخدمة المعمول بها. وقد تكون الهندسة العكسية مقيّدة في نطاق ولايتك القضائية أو بموجب ترخيص البرنامج الذي تحلّله.
|
||||
|
||||
ستُغلق طلبات المساعدة في الاستخدامات المذكورة أعلاه في issues والنقاشات من دون رد، وقد تؤدي إلى الإزالة من مجتمع REA.
|
||||
|
||||
## البدء السريع
|
||||
|
||||
### إعداد وكيلك
|
||||
@@ -73,10 +89,11 @@ npx rea-agents setup
|
||||
### اطلب من وكيلك
|
||||
|
||||
```text
|
||||
استقصِ طريقة عمل البحث في تطبيق Notes، واعرض الأدلة، ثم ابنِ ميزة مشابهة لمشروعي.
|
||||
تتعطّل نسخة الإصدار (release) من تطبيقي عند بدء التشغيل، لكن نسخة التصحيح (debug) لا تتعطّل.
|
||||
افحص ./build/MyApp، وحدّد موضع اختلاف النسختين، واعرض لي الأدلة.
|
||||
```
|
||||
|
||||
استبدل Notes بالتطبيق المستهدف وحدّد الميزة التي تريد فهمها.
|
||||
وجّه وكيلك إلى هدف مصرّح لك بتحليله، وصِف ما تريد فهمه.
|
||||
|
||||
### استخدام الطرفية
|
||||
|
||||
@@ -261,7 +278,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## سجل النجوم
|
||||
|
||||
🎉 **90,000 نجمة على GitHub — شكرًا لكم!**
|
||||
🎉 **110,000 نجمة على GitHub — شكرًا لكم!**
|
||||
|
||||
شكرًا لكل من يستخدم REA، ويبلّغ عن الأخطاء، ويقترح ميزات، ويختبر إصدارات البناء، ويساهم بالإصلاحات.
|
||||
|
||||
@@ -275,7 +292,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## إخلاء المسؤولية
|
||||
|
||||
يوفّر REA أدوات لأبحاث الهندسة العكسية والتحليل وإعادة البناء المشروعة. أنت مسؤول عن الحصول على أي تفويض مطلوب والالتزام بالقوانين المعمول بها. لا يؤيد المشروع الاستخدام غير القانوني أو غير المصرّح به.
|
||||
راجع [الاستخدام المسؤول](#الاستخدام-المسؤول).
|
||||
|
||||
## المساهمة
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### Ein MCP für das Reverse Engineering von Binärdateien, Anwendungen und Laufzeitverhalten.
|
||||
|
||||
**Eine Funktion gefällt dir? Verstehe, wie sie funktioniert – bis hinunter auf die Binärebene.**
|
||||
**Verstehe, wie Software funktioniert, bis hinunter auf die Binärebene – für Sicherheitsforschung, Interoperabilität und Software, die du analysieren darfst.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
Du hast in einer App eine Funktion entdeckt, die du in dein eigenes Produkt übernehmen möchtest? Lass deinen Agenten sie mit REA untersuchen. Er kann die App ohne ihren Quellcode analysieren, die Funktionsweise erklären, Belege zeigen und eine passende Umsetzung für dein Projekt entwickeln.
|
||||
REA hilft Sicherheitsforschern, Malware-Analysten, CTF-Spielern und Entwicklern, kompilierte Software mithilfe ihrer Agenten zu verstehen. Richte deinen Agenten auf eine Binärdatei, eine Anwendung oder eine Aufzeichnung, die du analysieren darfst; REA untersucht sie ohne Quellcode, erklärt ihr Verhalten und zeigt die Belege hinter jeder Schlussfolgerung.
|
||||
|
||||
REA verbindet deinen Agenten mit Werkzeugen zum Untersuchen nativer Binärdateien, von JavaScript- und Electron-Apps, .NET-Assemblies und Websites. Dieselben Werkzeuge kannst du auch im Terminal nutzen. Die Analyse läuft lokal; die Ergebnisse enthalten die Belege und Einschränkungen hinter jeder Schlussfolgerung.
|
||||
|
||||
@@ -56,6 +55,23 @@ Die Einrichtung registriert REA bei deinem Agenten und installiert passende Work
|
||||
|
||||
> **[Besuche die REA-Website](https://rea.tools/de/)** für Einrichtungshinweise, bebilderte Anleitungen und echte Fallstudien.
|
||||
|
||||
## Verantwortungsvolle Nutzung
|
||||
|
||||
REA ist für rechtmäßige Analysen gedacht: Sicherheitsforschung, Malware- und Vorfallsanalyse, Schwachstellenforschung und koordinierte Offenlegung, CTFs und Lehre, Interoperabilität, Softwarebewahrung sowie das Debuggen oder Prüfen von Software, die dir gehört oder die du untersuchen darfst.
|
||||
|
||||
Das REA-Projekt unterstützt Folgendes nicht und hilft auch nicht dabei:
|
||||
|
||||
- DRM, Lizenzprüfungen, Anti-Cheat-Systeme oder andere technische Schutzmaßnahmen zu umgehen;
|
||||
- Cheats für Spiele zu entwickeln oder Onlinedienste zu manipulieren;
|
||||
- Authentifizierung, Bot-Schutz oder Zugriffskontrollen auf Systemen zu umgehen, die du nicht testen darfst;
|
||||
- proprietäre Software zu kopieren oder ihren geschützten Code und ihre geschützten Assets zu reproduzieren;
|
||||
- die Nutzungsrichtlinien oder Sicherheitsmaßnahmen von KI-Modellanbietern zu unterlaufen;
|
||||
- KI-Konten oder API-Zugänge weiterzuverkaufen oder zu teilen.
|
||||
|
||||
Du bist dafür verantwortlich, erforderliche Genehmigungen einzuholen und geltendes Recht, Lizenzvereinbarungen und Nutzungsbedingungen einzuhalten. Reverse Engineering kann in deiner Rechtsordnung oder durch die Lizenz der analysierten Software eingeschränkt sein.
|
||||
|
||||
Anfragen nach Hilfe bei den oben genannten Zwecken werden in Issues und Diskussionen ohne Antwort geschlossen und können zum Ausschluss aus der REA-Community führen.
|
||||
|
||||
## Schnellstart
|
||||
|
||||
### Deinen Agenten einrichten
|
||||
@@ -73,10 +89,11 @@ Die Einrichtung unterstützt Claude Code, Codex, Cursor, Gemini CLI, Grok Build
|
||||
### Deinen Agenten fragen
|
||||
|
||||
```text
|
||||
Untersuche, wie die Suche in der Notes-App funktioniert, zeige mir die Belege und entwickle eine ähnliche Funktion für mein Projekt.
|
||||
Der Release-Build meiner App stürzt beim Start ab, der Debug-Build aber nicht.
|
||||
Untersuche ./build/MyApp, finde heraus, wo die beiden Builds voneinander abweichen, und zeige mir die Belege.
|
||||
```
|
||||
|
||||
Ersetze Notes durch deine Ziel-App und benenne die Funktion, die du verstehen möchtest.
|
||||
Richte deinen Agenten auf ein Ziel, das du analysieren darfst, und beschreibe, was du verstehen möchtest.
|
||||
|
||||
### Das Terminal verwenden
|
||||
|
||||
@@ -259,7 +276,7 @@ Melde Sicherheitslücken gemäß [SECURITY.md](SECURITY.md).
|
||||
|
||||
## Sternverlauf
|
||||
|
||||
🎉 **90.000 GitHub-Sterne – vielen Dank!**
|
||||
🎉 **110.000 GitHub-Sterne – vielen Dank!**
|
||||
|
||||
Danke an alle, die REA nutzen, Fehler melden, Funktionswünsche äußern, Builds testen und Fehlerbehebungen beitragen.
|
||||
|
||||
@@ -273,7 +290,7 @@ Danke an alle, die REA nutzen, Fehler melden, Funktionswünsche äußern, Builds
|
||||
|
||||
## Haftungsausschluss
|
||||
|
||||
REA stellt Werkzeuge für rechtmäßige Reverse-Engineering-Forschung, Analyse und Rekonstruktion bereit. Du bist dafür verantwortlich, erforderliche Genehmigungen einzuholen und geltendes Recht einzuhalten. Das Projekt unterstützt keine rechtswidrige oder unbefugte Nutzung.
|
||||
Siehe [Verantwortungsvolle Nutzung](#verantwortungsvolle-nutzung).
|
||||
|
||||
## Mitwirken
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### Un solo MCP para la ingeniería inversa de binarios, aplicaciones y comportamiento en ejecución.
|
||||
|
||||
**¿Ves una función que te gusta? Entiende cómo funciona, hasta el nivel binario.**
|
||||
**Entiende cómo funciona el software, hasta el nivel binario — para la investigación de seguridad, la interoperabilidad y el software que tienes autorización para analizar.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
¿Has visto una función en una aplicación que quieres incorporar a tu producto? Pide a tu agente que la investigue con REA. Puede inspeccionar la aplicación sin su código fuente, explicar cómo funciona la función, mostrar las pruebas y crear una versión para tu proyecto.
|
||||
REA ayuda a investigadores de seguridad, analistas de malware, participantes en CTF y desarrolladores a entender software compilado a través de sus agentes. Dirige a tu agente a un binario, una aplicación o una captura que tengas autorización para analizar; REA lo inspecciona sin código fuente, explica su comportamiento y muestra las pruebas en las que se basa cada conclusión.
|
||||
|
||||
REA conecta tu agente con herramientas para inspeccionar binarios nativos, aplicaciones JavaScript y Electron, ensamblados .NET y sitios web. También puedes usar las mismas herramientas desde la terminal. El análisis se ejecuta localmente y los resultados incluyen las pruebas y limitaciones que sustentan cada conclusión.
|
||||
|
||||
@@ -56,6 +55,23 @@ La configuración registra REA en tu agente e instala las instrucciones de traba
|
||||
|
||||
> **[Visita el sitio web de REA](https://rea.tools/es/)** para encontrar instrucciones de configuración, guías ilustradas y casos reales.
|
||||
|
||||
## Uso responsable
|
||||
|
||||
REA está pensado para el análisis lícito: investigación de seguridad, análisis de malware e incidentes, investigación de vulnerabilidades y divulgación coordinada, CTF y educación, interoperabilidad, preservación de software, y depuración o auditoría de software que te pertenece o que tienes autorización para examinar.
|
||||
|
||||
El proyecto REA no respalda lo siguiente ni ayudará con ello:
|
||||
|
||||
- eludir DRM, comprobaciones de licencia, sistemas antitrampas u otras medidas técnicas de protección;
|
||||
- desarrollar trampas para juegos o manipular servicios en línea;
|
||||
- eludir la autenticación, la protección contra bots o los controles de acceso en sistemas que no tienes autorización para probar;
|
||||
- copiar software propietario o reproducir su código y sus recursos protegidos;
|
||||
- evadir las políticas de uso o las medidas de seguridad de los proveedores de modelos de IA;
|
||||
- revender o compartir cuentas de IA o acceso a API.
|
||||
|
||||
Eres responsable de obtener las autorizaciones necesarias y de cumplir las leyes, los acuerdos de licencia y las condiciones de servicio aplicables. La ingeniería inversa puede estar restringida en tu jurisdicción o por la licencia del software que analizas.
|
||||
|
||||
Las solicitudes de ayuda para los usos anteriores se cerrarán sin respuesta en las issues y las discusiones, y pueden dar lugar a la expulsión de la comunidad de REA.
|
||||
|
||||
## Inicio rápido
|
||||
|
||||
### Configura tu agente
|
||||
@@ -73,10 +89,11 @@ La configuración admite Claude Code, Codex, Cursor, Gemini CLI, Grok Build y [o
|
||||
### Pregunta a tu agente
|
||||
|
||||
```text
|
||||
Investiga cómo funciona la búsqueda en la aplicación Notes, muéstrame las pruebas y crea una función similar para mi proyecto.
|
||||
La compilación de release de mi aplicación se bloquea al iniciarse, pero la de depuración no.
|
||||
Inspecciona ./build/MyApp, encuentra dónde divergen las dos compilaciones y muéstrame las pruebas.
|
||||
```
|
||||
|
||||
Sustituye Notes por la aplicación objetivo y especifica la función que quieres entender.
|
||||
Dirige a tu agente a un objetivo que tengas autorización para analizar y describe lo que quieres entender.
|
||||
|
||||
### Usa la terminal
|
||||
|
||||
@@ -259,7 +276,7 @@ Informa de vulnerabilidades siguiendo [SECURITY.md](SECURITY.md).
|
||||
|
||||
## Historial de estrellas
|
||||
|
||||
🎉 **90.000 estrellas en GitHub: ¡gracias!**
|
||||
🎉 **110.000 estrellas en GitHub: ¡gracias!**
|
||||
|
||||
Gracias a quienes usan REA, informan de errores, solicitan funciones, prueban compilaciones y contribuyen con correcciones.
|
||||
|
||||
@@ -273,7 +290,7 @@ Gracias a quienes usan REA, informan de errores, solicitan funciones, prueban co
|
||||
|
||||
## Aviso legal
|
||||
|
||||
REA proporciona herramientas para la investigación, el análisis y la reconstrucción mediante ingeniería inversa conforme a la ley. Eres responsable de obtener las autorizaciones necesarias y cumplir las leyes aplicables. El proyecto no respalda usos ilegales ni no autorizados.
|
||||
Consulta [Uso responsable](#uso-responsable).
|
||||
|
||||
## Contribuir
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### یک MCP برای مهندسی معکوس فایلهای باینری، برنامهها و رفتار زمان اجرا.
|
||||
|
||||
**قابلیتی دیدهاید که دوستش دارید؟ سازوکارش را تا سطح باینری درک کنید.**
|
||||
**سازوکار نرمافزار را تا سطح باینری درک کنید — برای پژوهش امنیتی، تعاملپذیری و نرمافزارهایی که مجوز تحلیلشان را دارید.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -40,7 +39,7 @@
|
||||
|
||||
<div dir="rtl">
|
||||
|
||||
قابلیتی در یک برنامه دیدهاید که میخواهید در محصول خودتان داشته باشید؟ از ایجنت خود بخواهید با REA آن را بررسی کند. ایجنت میتواند بدون دسترسی به کد منبع برنامه، نحوهٔ کار قابلیت را بررسی و توضیح دهد، شواهد را نشان دهد و نسخهای مشابه برای پروژهٔ شما بسازد.
|
||||
REA به پژوهشگران امنیتی، تحلیلگران بدافزار، شرکتکنندگان CTF و توسعهدهندگان کمک میکند تا نرمافزارهای کامپایلشده را از طریق ایجنتهایشان درک کنند. ایجنت خود را به سراغ فایل باینری، برنامه یا دادههای ضبطشدهای بفرستید که مجوز تحلیلشان را دارید؛ REA آن را بدون کد منبع بررسی میکند، رفتارش را توضیح میدهد و شواهد پشت هر نتیجهگیری را نشان میدهد.
|
||||
|
||||
REA ایجنت شما را به ابزارهایی برای بررسی باینریهای بومی، برنامههای JavaScript و Electron، اسمبلیهای .NET و وبسایتها متصل میکند. همین ابزارها از ترمینال نیز قابل استفادهاند. تحلیل بهصورت محلی انجام میشود و نتایج شامل شواهد و محدودیتهای پشت هر نتیجهگیری هستند.
|
||||
|
||||
@@ -48,6 +47,23 @@ REA ایجنت شما را به ابزارهایی برای بررسی باین
|
||||
|
||||
> برای دستورالعملهای راهاندازی، راهنماهای تصویری و مطالعات موردی واقعی، **[به وبسایت REA مراجعه کنید](https://rea.tools/fa/)**.
|
||||
|
||||
## استفادهٔ مسئولانه
|
||||
|
||||
REA برای تحلیل قانونی ساخته شده است: پژوهش امنیتی، تحلیل بدافزار و رخدادهای امنیتی، پژوهش آسیبپذیری و افشای هماهنگ، CTF و آموزش، تعاملپذیری، حفظ نرمافزار، و اشکالزدایی یا ممیزی نرمافزاری که مالک آن هستید یا مجوز بررسیاش را دارید.
|
||||
|
||||
پروژهٔ REA از موارد زیر پشتیبانی نمیکند و در آنها کمکی نخواهد کرد:
|
||||
|
||||
- دور زدن DRM، بررسیهای لایسنس، سامانههای ضدتقلب یا دیگر تدابیر حفاظت فنی؛
|
||||
- ساخت ابزار تقلب برای بازیها یا دستکاری سرویسهای آنلاین؛
|
||||
- دور زدن احراز هویت، محافظت در برابر رباتها یا کنترلهای دسترسی در سامانههایی که مجوز آزمودنشان را ندارید؛
|
||||
- کپیکردن نرمافزارهای انحصاری یا بازتولید کد و داراییهای محافظتشدهٔ آنها؛
|
||||
- گریز از سیاستهای استفاده یا تدابیر ایمنی ارائهدهندگان مدلهای هوش مصنوعی؛
|
||||
- فروش مجدد یا اشتراکگذاری حسابهای هوش مصنوعی یا دسترسی API.
|
||||
|
||||
مسئولیت دریافت مجوزهای لازم و رعایت قوانین، توافقنامههای لایسنس و شرایط خدمات مربوط بر عهدهٔ شماست. مهندسی معکوس ممکن است در حوزهٔ قضایی شما یا طبق لایسنس نرمافزاری که تحلیل میکنید محدود باشد.
|
||||
|
||||
درخواستهای کمک برای موارد بالا در issueها و بحثها بدون پاسخ بسته میشوند و ممکن است به حذف از جامعهٔ REA منجر شوند.
|
||||
|
||||
## شروع سریع
|
||||
|
||||
### ایجنت خود را راهاندازی کنید
|
||||
@@ -65,10 +81,11 @@ npx rea-agents setup
|
||||
### از ایجنت خود بپرسید
|
||||
|
||||
```text
|
||||
بررسی کن جستوجو در برنامهٔ Notes چگونه کار میکند، شواهدش را نشان بده و قابلیتی مشابه برای پروژهٔ من بساز.
|
||||
نسخهٔ release برنامهٔ من هنگام اجرا کرش میکند، اما نسخهٔ debug نه.
|
||||
فایل ./build/MyApp را بررسی کن، جایی را که این دو build از هم جدا میشوند پیدا کن و شواهدش را نشان بده.
|
||||
```
|
||||
|
||||
بهجای Notes، نام برنامهٔ هدف و قابلیتی را قرار دهید که میخواهید درک کنید.
|
||||
ایجنت خود را به سراغ هدفی بفرستید که مجوز تحلیلش را دارید و توضیح دهید چه چیزی را میخواهید درک کنید.
|
||||
|
||||
### استفاده از ترمینال
|
||||
|
||||
@@ -244,7 +261,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## تاریخچهٔ ستارهها
|
||||
|
||||
🎉 **۹۰٬۰۰۰ ستارهٔ GitHub — سپاسگزاریم!**
|
||||
🎉 **۱۱۰٬۰۰۰ ستارهٔ GitHub — سپاسگزاریم!**
|
||||
|
||||
از همهٔ کسانی که از REA استفاده میکنند، خطاها را گزارش میدهند، قابلیتهای جدید پیشنهاد میکنند، buildها را آزمایش میکنند و در بهبود پروژه مشارکت دارند سپاسگزاریم.
|
||||
|
||||
@@ -252,7 +269,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## سلب مسئولیت
|
||||
|
||||
REA ابزارهایی برای پژوهش، تحلیل و بازسازی قانونی در حوزهٔ مهندسی معکوس فراهم میکند. مسئولیت دریافت مجوزهای لازم و رعایت قوانین مربوط بر عهدهٔ شماست. این پروژه استفادهٔ غیرقانونی یا بدون مجوز را تأیید نمیکند.
|
||||
به [استفادهٔ مسئولانه](#استفادهٔ-مسئولانه) مراجعه کنید.
|
||||
|
||||
## مشارکت
|
||||
|
||||
|
||||
+24
-8
@@ -6,10 +6,9 @@
|
||||
|
||||
### Un seul MCP pour la rétro-ingénierie des binaires, des applications et du comportement à l'exécution.
|
||||
|
||||
**Repérez une fonctionnalité qui vous plaît. Comprenez son fonctionnement, jusqu'au niveau du binaire.**
|
||||
**Comprenez le fonctionnement des logiciels, jusqu'au niveau du binaire — pour la recherche en sécurité, l'interopérabilité et les logiciels que vous êtes autorisé à analyser.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
Vous avez repéré dans une application une fonctionnalité que vous aimeriez avoir dans votre propre produit ? Demandez à votre agent de l'étudier avec REA. Il peut inspecter l'application sans son code source, expliquer le fonctionnement de la fonctionnalité, présenter les preuves et en construire une version pour votre projet.
|
||||
REA aide les chercheurs en sécurité, les analystes de logiciels malveillants, les joueurs de CTF et les développeurs à comprendre des logiciels compilés grâce à leurs agents. Confiez à votre agent un binaire, une application ou une capture que vous êtes autorisé à analyser ; REA l'inspecte sans code source, explique son comportement et présente les preuves sur lesquelles repose chaque conclusion.
|
||||
|
||||
REA connecte votre agent à des outils d'inspection de binaires natifs, d'applications JavaScript et Electron, d'assemblys .NET et de sites web. Les mêmes outils sont aussi disponibles depuis votre terminal. L'analyse s'exécute localement, et chaque conclusion s'accompagne des preuves et des limites sur lesquelles elle repose.
|
||||
|
||||
@@ -56,6 +55,23 @@ Setup enregistre REA auprès de votre agent et installe les instructions de work
|
||||
|
||||
> **[Visitez le site web de REA](https://rea.tools/fr/)** pour les instructions d'installation, des guides illustrés et des études de cas réelles.
|
||||
|
||||
## Utilisation responsable
|
||||
|
||||
REA est conçu pour l'analyse licite : recherche en sécurité, analyse de logiciels malveillants et d'incidents, recherche de vulnérabilités et divulgation coordonnée, CTF et enseignement, interopérabilité, préservation des logiciels, ainsi que débogage ou audit de logiciels qui vous appartiennent ou que vous êtes autorisé à examiner.
|
||||
|
||||
Le projet REA ne soutient pas les usages suivants et n'y apportera aucune aide :
|
||||
|
||||
- contourner des DRM, des vérifications de licence, des systèmes anti-triche ou d'autres mesures techniques de protection ;
|
||||
- développer des triches pour des jeux ou altérer des services en ligne ;
|
||||
- contourner l'authentification, la protection contre les bots ou les contrôles d'accès de systèmes que vous n'êtes pas autorisé à tester ;
|
||||
- copier des logiciels propriétaires ou reproduire leur code et leurs ressources protégés ;
|
||||
- échapper aux politiques d'utilisation ou aux mesures de sécurité des fournisseurs de modèles d'IA ;
|
||||
- revendre ou partager des comptes d'IA ou des accès à des API.
|
||||
|
||||
Il vous appartient d'obtenir toute autorisation nécessaire et de respecter les lois, les contrats de licence et les conditions d'utilisation applicables. La rétro-ingénierie peut être restreinte dans votre juridiction ou par la licence du logiciel que vous analysez.
|
||||
|
||||
Les demandes d'aide pour les usages ci-dessus seront fermées sans réponse dans les issues et les discussions, et peuvent entraîner l'exclusion de la communauté REA.
|
||||
|
||||
## Démarrage rapide
|
||||
|
||||
### Configurer votre agent
|
||||
@@ -73,11 +89,11 @@ Setup prend en charge Claude Code, Codex, Cursor, Gemini CLI, Grok Build et [d'a
|
||||
### Interroger votre agent
|
||||
|
||||
```text
|
||||
Comprends comment fonctionne la recherche dans l'app Notes, montre-moi les preuves
|
||||
et construis une fonctionnalité similaire pour mon projet.
|
||||
La version release de mon app plante au démarrage, mais pas la version debug.
|
||||
Inspecte ./build/MyApp, trouve où les deux builds divergent et montre-moi les preuves.
|
||||
```
|
||||
|
||||
Remplacez Notes par votre application cible et la fonctionnalité que vous voulez comprendre.
|
||||
Confiez à votre agent une cible que vous êtes autorisé à analyser et décrivez ce que vous voulez comprendre.
|
||||
|
||||
### Utiliser le terminal
|
||||
|
||||
@@ -263,7 +279,7 @@ Signalez les vulnérabilités via [SECURITY.md](SECURITY.md).
|
||||
|
||||
## Historique des étoiles
|
||||
|
||||
🎉 **90 000 étoiles GitHub — merci !**
|
||||
🎉 **110 000 étoiles GitHub — merci !**
|
||||
|
||||
Merci à toutes les personnes qui utilisent REA, signalent des bugs, proposent des fonctionnalités, testent les builds et contribuent des correctifs.
|
||||
|
||||
@@ -277,7 +293,7 @@ Merci à toutes les personnes qui utilisent REA, signalent des bugs, proposent d
|
||||
|
||||
## Avertissement
|
||||
|
||||
REA fournit des outils destinés à la recherche, à l'analyse et à la reconstruction légales en rétro-ingénierie. Il vous appartient d'obtenir toute autorisation nécessaire et de respecter les lois applicables. Le projet n'encourage aucune utilisation illégale ou non autorisée.
|
||||
Voir [Utilisation responsable](#utilisation-responsable).
|
||||
|
||||
## Contribuer
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### Satu MCP untuk rekayasa balik pada berbagai biner, aplikasi, dan perilaku saat runtime.
|
||||
|
||||
**Melihat fitur yang Anda sukai. Pahami cara kerjanya hingga ke tingkat biner.**
|
||||
**Pahami cara kerja perangkat lunak hingga ke tingkat biner — untuk riset keamanan, interoperabilitas, dan perangkat lunak yang diizinkan untuk Anda analisis.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
Melihat fitur dalam suatu aplikasi yang ingin Anda hadirkan di produk sendiri? Minta agen Anda menyelidikinya menggunakan REA. REA dapat memeriksa aplikasi tanpa kode sumbernya, menjelaskan cara kerja fitur tersebut, menunjukkan bukti, dan membangun versi untuk proyek Anda.
|
||||
REA membantu peneliti keamanan, analis malware, pemain CTF, dan pengembang memahami perangkat lunak terkompilasi melalui agen mereka. Arahkan agen Anda ke biner, aplikasi, atau rekaman yang diizinkan untuk Anda analisis; REA memeriksanya tanpa kode sumber, menjelaskan perilakunya, dan menunjukkan bukti di balik setiap kesimpulan.
|
||||
|
||||
REA menghubungkan agen Anda dengan berbagai alat untuk memeriksa biner native, aplikasi JavaScript dan Electron, assembly .NET, serta situs web. Anda juga dapat menggunakan alat yang sama dari terminal. Analisis dijalankan secara lokal, dan hasilnya mencakup bukti serta keterbatasan yang mendasari setiap kesimpulan.
|
||||
|
||||
@@ -56,6 +55,23 @@ Proses penyiapan mendaftarkan REA ke agen Anda dan memasang instruksi alur kerja
|
||||
|
||||
> **[Kunjungi situs web REA](https://rea.tools/id/)** untuk mendapatkan petunjuk penyiapan, panduan bergambar, dan studi kasus nyata.
|
||||
|
||||
## Penggunaan yang Bertanggung Jawab
|
||||
|
||||
REA dibuat untuk analisis yang sah: riset keamanan, analisis malware dan insiden, riset kerentanan dan pengungkapan terkoordinasi, CTF dan pendidikan, interoperabilitas, pelestarian perangkat lunak, serta debugging atau audit perangkat lunak yang Anda miliki atau yang diizinkan untuk Anda periksa.
|
||||
|
||||
Proyek REA tidak mendukung, dan tidak akan membantu, hal-hal berikut:
|
||||
|
||||
- mengakali DRM, pemeriksaan lisensi, sistem anti-cheat, atau langkah perlindungan teknis lainnya;
|
||||
- mengembangkan cheat game atau memanipulasi layanan online;
|
||||
- melewati autentikasi, perlindungan bot, atau kontrol akses pada sistem yang tidak diizinkan untuk Anda uji;
|
||||
- menyalin perangkat lunak berpemilik atau mereproduksi kode dan aset yang dilindunginya;
|
||||
- menghindari kebijakan penggunaan atau langkah keamanan penyedia model AI;
|
||||
- menjual kembali atau membagikan akun AI atau akses API.
|
||||
|
||||
Anda bertanggung jawab untuk memperoleh otorisasi yang diperlukan dan mematuhi hukum, perjanjian lisensi, serta ketentuan layanan yang berlaku. Rekayasa balik mungkin dibatasi di yurisdiksi Anda atau oleh lisensi perangkat lunak yang Anda analisis.
|
||||
|
||||
Permintaan bantuan untuk penggunaan di atas akan ditutup tanpa tanggapan di issue dan diskusi, dan dapat berujung pada pengeluaran dari komunitas REA.
|
||||
|
||||
## Mulai Cepat
|
||||
|
||||
### Siapkan agen Anda
|
||||
@@ -73,10 +89,11 @@ Penyiapan mendukung Claude Code, Codex, Cursor, Gemini CLI, Grok Build, dan [age
|
||||
### Minta agen Anda
|
||||
|
||||
```text
|
||||
Pahami cara kerja pencarian di aplikasi Notes, tunjukkan buktinya, dan buat fitur serupa untuk proyek saya.
|
||||
Build rilis aplikasi saya crash saat dijalankan, tetapi build debug tidak.
|
||||
Periksa ./build/MyApp, temukan titik perbedaan kedua build, dan tunjukkan buktinya.
|
||||
```
|
||||
|
||||
Ganti Notes dengan aplikasi target dan fitur yang ingin Anda pahami.
|
||||
Arahkan agen Anda ke target yang diizinkan untuk Anda analisis dan jelaskan apa yang ingin Anda pahami.
|
||||
|
||||
### Gunakan terminal
|
||||
|
||||
@@ -284,7 +301,7 @@ Laporkan kerentanan melalui [SECURITY.md](SECURITY.md).
|
||||
|
||||
## Riwayat Bintang
|
||||
|
||||
🎉 **90.000 bintang GitHub, terima kasih!**
|
||||
🎉 **110.000 bintang GitHub, terima kasih!**
|
||||
|
||||
Terima kasih kepada semua orang yang menggunakan REA, melaporkan bug, meminta fitur, menguji build, dan berkontribusi dalam perbaikan.
|
||||
|
||||
@@ -298,7 +315,7 @@ Terima kasih kepada semua orang yang menggunakan REA, melaporkan bug, meminta fi
|
||||
|
||||
## Penafian
|
||||
|
||||
REA menyediakan alat untuk penelitian, analisis, dan rekonstruksi rekayasa balik yang sah. Anda bertanggung jawab untuk memperoleh otorisasi yang diperlukan dan mematuhi hukum yang berlaku. Proyek ini tidak mendukung penggunaan ilegal atau tanpa izin.
|
||||
Lihat [Penggunaan yang Bertanggung Jawab](#penggunaan-yang-bertanggung-jawab).
|
||||
|
||||
REA adalah proyek perangkat lunak sumber terbuka. Kami tidak menerbitkan atau mendukung mata uang kripto maupun token apa pun. Token yang menggunakan nama REA tidak berafiliasi dengan proyek ini.
|
||||
|
||||
|
||||
+24
-8
@@ -6,10 +6,9 @@
|
||||
|
||||
### Un unico MCP per l'ingegneria inversa su binari, applicazioni e comportamento a runtime.
|
||||
|
||||
**Vedi una funzionalità che ti piace. Capisci come funziona, fino al livello binario.**
|
||||
**Capisci come funziona il software, fino al livello binario — per la ricerca sulla sicurezza, l'interoperabilità e il software che sei autorizzato ad analizzare.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#cosa-puoi-analizzare)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
Hai visto una funzionalità in un'app che vorresti nel tuo prodotto? Chiedi al tuo agente di analizzarla con REA. Può ispezionare l'app senza il suo codice sorgente, spiegare come funziona la funzionalità, mostrare le prove e realizzare una versione per il tuo progetto.
|
||||
REA aiuta ricercatori di sicurezza, analisti di malware, giocatori di CTF e sviluppatori a capire il software compilato attraverso i loro agenti. Indirizza il tuo agente verso un binario, un'applicazione o un'acquisizione che sei autorizzato ad analizzare; REA lo ispeziona senza codice sorgente, ne spiega il comportamento e mostra le prove alla base di ogni conclusione.
|
||||
|
||||
REA collega il tuo agente a strumenti per ispezionare binari nativi, app JavaScript ed Electron, assembly .NET e siti web. Puoi usare gli stessi strumenti anche dal terminale. L'analisi viene eseguita localmente e i risultati includono le prove e i limiti dietro ogni conclusione.
|
||||
|
||||
@@ -56,6 +55,23 @@ La configurazione registra REA con il tuo agente e installa le istruzioni di wor
|
||||
|
||||
> **[Visita il sito web di REA](https://rea.tools/it/)** per istruzioni di configurazione, guide illustrate e veri casi studio.
|
||||
|
||||
## Uso responsabile
|
||||
|
||||
REA è pensato per l'analisi lecita: ricerca sulla sicurezza, analisi di malware e incidenti, ricerca di vulnerabilità e divulgazione coordinata, CTF e formazione, interoperabilità, conservazione del software, e debugging o audit di software che possiedi o che sei autorizzato a esaminare.
|
||||
|
||||
Il progetto REA non supporta i seguenti usi e non fornirà aiuto per:
|
||||
|
||||
- aggirare DRM, controlli di licenza, sistemi anti-cheat o altre misure tecniche di protezione;
|
||||
- sviluppare cheat per videogiochi o manomettere servizi online;
|
||||
- eludere autenticazione, protezioni anti-bot o controlli di accesso su sistemi che non sei autorizzato a testare;
|
||||
- copiare software proprietario o riprodurne il codice e gli asset protetti;
|
||||
- eludere le politiche d'uso o le misure di sicurezza dei fornitori di modelli di IA;
|
||||
- rivendere o condividere account di IA o accessi API.
|
||||
|
||||
Sei responsabile di ottenere le eventuali autorizzazioni richieste e di rispettare le leggi, i contratti di licenza e i termini di servizio applicabili. L'ingegneria inversa può essere limitata nella tua giurisdizione o dalla licenza del software che analizzi.
|
||||
|
||||
Le richieste di aiuto per gli usi sopra elencati verranno chiuse senza risposta nelle issue e nelle discussioni e possono comportare l'allontanamento dalla community di REA.
|
||||
|
||||
## Avvio rapido
|
||||
|
||||
### Configura il tuo agente
|
||||
@@ -73,11 +89,11 @@ La configurazione supporta Claude Code, Codex, Cursor, Gemini CLI, Grok Build e
|
||||
### Chiedi al tuo agente
|
||||
|
||||
```text
|
||||
Spiega come funziona la ricerca nell'app Note, mostrami le prove e realizza
|
||||
una funzionalità simile per il mio progetto.
|
||||
La build di release della mia app va in crash all'avvio, ma la build di debug no.
|
||||
Ispeziona ./build/MyApp, trova dove le due build divergono e mostrami le prove.
|
||||
```
|
||||
|
||||
Sostituisci Note con la tua app di destinazione e la funzionalità che vuoi capire.
|
||||
Indirizza il tuo agente verso un obiettivo che sei autorizzato ad analizzare e descrivi cosa vuoi capire.
|
||||
|
||||
### Usa il terminale
|
||||
|
||||
@@ -263,7 +279,7 @@ Segnala vulnerabilità tramite [SECURITY.md](SECURITY.md).
|
||||
|
||||
## Cronologia delle stelle
|
||||
|
||||
🎉 **90.000 stelle su GitHub — grazie!**
|
||||
🎉 **110.000 stelle su GitHub — grazie!**
|
||||
|
||||
Grazie a tutti coloro che usano REA, segnalano bug, propongono funzionalità, testano le build e contribuiscono con correzioni.
|
||||
|
||||
@@ -277,7 +293,7 @@ Grazie a tutti coloro che usano REA, segnalano bug, propongono funzionalità, te
|
||||
|
||||
## Disclaimer
|
||||
|
||||
REA fornisce strumenti per la ricerca, l'analisi e la ricostruzione di ingegneria inversa lecita. Sei responsabile di ottenere le eventuali autorizzazioni richieste e di rispettare le leggi applicabili. Il progetto non approva l'uso illegale o non autorizzato.
|
||||
Vedi [Uso responsabile](#uso-responsabile).
|
||||
|
||||
REA è un progetto software open-source. Non abbiamo emesso né approvato alcuna criptovaluta o token. I token che usano il nome REA non sono affiliati al progetto.
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### バイナリ、アプリケーション、実行時の動作を、ひとつの MCP でリバースエンジニアリング。
|
||||
|
||||
**気に入った機能を見つけたら、その仕組みをバイナリのレベルまで理解する。**
|
||||
**ソフトウェアの仕組みを、バイナリのレベルまで理解する。セキュリティ研究、相互運用性、そして分析を許可されたソフトウェアのために。**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
アプリに、自分の製品にも取り入れたい機能を見つけましたか?エージェントに REA で調査を依頼してください。ソースコードがなくてもアプリを調べ、機能の仕組みを説明し、根拠を示したうえで、あなたのプロジェクト向けに実装できます。
|
||||
REA は、セキュリティ研究者、マルウェアアナリスト、CTF プレイヤー、開発者が、エージェントを通じてコンパイル済みソフトウェアを理解できるよう支援します。分析を許可されたバイナリ、アプリケーション、キャプチャをエージェントに指定してください。REA はソースコードなしでそれを調べ、動作を説明し、各結論の根拠を示します。
|
||||
|
||||
REA はエージェントを分析ツールにつなぎ、ネイティブバイナリ、JavaScript や Electron のアプリ、.NET アセンブリ、ウェブサイトを調べられるようにします。同じツールをターミナルからも使えます。分析はローカルで実行され、結果には各結論を裏付ける根拠と制限事項が含まれます。
|
||||
|
||||
@@ -56,6 +55,23 @@ REA はエージェントを分析ツールにつなぎ、ネイティブバイ
|
||||
|
||||
> **[REA のウェブサイト](https://rea.tools/ja/)**で、セットアップ手順、図解ガイド、実際の事例を確認できます。
|
||||
|
||||
## 責任ある利用
|
||||
|
||||
REA は合法的な分析のために作られています。対象は、セキュリティ研究、マルウェアやインシデントの分析、脆弱性研究と協調的な開示、CTF と教育、相互運用性、ソフトウェアの保存、そして自分が所有するソフトウェアや調査を許可されたソフトウェアのデバッグや監査です。
|
||||
|
||||
REA プロジェクトは、次の用途を支持せず、その手助けも行いません。
|
||||
|
||||
- DRM、ライセンスチェック、アンチチートシステム、その他の技術的保護手段の回避
|
||||
- ゲームのチート開発やオンラインサービスの改ざん
|
||||
- テストを許可されていないシステムにおける認証、ボット対策、アクセス制御の回避
|
||||
- プロプライエタリソフトウェアの複製や、その保護されたコードやアセットの再現
|
||||
- AI モデル提供者の利用ポリシーや安全対策のすり抜け
|
||||
- AI アカウントや API アクセスの転売・共有
|
||||
|
||||
必要な許可を得て、適用される法律、ライセンス契約、利用規約に従う責任は利用者にあります。リバースエンジニアリングは、お住まいの法域や、分析するソフトウェアのライセンスによって制限されている場合があります。
|
||||
|
||||
上記の用途に関する支援の依頼は、issue やディスカッションで返答なしにクローズされ、REA コミュニティからの除外につながる場合があります。
|
||||
|
||||
## クイックスタート
|
||||
|
||||
### エージェントを設定する
|
||||
@@ -73,10 +89,11 @@ npx rea-agents setup
|
||||
### エージェントに依頼する
|
||||
|
||||
```text
|
||||
Notes アプリの検索機能の仕組みを調べ、根拠を示して、私のプロジェクトにも同様の機能を実装してください。
|
||||
私のアプリのリリースビルドは起動時にクラッシュしますが、デバッグビルドではクラッシュしません。
|
||||
./build/MyApp を調べて、2 つのビルドがどこで分岐するかを見つけ、根拠を示してください。
|
||||
```
|
||||
|
||||
Notes を対象のアプリに置き換え、理解したい機能を指定してください。
|
||||
分析を許可された対象をエージェントに指定し、理解したい内容を説明してください。
|
||||
|
||||
### ターミナルを使う
|
||||
|
||||
@@ -261,7 +278,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## スター履歴
|
||||
|
||||
🎉 **GitHub スター 90,000 件、ありがとうございます!**
|
||||
🎉 **GitHub スター 110,000 件、ありがとうございます!**
|
||||
|
||||
REA の利用、バグ報告、機能の要望、ビルドのテスト、修正への貢献に感謝します。
|
||||
|
||||
@@ -275,7 +292,7 @@ REA の利用、バグ報告、機能の要望、ビルドのテスト、修正
|
||||
|
||||
## 免責事項
|
||||
|
||||
REA は、合法的なリバースエンジニアリングの研究、分析、再構築のためのツールを提供します。必要な許可を得て、適用される法律に従う責任は利用者にあります。このプロジェクトは違法または無許可の利用を支持しません。
|
||||
[責任ある利用](#責任ある利用)を参照してください。
|
||||
|
||||
## 貢献する
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### 하나의 MCP로 바이너리, 애플리케이션, 런타임 동작을 리버스 엔지니어링합니다.
|
||||
|
||||
**마음에 드는 기능을 발견했다면, 바이너리 수준까지 작동 원리를 이해하세요.**
|
||||
**소프트웨어의 작동 원리를 바이너리 수준까지 이해하세요 — 보안 연구, 상호 운용성, 그리고 분석 권한이 있는 소프트웨어를 위해.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
앱에서 내 제품에도 넣고 싶은 기능을 발견했나요? 에이전트에게 REA로 조사해 달라고 요청하세요. 소스 코드가 없어도 앱을 검사하고, 기능의 작동 원리를 설명하고, 근거를 보여 주고, 프로젝트에 맞는 기능을 구현할 수 있습니다.
|
||||
REA는 보안 연구자, 악성코드 분석가, CTF 참가자, 개발자가 에이전트를 통해 컴파일된 소프트웨어를 이해하도록 돕습니다. 분석 권한이 있는 바이너리, 애플리케이션 또는 캡처를 에이전트에게 지정하세요. REA는 소스 코드 없이 이를 검사하고, 동작을 설명하고, 각 결론의 근거를 보여 줍니다.
|
||||
|
||||
REA는 에이전트를 분석 도구에 연결해 네이티브 바이너리, JavaScript와 Electron 앱, .NET 어셈블리, 웹사이트를 검사할 수 있게 합니다. 터미널에서도 같은 도구를 사용할 수 있습니다. 분석은 로컬에서 실행되며, 결과에는 각 결론의 근거와 한계가 포함됩니다.
|
||||
|
||||
@@ -56,6 +55,23 @@ REA는 에이전트를 분석 도구에 연결해 네이티브 바이너리, Jav
|
||||
|
||||
> **[REA 웹사이트](https://rea.tools/ko/)**에서 설정 방법, 그림으로 설명한 가이드, 실제 사례를 확인하세요.
|
||||
|
||||
## 책임 있는 사용
|
||||
|
||||
REA는 합법적인 분석을 위해 만들어졌습니다. 보안 연구, 악성코드 및 침해 사고 분석, 취약점 연구와 조율된 공개, CTF와 교육, 상호 운용성, 소프트웨어 보존, 그리고 직접 소유하거나 조사 권한이 있는 소프트웨어의 디버깅 및 감사가 여기에 해당합니다.
|
||||
|
||||
REA 프로젝트는 다음 용도를 지원하지 않으며, 이에 대한 도움도 제공하지 않습니다.
|
||||
|
||||
- DRM, 라이선스 검사, 안티치트 시스템 또는 기타 기술적 보호 조치 우회
|
||||
- 게임 치트 개발 또는 온라인 서비스 변조
|
||||
- 테스트 권한이 없는 시스템의 인증, 봇 방지 또는 접근 제어 우회
|
||||
- 독점 소프트웨어 복제 또는 보호된 코드와 에셋 재현
|
||||
- AI 모델 제공업체의 사용 정책이나 안전 조치 회피
|
||||
- AI 계정 또는 API 접근 권한 재판매나 공유
|
||||
|
||||
필요한 권한을 얻고 관련 법률, 라이선스 계약, 서비스 약관을 준수할 책임은 사용자에게 있습니다. 리버스 엔지니어링은 사용자의 관할권이나 분석하는 소프트웨어의 라이선스에 따라 제한될 수 있습니다.
|
||||
|
||||
위 용도에 대한 도움 요청은 issue와 토론에서 답변 없이 종료되며, REA 커뮤니티에서 제외될 수 있습니다.
|
||||
|
||||
## 빠른 시작
|
||||
|
||||
### 에이전트 설정
|
||||
@@ -73,10 +89,11 @@ npx rea-agents setup
|
||||
### 에이전트에게 요청
|
||||
|
||||
```text
|
||||
Notes 앱의 검색 기능이 어떻게 작동하는지 조사하고, 근거를 보여 준 다음, 내 프로젝트에 비슷한 기능을 구현해 줘.
|
||||
내 앱의 릴리스 빌드는 시작할 때 크래시가 나지만 디버그 빌드는 그렇지 않아.
|
||||
./build/MyApp을 검사해서 두 빌드가 어디서 갈라지는지 찾고 근거를 보여 줘.
|
||||
```
|
||||
|
||||
Notes를 대상 앱으로 바꾸고 이해하고 싶은 기능을 지정하세요.
|
||||
분석 권한이 있는 대상을 에이전트에게 지정하고 이해하고 싶은 내용을 설명하세요.
|
||||
|
||||
### 터미널 사용
|
||||
|
||||
@@ -261,7 +278,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## 스타 기록
|
||||
|
||||
🎉 **GitHub 스타 90,000개, 감사합니다!**
|
||||
🎉 **GitHub 스타 110,000개, 감사합니다!**
|
||||
|
||||
REA를 사용하고, 버그를 보고하고, 기능을 요청하고, 빌드를 테스트하고, 수정에 기여해 주신 모든 분께 감사드립니다.
|
||||
|
||||
@@ -275,7 +292,7 @@ REA를 사용하고, 버그를 보고하고, 기능을 요청하고, 빌드를
|
||||
|
||||
## 면책 조항
|
||||
|
||||
REA는 합법적인 리버스 엔지니어링 연구, 분석, 재구성을 위한 도구를 제공합니다. 필요한 권한을 얻고 관련 법률을 준수할 책임은 사용자에게 있습니다. 이 프로젝트는 불법적이거나 허가받지 않은 사용을 지지하지 않습니다.
|
||||
[책임 있는 사용](#책임-있는-사용)을 참고하세요.
|
||||
|
||||
## 기여
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### Jeden serwer MCP do inżynierii wstecznej plików binarnych, aplikacji i zachowania w czasie wykonywania.
|
||||
|
||||
**Widzisz funkcję, która Ci się podoba? Dowiedz się, jak działa, aż do poziomu kodu binarnego.**
|
||||
**Zrozum, jak działa oprogramowanie, aż do poziomu kodu binarnego — na potrzeby badań nad bezpieczeństwem, interoperacyjności i oprogramowania, do którego analizy masz uprawnienia.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
Widzisz w aplikacji funkcję, którą chcesz mieć we własnym produkcie? Poproś swojego agenta, aby zbadał ją za pomocą REA. Może przeanalizować aplikację bez jej kodu źródłowego, wyjaśnić działanie funkcji, pokazać dowody i stworzyć jej wersję dla Twojego projektu.
|
||||
REA pomaga badaczom bezpieczeństwa, analitykom złośliwego oprogramowania, uczestnikom CTF i programistom zrozumieć skompilowane oprogramowanie za pośrednictwem ich agentów. Wskaż agentowi plik binarny, aplikację lub przechwycenie ruchu, do których analizy masz uprawnienia; REA zbada wskazany cel bez kodu źródłowego, wyjaśni jego działanie i pokaże dowody stojące za każdym wnioskiem.
|
||||
|
||||
REA łączy Twojego agenta z narzędziami do analizy natywnych plików binarnych, aplikacji JavaScript i Electron, zestawów .NET oraz stron internetowych. Z tych samych narzędzi możesz korzystać w terminalu. Analiza odbywa się lokalnie, a wyniki zawierają dowody i ograniczenia stojące za każdym wnioskiem.
|
||||
|
||||
@@ -56,6 +55,23 @@ Konfigurator rejestruje REA w Twoim agencie i instaluje pasujące instrukcje pra
|
||||
|
||||
> **[Odwiedź stronę REA](https://rea.tools/pl/)**, aby znaleźć instrukcje konfiguracji, ilustrowane przewodniki i rzeczywiste studia przypadków.
|
||||
|
||||
## Odpowiedzialne korzystanie
|
||||
|
||||
REA powstało z myślą o analizie zgodnej z prawem: badaniach nad bezpieczeństwem, analizie złośliwego oprogramowania i incydentów, badaniu podatności i skoordynowanym ujawnianiu, CTF i edukacji, interoperacyjności, zachowywaniu oprogramowania oraz debugowaniu lub audytowaniu oprogramowania, które należy do Ciebie lub do którego badania masz uprawnienia.
|
||||
|
||||
Projekt REA nie wspiera następujących działań i nie będzie w nich pomagać:
|
||||
|
||||
- obchodzenia DRM, kontroli licencji, systemów anti-cheat ani innych technicznych środków ochrony;
|
||||
- tworzenia cheatów do gier ani ingerowania w usługi online;
|
||||
- obchodzenia uwierzytelniania, ochrony przed botami lub kontroli dostępu w systemach, do których testowania nie masz uprawnień;
|
||||
- kopiowania oprogramowania własnościowego ani odtwarzania jego chronionego kodu i zasobów;
|
||||
- omijania zasad użytkowania lub zabezpieczeń dostawców modeli AI;
|
||||
- odsprzedawania lub udostępniania kont AI albo dostępu do API.
|
||||
|
||||
Odpowiadasz za uzyskanie wymaganych zezwoleń oraz przestrzeganie obowiązującego prawa, umów licencyjnych i warunków korzystania z usług. Inżynieria wsteczna może być ograniczona w Twojej jurysdykcji lub przez licencję analizowanego oprogramowania.
|
||||
|
||||
Prośby o pomoc w powyższych działaniach będą zamykane bez odpowiedzi w zgłoszeniach i dyskusjach i mogą skutkować usunięciem ze społeczności REA.
|
||||
|
||||
## Szybki start
|
||||
|
||||
### Skonfiguruj agenta
|
||||
@@ -73,10 +89,11 @@ Konfigurator obsługuje Claude Code, Codex, Cursor, Gemini CLI, Grok Build i [in
|
||||
### Zapytaj agenta
|
||||
|
||||
```text
|
||||
Sprawdź, jak działa wyszukiwanie w aplikacji Notes, pokaż mi dowody i stwórz podobną funkcję dla mojego projektu.
|
||||
Wersja release mojej aplikacji ulega awarii przy uruchomieniu, a wersja debug nie.
|
||||
Zbadaj ./build/MyApp, znajdź miejsce, w którym obie kompilacje się rozchodzą, i pokaż mi dowody.
|
||||
```
|
||||
|
||||
Zastąp Notes wybraną aplikacją i wskaż funkcję, którą chcesz zrozumieć.
|
||||
Wskaż agentowi cel, do którego analizy masz uprawnienia, i opisz, co chcesz zrozumieć.
|
||||
|
||||
### Korzystaj z terminala
|
||||
|
||||
@@ -259,7 +276,7 @@ Zgłaszaj podatności zgodnie z [SECURITY.md](SECURITY.md).
|
||||
|
||||
## Historia gwiazdek
|
||||
|
||||
🎉 **90 000 gwiazdek na GitHubie — dziękujemy!**
|
||||
🎉 **110 000 gwiazdek na GitHubie — dziękujemy!**
|
||||
|
||||
Dziękujemy wszystkim, którzy używają REA, zgłaszają błędy, proponują nowe funkcje, testują kompilacje i przesyłają poprawki.
|
||||
|
||||
@@ -273,7 +290,7 @@ Dziękujemy wszystkim, którzy używają REA, zgłaszają błędy, proponują no
|
||||
|
||||
## Zastrzeżenie
|
||||
|
||||
REA dostarcza narzędzia do zgodnych z prawem badań z zakresu inżynierii wstecznej, analizy i odtwarzania. Odpowiadasz za uzyskanie wymaganych zezwoleń i przestrzeganie obowiązującego prawa. Projekt nie popiera działań nielegalnych ani nieautoryzowanych.
|
||||
Zobacz [Odpowiedzialne korzystanie](#odpowiedzialne-korzystanie).
|
||||
|
||||
## Współtworzenie
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### Um único MCP para engenharia reversa de binários, aplicativos e comportamento em execução.
|
||||
|
||||
**Viu uma funcionalidade de que gostou? Entenda como ela funciona, até o nível binário.**
|
||||
**Entenda como o software funciona, até o nível binário — para pesquisa de segurança, interoperabilidade e softwares que você tem autorização para analisar.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
Viu uma funcionalidade em um aplicativo que gostaria de ter no seu próprio produto? Peça ao seu agente para investigá-la com o REA. Ele pode inspecionar o aplicativo sem o código-fonte, explicar como a funcionalidade funciona, apresentar as evidências e criar uma versão para o seu projeto.
|
||||
O REA ajuda pesquisadores de segurança, analistas de malware, jogadores de CTF e desenvolvedores a entender softwares compilados por meio de seus agentes. Aponte seu agente para um binário, aplicativo ou captura que você tenha autorização para analisar; o REA o inspeciona sem o código-fonte, explica seu comportamento e mostra as evidências por trás de cada conclusão.
|
||||
|
||||
O REA conecta seu agente a ferramentas para inspecionar binários nativos, aplicativos JavaScript e Electron, assemblies .NET e sites. Você também pode usar as mesmas ferramentas pelo terminal. A análise acontece localmente, e os resultados incluem as evidências e limitações por trás de cada conclusão.
|
||||
|
||||
@@ -56,6 +55,23 @@ A configuração registra o REA no seu agente e instala as instruções de fluxo
|
||||
|
||||
> **[Visite o site do REA](https://rea.tools/pt-BR/)** para instruções de configuração, guias ilustrados e estudos de caso reais.
|
||||
|
||||
## Uso responsável
|
||||
|
||||
O REA foi criado para análises lícitas: pesquisa de segurança, análise de malware e de incidentes, pesquisa de vulnerabilidades e divulgação coordenada, CTFs e educação, interoperabilidade, preservação de software e depuração ou auditoria de software que você possui ou tem autorização para examinar.
|
||||
|
||||
O projeto REA não apoia e não ajudará com:
|
||||
|
||||
- contornar DRM, verificações de licença, sistemas anti-cheat ou outras medidas técnicas de proteção;
|
||||
- desenvolver cheats para jogos ou adulterar serviços online;
|
||||
- burlar autenticação, proteção contra bots ou controles de acesso em sistemas que você não tem autorização para testar;
|
||||
- copiar software proprietário ou reproduzir seu código e seus recursos protegidos;
|
||||
- driblar as políticas de uso ou as medidas de segurança de provedores de modelos de IA;
|
||||
- revender ou compartilhar contas de IA ou acesso a APIs.
|
||||
|
||||
Você é responsável por obter as autorizações necessárias e cumprir as leis, os contratos de licença e os termos de serviço aplicáveis. A engenharia reversa pode ser restrita na sua jurisdição ou pela licença do software que você analisa.
|
||||
|
||||
Pedidos de ajuda para os usos acima serão fechados sem resposta em issues e discussões e podem resultar na remoção da comunidade do REA.
|
||||
|
||||
## Início rápido
|
||||
|
||||
### Configure seu agente
|
||||
@@ -73,10 +89,11 @@ A configuração oferece suporte ao Claude Code, Codex, Cursor, Gemini CLI, Grok
|
||||
### Peça ao seu agente
|
||||
|
||||
```text
|
||||
Investigue como a busca funciona no aplicativo Notes, mostre as evidências e crie uma funcionalidade semelhante para o meu projeto.
|
||||
O build de release do meu aplicativo trava na inicialização, mas o build de debug não.
|
||||
Inspecione ./build/MyApp, descubra onde os dois builds divergem e mostre as evidências.
|
||||
```
|
||||
|
||||
Substitua Notes pelo aplicativo alvo e indique a funcionalidade que deseja entender.
|
||||
Aponte seu agente para um alvo que você tenha autorização para analisar e descreva o que deseja entender.
|
||||
|
||||
### Use o terminal
|
||||
|
||||
@@ -259,7 +276,7 @@ Relate vulnerabilidades conforme [SECURITY.md](SECURITY.md).
|
||||
|
||||
## Histórico de estrelas
|
||||
|
||||
🎉 **90.000 estrelas no GitHub — muito obrigado!**
|
||||
🎉 **110.000 estrelas no GitHub — muito obrigado!**
|
||||
|
||||
Obrigado a todos que usam o REA, relatam bugs, sugerem funcionalidades, testam builds e contribuem com correções.
|
||||
|
||||
@@ -273,7 +290,7 @@ Obrigado a todos que usam o REA, relatam bugs, sugerem funcionalidades, testam b
|
||||
|
||||
## Aviso legal
|
||||
|
||||
O REA fornece ferramentas para pesquisa, análise e reconstrução por engenharia reversa dentro da lei. Você é responsável por obter as autorizações necessárias e cumprir as leis aplicáveis. O projeto não apoia usos ilegais ou não autorizados.
|
||||
Veja [Uso responsável](#uso-responsável).
|
||||
|
||||
## Contribuir
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### Один MCP для реверс-инжиниринга бинарных файлов, приложений и поведения во время выполнения.
|
||||
|
||||
**Нашли интересную функцию? Разберитесь, как она работает, вплоть до двоичного кода.**
|
||||
**Разберитесь, как работает программное обеспечение, вплоть до двоичного кода — для исследований безопасности, обеспечения совместимости и работы с ПО, которое вам разрешено анализировать.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
Увидели в приложении функцию, которую хотите добавить в свой продукт? Попросите агента исследовать её с помощью REA. Он может изучить приложение без исходного кода, объяснить работу функции, показать подтверждающие данные и создать подходящую реализацию для вашего проекта.
|
||||
REA помогает исследователям безопасности, аналитикам вредоносного ПО, участникам CTF и разработчикам разбираться в скомпилированных программах с помощью их агентов. Укажите агенту бинарный файл, приложение или захват, которые вам разрешено анализировать; REA изучит их без исходного кода, объяснит их поведение и покажет данные, подтверждающие каждый вывод.
|
||||
|
||||
REA подключает вашего агента к инструментам для исследования нативных бинарных файлов, приложений JavaScript и Electron, сборок .NET и сайтов. Те же инструменты доступны в терминале. Анализ выполняется локально, а результаты содержат данные и ограничения, на которых основан каждый вывод.
|
||||
|
||||
@@ -56,6 +55,23 @@ REA подключает вашего агента к инструментам
|
||||
|
||||
> **[Посетите сайт REA](https://rea.tools/ru/)**: там есть инструкции по настройке, иллюстрированные руководства и реальные исследования.
|
||||
|
||||
## Ответственное использование
|
||||
|
||||
REA создан для законного анализа: исследований безопасности, анализа вредоносного ПО и инцидентов, исследования уязвимостей и скоординированного раскрытия, CTF и обучения, обеспечения совместимости, сохранения программного обеспечения, а также отладки или аудита ПО, которым вы владеете или которое вам разрешено изучать.
|
||||
|
||||
Проект REA не поддерживает следующее и не будет с этим помогать:
|
||||
|
||||
- обход DRM, проверок лицензий, античит-систем и других технических средств защиты;
|
||||
- разработка читов для игр или вмешательство в работу онлайн-сервисов;
|
||||
- обход аутентификации, защиты от ботов или контроля доступа в системах, которые вам не разрешено тестировать;
|
||||
- копирование проприетарного ПО или воспроизведение его защищённого кода и ресурсов;
|
||||
- уклонение от правил использования или мер безопасности поставщиков ИИ-моделей;
|
||||
- перепродажа или передача аккаунтов ИИ-сервисов или доступа к API.
|
||||
|
||||
Вы отвечаете за получение необходимых разрешений и соблюдение применимых законов, лицензионных соглашений и условий обслуживания. Реверс-инжиниринг может быть ограничен в вашей юрисдикции или лицензией анализируемого ПО.
|
||||
|
||||
Запросы о помощи в перечисленных выше целях будут закрываться без ответа в issues и обсуждениях и могут привести к исключению из сообщества REA.
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
### Настройте агента
|
||||
@@ -73,10 +89,11 @@ npx rea-agents setup
|
||||
### Задайте вопрос агенту
|
||||
|
||||
```text
|
||||
Исследуй, как работает поиск в приложении Notes, покажи подтверждающие данные и создай похожую функцию для моего проекта.
|
||||
Release-сборка моего приложения падает при запуске, а debug-сборка — нет.
|
||||
Изучи ./build/MyApp, найди, где расходятся две сборки, и покажи подтверждающие данные.
|
||||
```
|
||||
|
||||
Замените Notes нужным приложением и укажите функцию, которую хотите понять.
|
||||
Укажите агенту цель, которую вам разрешено анализировать, и опишите, что вы хотите понять.
|
||||
|
||||
### Используйте терминал
|
||||
|
||||
@@ -259,7 +276,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## История звёзд
|
||||
|
||||
🎉 **90 000 звёзд на GitHub — спасибо!**
|
||||
🎉 **110 000 звёзд на GitHub — спасибо!**
|
||||
|
||||
Спасибо всем, кто использует REA, сообщает об ошибках, предлагает новые функции, тестирует сборки и помогает с исправлениями.
|
||||
|
||||
@@ -273,7 +290,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## Отказ от ответственности
|
||||
|
||||
REA предоставляет инструменты для законных исследований, анализа и реконструкции с помощью реверс-инжиниринга. Вы отвечаете за получение необходимых разрешений и соблюдение применимых законов. Проект не поддерживает незаконное или несанкционированное использование.
|
||||
См. [Ответственное использование](#ответственное-использование).
|
||||
|
||||
## Участие в проекте
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### MCP เดียวสำหรับวิศวกรรมย้อนกลับ ทั้งไบนารี แอปพลิเคชัน และพฤติกรรมขณะทำงาน
|
||||
|
||||
**เห็นฟีเจอร์ที่ชอบแล้วอยากรู้ว่าทำงานอย่างไร? เข้าใจได้ลึกถึงระดับไบนารี**
|
||||
**เข้าใจว่าซอฟต์แวร์ทำงานอย่างไรได้ลึกถึงระดับไบนารี — สำหรับงานวิจัยด้านความปลอดภัย การทำงานร่วมกันได้ และซอฟต์แวร์ที่คุณได้รับอนุญาตให้วิเคราะห์**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
เห็นฟีเจอร์ในแอปที่อยากนำมาใช้ในผลิตภัณฑ์ของคุณเองหรือไม่? ขอให้เอเจนต์ของคุณตรวจสอบด้วย REA เอเจนต์สามารถวิเคราะห์แอปโดยไม่ต้องมีซอร์สโค้ด อธิบายการทำงานของฟีเจอร์ แสดงหลักฐาน และสร้างเวอร์ชันสำหรับโปรเจกต์ของคุณได้
|
||||
REA ช่วยให้นักวิจัยด้านความปลอดภัย นักวิเคราะห์มัลแวร์ ผู้เล่น CTF และนักพัฒนาเข้าใจซอฟต์แวร์ที่คอมไพล์แล้วผ่านเอเจนต์ของตน ชี้เอเจนต์ของคุณไปยังไบนารี แอปพลิเคชัน หรือข้อมูลที่บันทึกไว้ซึ่งคุณได้รับอนุญาตให้วิเคราะห์ แล้ว REA จะตรวจสอบโดยไม่ต้องมีซอร์สโค้ด อธิบายพฤติกรรม และแสดงหลักฐานเบื้องหลังข้อสรุปแต่ละข้อ
|
||||
|
||||
REA เชื่อมเอเจนต์ของคุณกับเครื่องมือสำหรับตรวจสอบไบนารีเนทีฟ แอป JavaScript และ Electron แอสเซมบลี .NET และเว็บไซต์ คุณใช้เครื่องมือเดียวกันจากเทอร์มินัลได้ การวิเคราะห์ทำงานบนเครื่องของคุณ และผลลัพธ์มีทั้งหลักฐานและข้อจำกัดที่ประกอบแต่ละข้อสรุป
|
||||
|
||||
@@ -56,6 +55,23 @@ REA เชื่อมเอเจนต์ของคุณกับเคร
|
||||
|
||||
> **[เยี่ยมชมเว็บไซต์ REA](https://rea.tools/th/)** เพื่อดูคำแนะนำการตั้งค่า คู่มือพร้อมภาพประกอบ และกรณีศึกษาจริง
|
||||
|
||||
## การใช้งานอย่างรับผิดชอบ
|
||||
|
||||
REA สร้างขึ้นเพื่อการวิเคราะห์ที่ถูกกฎหมาย ได้แก่ การวิจัยด้านความปลอดภัย การวิเคราะห์มัลแวร์และเหตุการณ์ด้านความปลอดภัย การวิจัยช่องโหว่และการเปิดเผยข้อมูลแบบประสานงาน CTF และการศึกษา การทำงานร่วมกันได้ การอนุรักษ์ซอฟต์แวร์ ตลอดจนการดีบักหรือตรวจสอบซอฟต์แวร์ที่คุณเป็นเจ้าของหรือได้รับอนุญาตให้ตรวจสอบ
|
||||
|
||||
โปรเจกต์ REA ไม่สนับสนุนและจะไม่ช่วยเหลือในเรื่องต่อไปนี้
|
||||
|
||||
- การหลบเลี่ยง DRM การตรวจสอบไลเซนส์ ระบบป้องกันการโกง หรือมาตรการป้องกันทางเทคนิคอื่น ๆ
|
||||
- การพัฒนาโปรแกรมโกงเกมหรือการแทรกแซงบริการออนไลน์
|
||||
- การข้ามการยืนยันตัวตน การป้องกันบอต หรือการควบคุมการเข้าถึงบนระบบที่คุณไม่ได้รับอนุญาตให้ทดสอบ
|
||||
- การคัดลอกซอฟต์แวร์กรรมสิทธิ์หรือการทำซ้ำโค้ดและแอสเซ็ตที่ได้รับการคุ้มครอง
|
||||
- การหลีกเลี่ยงนโยบายการใช้งานหรือมาตรการด้านความปลอดภัยของผู้ให้บริการโมเดล AI
|
||||
- การขายต่อหรือแบ่งปันบัญชี AI หรือสิทธิ์การเข้าถึง API
|
||||
|
||||
คุณมีหน้าที่ขออนุญาตตามที่จำเป็นและปฏิบัติตามกฎหมาย ข้อตกลงไลเซนส์ และข้อกำหนดในการให้บริการที่เกี่ยวข้อง วิศวกรรมย้อนกลับอาจถูกจำกัดในเขตอำนาจศาลของคุณหรือโดยไลเซนส์ของซอฟต์แวร์ที่คุณวิเคราะห์
|
||||
|
||||
คำขอความช่วยเหลือสำหรับการใช้งานข้างต้นใน issue และการสนทนาจะถูกปิดโดยไม่มีการตอบกลับ และอาจส่งผลให้ถูกนำออกจากชุมชน REA
|
||||
|
||||
## เริ่มต้นอย่างรวดเร็ว
|
||||
|
||||
### ตั้งค่าเอเจนต์
|
||||
@@ -73,10 +89,11 @@ npx rea-agents setup
|
||||
### ถามเอเจนต์
|
||||
|
||||
```text
|
||||
ตรวจสอบว่าการค้นหาในแอป Notes ทำงานอย่างไร แสดงหลักฐานให้ฉันดู และสร้างฟีเจอร์ที่คล้ายกันสำหรับโปรเจกต์ของฉัน
|
||||
บิลด์ release ของแอปฉันแครชตอนเริ่มทำงาน แต่บิลด์ debug ไม่แครช
|
||||
ตรวจสอบ ./build/MyApp หาจุดที่สองบิลด์แตกต่างกัน และแสดงหลักฐานให้ฉันดู
|
||||
```
|
||||
|
||||
แทนที่ Notes ด้วยแอปเป้าหมายและฟีเจอร์ที่คุณต้องการเข้าใจ
|
||||
ชี้เอเจนต์ของคุณไปยังเป้าหมายที่คุณได้รับอนุญาตให้วิเคราะห์ และอธิบายสิ่งที่คุณต้องการเข้าใจ
|
||||
|
||||
### ใช้เทอร์มินัล
|
||||
|
||||
@@ -259,7 +276,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## ประวัติดาว
|
||||
|
||||
🎉 **90,000 ดาวบน GitHub — ขอบคุณทุกคน!**
|
||||
🎉 **110,000 ดาวบน GitHub — ขอบคุณทุกคน!**
|
||||
|
||||
ขอบคุณทุกคนที่ใช้ REA รายงานบั๊ก เสนอฟีเจอร์ ทดสอบบิลด์ และช่วยแก้ไข
|
||||
|
||||
@@ -273,7 +290,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## ข้อสงวนสิทธิ์
|
||||
|
||||
REA มีเครื่องมือสำหรับการวิจัยวิศวกรรมย้อนกลับ การวิเคราะห์ และการสร้างขึ้นใหม่อย่างถูกกฎหมาย คุณมีหน้าที่ขออนุญาตตามที่จำเป็นและปฏิบัติตามกฎหมายที่เกี่ยวข้อง โปรเจกต์นี้ไม่สนับสนุนการใช้งานที่ผิดกฎหมายหรือไม่ได้รับอนุญาต
|
||||
ดู[การใช้งานอย่างรับผิดชอบ](#การใช้งานอย่างรับผิดชอบ)
|
||||
|
||||
## การมีส่วนร่วม
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### İkili dosyaların, uygulamaların ve çalışma zamanı davranışının tersine mühendisliği için tek bir MCP.
|
||||
|
||||
**Beğendiğiniz bir özellik mi gördünüz? İkili kod düzeyine kadar nasıl çalıştığını anlayın.**
|
||||
**Yazılımın nasıl çalıştığını ikili kod düzeyine kadar anlayın — güvenlik araştırmaları, birlikte çalışabilirlik ve analiz etme yetkiniz olan yazılımlar için.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
Bir uygulamada kendi ürününüze eklemek istediğiniz bir özellik mi gördünüz? Ajanınızdan REA ile araştırmasını isteyin. Kaynak kodu olmadan uygulamayı inceleyebilir, özelliğin nasıl çalıştığını açıklayabilir, kanıtları gösterebilir ve projenize uygun bir sürümünü geliştirebilir.
|
||||
REA; güvenlik araştırmacılarının, kötü amaçlı yazılım analistlerinin, CTF oyuncularının ve geliştiricilerin derlenmiş yazılımları ajanları aracılığıyla anlamasına yardımcı olur. Ajanınızı analiz etme yetkiniz olan bir ikili dosyaya, uygulamaya veya yakalamaya yönlendirin; REA onu kaynak kodu olmadan inceler, davranışını açıklar ve her sonucun dayandığı kanıtları gösterir.
|
||||
|
||||
REA, ajanınızı yerel makine kodu içeren ikili dosyaları, JavaScript ve Electron uygulamalarını, .NET derlemelerini ve web sitelerini inceleyen araçlara bağlar. Aynı araçları terminalden de kullanabilirsiniz. Analiz yerel olarak çalışır; sonuçlar her çıkarımın dayandığı kanıtları ve sınırlamaları içerir.
|
||||
|
||||
@@ -56,6 +55,23 @@ Kurulum, REA'yı ajanınıza kaydeder ve uyumlu iş akışı talimatlarını yü
|
||||
|
||||
> Kurulum talimatları, görselli rehberler ve gerçek örnek çalışmalar için **[REA web sitesini ziyaret edin](https://rea.tools/tr/)**.
|
||||
|
||||
## Sorumlu kullanım
|
||||
|
||||
REA yasal analiz için tasarlanmıştır: güvenlik araştırmaları, kötü amaçlı yazılım ve olay analizi, güvenlik açığı araştırması ve koordineli açıklama, CTF'ler ve eğitim, birlikte çalışabilirlik, yazılımların korunması ve sahibi olduğunuz ya da inceleme yetkiniz olan yazılımlarda hata ayıklama veya denetim.
|
||||
|
||||
REA projesi aşağıdakileri desteklemez ve bunlara yardım etmez:
|
||||
|
||||
- DRM'yi, lisans denetimlerini, hile önleme sistemlerini veya diğer teknik koruma önlemlerini atlatmak;
|
||||
- oyun hileleri geliştirmek veya çevrim içi hizmetlere müdahale etmek;
|
||||
- test etme yetkiniz olmayan sistemlerde kimlik doğrulamayı, bot korumasını veya erişim denetimlerini aşmak;
|
||||
- tescilli yazılımları kopyalamak veya korunan kodlarını ve varlıklarını yeniden üretmek;
|
||||
- yapay zekâ modeli sağlayıcılarının kullanım politikalarından veya güvenlik önlemlerinden kaçmak;
|
||||
- yapay zekâ hesaplarını veya API erişimini yeniden satmak ya da paylaşmak.
|
||||
|
||||
Gerekli izinleri almak ve geçerli yasalara, lisans sözleşmelerine ve hizmet şartlarına uymak sizin sorumluluğunuzdadır. Tersine mühendislik, bulunduğunuz yargı bölgesinde veya analiz ettiğiniz yazılımın lisansı tarafından kısıtlanmış olabilir.
|
||||
|
||||
Yukarıdaki kullanımlar için yardım talepleri, issue'larda ve tartışmalarda yanıt verilmeden kapatılır ve REA topluluğundan çıkarılmaya yol açabilir.
|
||||
|
||||
## Hızlı başlangıç
|
||||
|
||||
### Ajanınızı yapılandırın
|
||||
@@ -73,10 +89,11 @@ Kurulum Claude Code, Codex, Cursor, Gemini CLI, Grok Build ve [diğer ajanları]
|
||||
### Ajanınıza sorun
|
||||
|
||||
```text
|
||||
Notes uygulamasında aramanın nasıl çalıştığını araştır, kanıtları göster ve projem için benzer bir özellik geliştir.
|
||||
Uygulamamın release derlemesi açılışta çöküyor ama debug derlemesi çökmüyor.
|
||||
./build/MyApp dosyasını incele, iki derlemenin nerede ayrıştığını bul ve kanıtları göster.
|
||||
```
|
||||
|
||||
Notes yerine hedef uygulamanızı ve anlamak istediğiniz özelliği belirtin.
|
||||
Ajanınızı analiz etme yetkiniz olan bir hedefe yönlendirin ve neyi anlamak istediğinizi açıklayın.
|
||||
|
||||
### Terminali kullanın
|
||||
|
||||
@@ -259,7 +276,7 @@ Güvenlik açıklarını [SECURITY.md](SECURITY.md) üzerinden bildirin.
|
||||
|
||||
## Yıldız geçmişi
|
||||
|
||||
🎉 **GitHub'da 90.000 yıldız — teşekkürler!**
|
||||
🎉 **GitHub'da 110.000 yıldız — teşekkürler!**
|
||||
|
||||
REA'yı kullanan, hata bildiren, özellik talep eden, derlemeleri test eden ve düzeltmelere katkıda bulunan herkese teşekkürler.
|
||||
|
||||
@@ -273,7 +290,7 @@ REA'yı kullanan, hata bildiren, özellik talep eden, derlemeleri test eden ve d
|
||||
|
||||
## Sorumluluk reddi
|
||||
|
||||
REA, yasal tersine mühendislik araştırması, analizi ve yeniden oluşturma için araçlar sağlar. Gerekli izinleri almak ve geçerli yasalara uymak sizin sorumluluğunuzdadır. Proje yasa dışı veya yetkisiz kullanımı desteklemez.
|
||||
Bkz. [Sorumlu kullanım](#sorumlu-kullanım).
|
||||
|
||||
## Katkıda bulunma
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### Один MCP для реверс-інжинірингу бінарних файлів, застосунків і поведінки під час виконання.
|
||||
|
||||
**Побачили цікаву функцію? Зрозумійте, як вона працює, аж до рівня двійкового коду.**
|
||||
**Зрозумійте, як працює програмне забезпечення, аж до рівня двійкового коду — для досліджень безпеки, забезпечення сумісності та роботи з ПЗ, яке вам дозволено аналізувати.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
Побачили в застосунку функцію, яку хочете додати до свого продукту? Попросіть агента дослідити її за допомогою REA. Він може перевірити застосунок без вихідного коду, пояснити роботу функції, показати підтвердження та створити реалізацію для вашого проєкту.
|
||||
REA допомагає дослідникам безпеки, аналітикам шкідливого ПЗ, учасникам CTF і розробникам розбиратися в скомпільованому програмному забезпеченні за допомогою їхніх агентів. Вкажіть агенту бінарний файл, застосунок або захоплення, які вам дозволено аналізувати; REA дослідить їх без вихідного коду, пояснить їхню поведінку й покаже підтвердження кожного висновку.
|
||||
|
||||
REA підключає вашого агента до інструментів дослідження нативних бінарних файлів, застосунків JavaScript і Electron, збірок .NET та сайтів. Ті самі інструменти можна використовувати з термінала. Аналіз виконується локально, а результати містять підтвердження й обмеження, на яких ґрунтується кожен висновок.
|
||||
|
||||
@@ -56,6 +55,23 @@ REA підключає вашого агента до інструментів
|
||||
|
||||
> **[Відвідайте сайт REA](https://rea.tools/uk/)**, щоб знайти інструкції налаштування, ілюстровані посібники та реальні дослідження.
|
||||
|
||||
## Відповідальне використання
|
||||
|
||||
REA створено для законного аналізу: досліджень безпеки, аналізу шкідливого ПЗ та інцидентів, дослідження вразливостей і скоординованого розкриття, CTF і навчання, забезпечення сумісності, збереження програмного забезпечення, а також налагодження чи аудиту ПЗ, яким ви володієте або яке вам дозволено досліджувати.
|
||||
|
||||
Проєкт REA не підтримує таких дій і не допомагатиме з ними:
|
||||
|
||||
- обхід DRM, перевірок ліцензій, античит-систем та інших технічних засобів захисту;
|
||||
- розробка читів для ігор або втручання в роботу онлайн-сервісів;
|
||||
- обхід автентифікації, захисту від ботів або контролю доступу в системах, які вам не дозволено тестувати;
|
||||
- копіювання пропрієтарного ПЗ або відтворення його захищеного коду й ресурсів;
|
||||
- ухиляння від правил використання або заходів безпеки постачальників моделей ШІ;
|
||||
- перепродаж або передавання облікових записів ШІ чи доступу до API.
|
||||
|
||||
Ви відповідаєте за отримання необхідних дозволів і дотримання застосовних законів, ліцензійних угод та умов обслуговування. Реверс-інжиніринг може бути обмежений у вашій юрисдикції або ліцензією програмного забезпечення, яке ви аналізуєте.
|
||||
|
||||
Запити про допомогу з переліченими вище діями закриватимуться без відповіді в issues та обговореннях і можуть призвести до виключення зі спільноти REA.
|
||||
|
||||
## Швидкий старт
|
||||
|
||||
### Налаштуйте агента
|
||||
@@ -73,10 +89,11 @@ npx rea-agents setup
|
||||
### Запитайте агента
|
||||
|
||||
```text
|
||||
Досліди, як працює пошук у застосунку Notes, покажи підтвердження й створи подібну функцію для мого проєкту.
|
||||
Release-збірка мого застосунку падає під час запуску, а debug-збірка — ні.
|
||||
Досліди ./build/MyApp, знайди, де розходяться дві збірки, і покажи підтвердження.
|
||||
```
|
||||
|
||||
Замініть Notes цільовим застосунком і вкажіть функцію, яку хочете зрозуміти.
|
||||
Вкажіть агенту ціль, яку вам дозволено аналізувати, і опишіть, що ви хочете зрозуміти.
|
||||
|
||||
### Використовуйте термінал
|
||||
|
||||
@@ -259,7 +276,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## Історія зірок
|
||||
|
||||
🎉 **90 000 зірок на GitHub — дякуємо!**
|
||||
🎉 **110 000 зірок на GitHub — дякуємо!**
|
||||
|
||||
Дякуємо всім, хто використовує REA, повідомляє про помилки, пропонує нові функції, тестує збірки й допомагає з виправленнями.
|
||||
|
||||
@@ -273,7 +290,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## Відмова від відповідальності
|
||||
|
||||
REA надає інструменти для законних досліджень, аналізу й реконструкції за допомогою реверс-інжинірингу. Ви відповідаєте за отримання необхідних дозволів і дотримання застосовних законів. Проєкт не підтримує незаконне або несанкціоноване використання.
|
||||
Див. [Відповідальне використання](#відповідальне-використання).
|
||||
|
||||
## Участь у проєкті
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### Một MCP để dịch ngược tệp nhị phân, ứng dụng và hành vi khi chạy.
|
||||
|
||||
**Thấy một tính năng bạn thích? Hiểu cách nó hoạt động, đến tận mức mã nhị phân.**
|
||||
**Hiểu cách phần mềm hoạt động, đến tận mức mã nhị phân — phục vụ nghiên cứu bảo mật, khả năng tương tác và phần mềm bạn được phép phân tích.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
Bạn thấy một tính năng trong ứng dụng và muốn đưa nó vào sản phẩm của mình? Hãy nhờ tác nhân của bạn điều tra bằng REA. Tác nhân có thể kiểm tra ứng dụng mà không cần mã nguồn, giải thích cách tính năng hoạt động, đưa ra bằng chứng và xây dựng một phiên bản cho dự án của bạn.
|
||||
REA giúp các nhà nghiên cứu bảo mật, nhà phân tích mã độc, người chơi CTF và nhà phát triển hiểu phần mềm đã biên dịch thông qua tác nhân của họ. Hãy chỉ cho tác nhân của bạn một tệp nhị phân, ứng dụng hoặc bản ghi mà bạn được phép phân tích; REA kiểm tra nó mà không cần mã nguồn, giải thích hành vi của nó và đưa ra bằng chứng đằng sau từng kết luận.
|
||||
|
||||
REA kết nối tác nhân của bạn với các công cụ kiểm tra tệp nhị phân mã máy, ứng dụng JavaScript và Electron, assembly .NET và trang web. Bạn cũng có thể dùng các công cụ đó từ terminal. Việc phân tích chạy cục bộ; kết quả bao gồm bằng chứng và giới hạn làm cơ sở cho từng kết luận.
|
||||
|
||||
@@ -56,6 +55,23 @@ Quá trình thiết lập đăng ký REA với tác nhân và cài đặt các h
|
||||
|
||||
> **[Truy cập trang web REA](https://rea.tools/vi/)** để xem cách thiết lập, hướng dẫn có hình minh họa và các nghiên cứu điển hình thực tế.
|
||||
|
||||
## Sử dụng có trách nhiệm
|
||||
|
||||
REA được xây dựng cho hoạt động phân tích hợp pháp: nghiên cứu bảo mật, phân tích mã độc và sự cố, nghiên cứu lỗ hổng và công bố có phối hợp, CTF và giáo dục, khả năng tương tác, bảo tồn phần mềm, cũng như gỡ lỗi hoặc kiểm tra phần mềm mà bạn sở hữu hoặc được phép xem xét.
|
||||
|
||||
Dự án REA không ủng hộ và sẽ không hỗ trợ những việc sau:
|
||||
|
||||
- vượt qua DRM, cơ chế kiểm tra giấy phép, hệ thống chống gian lận hoặc các biện pháp bảo vệ kỹ thuật khác;
|
||||
- phát triển công cụ gian lận trong trò chơi hoặc can thiệp vào dịch vụ trực tuyến;
|
||||
- vượt qua cơ chế xác thực, chống bot hoặc kiểm soát truy cập trên các hệ thống bạn không được phép kiểm thử;
|
||||
- sao chép phần mềm độc quyền hoặc tái tạo mã và tài nguyên được bảo hộ của phần mềm đó;
|
||||
- né tránh chính sách sử dụng hoặc biện pháp an toàn của các nhà cung cấp mô hình AI;
|
||||
- bán lại hoặc chia sẻ tài khoản AI hoặc quyền truy cập API.
|
||||
|
||||
Bạn có trách nhiệm có được các quyền cho phép cần thiết và tuân thủ luật, thỏa thuận cấp phép và điều khoản dịch vụ áp dụng. Dịch ngược có thể bị hạn chế tại khu vực pháp lý của bạn hoặc bởi giấy phép của phần mềm bạn phân tích.
|
||||
|
||||
Các yêu cầu trợ giúp cho những mục đích nêu trên sẽ bị đóng mà không phản hồi trong issue và thảo luận, và có thể dẫn đến việc bị loại khỏi cộng đồng REA.
|
||||
|
||||
## Bắt đầu nhanh
|
||||
|
||||
### Thiết lập tác nhân
|
||||
@@ -73,10 +89,11 @@ Quá trình thiết lập hỗ trợ Claude Code, Codex, Cursor, Gemini CLI, Gro
|
||||
### Hỏi tác nhân
|
||||
|
||||
```text
|
||||
Tìm hiểu cách tính năng tìm kiếm trong ứng dụng Notes hoạt động, cho tôi xem bằng chứng và xây dựng tính năng tương tự cho dự án của tôi.
|
||||
Bản build release của ứng dụng tôi bị crash khi khởi động nhưng bản build debug thì không.
|
||||
Hãy kiểm tra ./build/MyApp, tìm chỗ hai bản build khác nhau và cho tôi xem bằng chứng.
|
||||
```
|
||||
|
||||
Thay Notes bằng ứng dụng mục tiêu và chỉ rõ tính năng bạn muốn hiểu.
|
||||
Hãy chỉ cho tác nhân của bạn một mục tiêu mà bạn được phép phân tích và mô tả điều bạn muốn hiểu.
|
||||
|
||||
### Sử dụng terminal
|
||||
|
||||
@@ -259,7 +276,7 @@ Báo cáo lỗ hổng theo [SECURITY.md](SECURITY.md).
|
||||
|
||||
## Lịch sử sao
|
||||
|
||||
🎉 **90.000 sao trên GitHub — xin cảm ơn!**
|
||||
🎉 **110.000 sao trên GitHub — xin cảm ơn!**
|
||||
|
||||
Cảm ơn mọi người đã sử dụng REA, báo lỗi, đề xuất tính năng, kiểm thử bản dựng và đóng góp bản sửa lỗi.
|
||||
|
||||
@@ -273,7 +290,7 @@ Cảm ơn mọi người đã sử dụng REA, báo lỗi, đề xuất tính n
|
||||
|
||||
## Miễn trừ trách nhiệm
|
||||
|
||||
REA cung cấp công cụ cho nghiên cứu dịch ngược, phân tích và tái tạo hợp pháp. Bạn có trách nhiệm có được các quyền cho phép cần thiết và tuân thủ luật áp dụng. Dự án không ủng hộ việc sử dụng trái pháp luật hoặc không được cho phép.
|
||||
Xem [Sử dụng có trách nhiệm](#sử-dụng-có-trách-nhiệm).
|
||||
|
||||
## Đóng góp
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### 通過一個 MCP 服務,逆向分析二進位檔案、應用程式和執行階段行為。
|
||||
|
||||
**看到喜歡的功能,弄清它的工作原理,深入到二進位層面。**
|
||||
**弄清軟體的工作原理,深入到二進位層面——用於安全研究、互通性,以及你獲授權分析的軟體。**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
在應用中看到一個想加入自己產品的功能?讓 AI 代理用 REA 調查它。即使沒有原始碼,AI 代理也可以檢查應用、解釋功能的工作方式、展示證據,並為你的專案實現類似功能。
|
||||
REA 協助資安研究人員、惡意程式分析師、CTF 選手和開發者透過 AI 代理理解已編譯的軟體。把你獲授權分析的二進位檔案、應用程式或擷取資料交給 AI 代理;REA 無需原始碼即可檢查它、解釋其行為,並展示每個結論背後的證據。
|
||||
|
||||
REA 將 AI 代理連接到分析工具,用於檢查原生二進位檔案、JavaScript 和 Electron 應用、.NET 程式集以及網站。你也可以在終端中使用同樣的工具。分析在本機執行,結果包含支撐各項結論的證據和相關限制。
|
||||
|
||||
@@ -56,6 +55,23 @@ REA 將 AI 代理連接到分析工具,用於檢查原生二進位檔案、Jav
|
||||
|
||||
> **[訪問 REA 網站](https://rea.tools/zh-TW/)**,查看設定說明、圖解指南和真實案例。
|
||||
|
||||
## 負責任的使用
|
||||
|
||||
REA 專為合法分析而打造:資安研究、惡意程式與資安事件分析、漏洞研究與協調揭露、CTF 與教育、互通性、軟體保存,以及對你擁有或獲授權檢查的軟體進行除錯或稽核。
|
||||
|
||||
REA 專案不支持、也不會協助以下用途:
|
||||
|
||||
- 規避 DRM、授權檢查、反作弊系統或其他技術保護措施;
|
||||
- 開發遊戲外掛或竄改線上服務;
|
||||
- 在你未獲授權測試的系統上繞過身分驗證、機器人防護或存取控制;
|
||||
- 複製專有軟體,或重現其受保護的程式碼和資源;
|
||||
- 規避 AI 模型供應商的使用政策或安全措施;
|
||||
- 轉售或共享 AI 帳號或 API 存取權限。
|
||||
|
||||
你有責任取得所需授權,並遵守適用的法律、授權協議和服務條款。逆向工程可能受到你所在司法管轄區或所分析軟體授權條款的限制。
|
||||
|
||||
在 issue 和討論區中尋求上述用途協助的請求將被直接關閉且不予回覆,並可能導致被移出 REA 社群。
|
||||
|
||||
## 快速開始
|
||||
|
||||
### 設定 AI 代理
|
||||
@@ -73,10 +89,11 @@ npx rea-agents setup
|
||||
### 詢問 AI 代理
|
||||
|
||||
```text
|
||||
弄清 Notes 應用的搜尋功能如何工作,展示證據,並為我的專案實現類似功能。
|
||||
我的應用程式的 release 建置在啟動時當機,但 debug 建置不會。
|
||||
檢查 ./build/MyApp,找出兩個建置在哪裡出現分歧,並展示證據。
|
||||
```
|
||||
|
||||
把 Notes 替換為目標應用,並描述你想瞭解的功能。
|
||||
把你獲授權分析的目標交給 AI 代理,並描述你想瞭解的內容。
|
||||
|
||||
### 使用終端
|
||||
|
||||
@@ -261,7 +278,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## Star 歷史
|
||||
|
||||
🎉 **GitHub Star 達到 90,000 個,感謝大家!**
|
||||
🎉 **GitHub Star 達到 110,000 個,感謝大家!**
|
||||
|
||||
感謝每一位使用 REA、報告錯誤、提出功能需求、測試建置和貢獻修復的朋友。
|
||||
|
||||
@@ -275,7 +292,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## 免責聲明
|
||||
|
||||
REA 為合法的逆向工程研究、分析和重建提供工具。你有責任取得所需授權並遵守適用法律。本專案不認同非法或未經授權的使用。
|
||||
參見[負責任的使用](#負責任的使用)。
|
||||
|
||||
## 參與貢獻
|
||||
|
||||
|
||||
+24
-7
@@ -6,10 +6,9 @@
|
||||
|
||||
### 通过一个 MCP 服务,逆向分析二进制文件、应用程序和运行时行为。
|
||||
|
||||
**看到喜欢的功能,弄清它的工作原理,深入到二进制层面。**
|
||||
**弄清软件的工作原理,深入到二进制层面——用于安全研究、互操作性,以及你获授权分析的软件。**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
||||
[](docs/mcp-contracts.md#generated-catalog)
|
||||
[](#current-status)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
@@ -48,7 +47,7 @@
|
||||
|
||||
---
|
||||
|
||||
在应用中看到一个想加入自己产品的功能?让智能体用 REA 调查它。即使没有源代码,智能体也可以检查应用、解释功能的工作方式、展示证据,并为你的项目实现类似功能。
|
||||
REA 帮助安全研究人员、恶意软件分析师、CTF 选手和开发者通过智能体理解已编译的软件。把你获授权分析的二进制文件、应用程序或捕获数据交给智能体;REA 无需源代码即可检查它、解释其行为,并展示每个结论背后的证据。
|
||||
|
||||
REA 将智能体连接到分析工具,用于检查原生二进制文件、JavaScript 和 Electron 应用、.NET 程序集以及网站。你也可以在终端中使用同样的工具。分析在本机运行,结果包含支撑各项结论的证据和相关限制。
|
||||
|
||||
@@ -56,6 +55,23 @@ REA 将智能体连接到分析工具,用于检查原生二进制文件、Java
|
||||
|
||||
> **[访问 REA 网站](https://rea.tools/zh-CN/)**,查看设置说明、图解指南和真实案例。
|
||||
|
||||
## 负责任的使用
|
||||
|
||||
REA 专为合法分析而构建:安全研究、恶意软件与安全事件分析、漏洞研究与协调披露、CTF 与教育、互操作性、软件保存,以及对你拥有或获授权检查的软件进行调试或审计。
|
||||
|
||||
REA 项目不支持、也不会协助以下用途:
|
||||
|
||||
- 规避 DRM、许可证检查、反作弊系统或其他技术保护措施;
|
||||
- 开发游戏外挂或篡改在线服务;
|
||||
- 在你未获授权测试的系统上绕过身份验证、机器人防护或访问控制;
|
||||
- 复制专有软件,或复现其受保护的代码和资源;
|
||||
- 规避 AI 模型提供商的使用政策或安全措施;
|
||||
- 转售或共享 AI 账号或 API 访问权限。
|
||||
|
||||
你有责任取得所需授权,并遵守适用的法律、许可协议和服务条款。逆向工程可能受到你所在司法管辖区或所分析软件许可证的限制。
|
||||
|
||||
在 issue 和讨论区中寻求上述用途协助的请求将被直接关闭且不予回复,并可能导致被移出 REA 社区。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 设置智能体
|
||||
@@ -73,10 +89,11 @@ npx rea-agents setup
|
||||
### 询问智能体
|
||||
|
||||
```text
|
||||
弄清 Notes 应用的搜索功能如何工作,展示证据,并为我的项目实现类似功能。
|
||||
我的应用的 release 构建在启动时崩溃,但 debug 构建不会。
|
||||
检查 ./build/MyApp,找出两个构建在哪里出现分歧,并展示证据。
|
||||
```
|
||||
|
||||
把 Notes 替换为目标应用,并描述你想了解的功能。
|
||||
把你获授权分析的目标交给智能体,并描述你想了解的内容。
|
||||
|
||||
### 使用终端
|
||||
|
||||
@@ -261,7 +278,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## Star 历史
|
||||
|
||||
🎉 **GitHub Star 达到 90,000 个,感谢大家!**
|
||||
🎉 **GitHub Star 达到 110,000 个,感谢大家!**
|
||||
|
||||
感谢每一位使用 REA、报告错误、提出功能需求、测试构建和贡献修复的朋友。
|
||||
|
||||
@@ -275,7 +292,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## 免责声明
|
||||
|
||||
REA 为合法的逆向工程研究、分析和重建提供工具。你有责任取得所需授权并遵守适用法律。项目不支持非法或未经授权的使用。
|
||||
参见[负责任的使用](#负责任的使用)。
|
||||
|
||||
## 参与贡献
|
||||
|
||||
|
||||
@@ -126,6 +126,20 @@ class ReaJadxBridge {
|
||||
return result;
|
||||
}
|
||||
|
||||
private JadxSession.SmartCode methodBody(JadxSession current, ClassSnapshot cls,
|
||||
MethodSnapshot selected, JavaMethod method) {
|
||||
if (!selected.shortId().equals("<clinit>()V"))
|
||||
return current.getMethodBodySmart(method, current.getMaxSourceBytes(), true);
|
||||
// Java code generation can fold all or part of <clinit> into field declarations.
|
||||
// Its method text cannot establish coverage, even when a static block remains.
|
||||
JadxSession.MethodSmali smali = current.getMethodSmali(cls.node(), method.getName());
|
||||
if (!smali.getFound() || smali.getBlocks().size() != 1)
|
||||
throw new IllegalArgumentException("Static initializer disassembly requires one exact method: " + selected.shortId());
|
||||
return new JadxSession.SmartCode(
|
||||
TextUtilKt.truncateToBytes(smali.getBlocks().get(0), current.getMaxSourceBytes()),
|
||||
"smali", true, List.of("static_initializer_disassembly"));
|
||||
}
|
||||
|
||||
private CallToolResult handle(String name, JsonObject args) {
|
||||
if (name.equals("rea_jvm_status")) return json(Map.of(
|
||||
"engine_reported_version", BuildInfo.VERSION,
|
||||
@@ -150,7 +164,7 @@ class ReaJadxBridge {
|
||||
int index = integer(args, "overload_index", 0);
|
||||
if (index >= overloads.size()) throw new InputFailure("overload_index", "overload_index out of range: " + index);
|
||||
JavaMethod method = method(cls, overloads.get(index));
|
||||
JadxSession.SmartCode code = current.getMethodBodySmart(method, current.getMaxSourceBytes(), true);
|
||||
JadxSession.SmartCode code = methodBody(current, cls, overloads.get(index), method);
|
||||
return json(Map.of("class_name", cls.summary().get("full_name"),
|
||||
"method_name", overloads.get(index).name(), "full_name", method.getFullName(),
|
||||
"overload_index", index, "overload_count", overloads.size(),
|
||||
@@ -211,7 +225,7 @@ class ReaJadxBridge {
|
||||
String description = switch (name) {
|
||||
case "list_classes" -> "Indexed pages of all top-level class names, with offset and limit.";
|
||||
case "get_class_summary" -> "Parsed member metadata for one exact class name; no source generation.";
|
||||
case "get_method_body" -> "Source for an exact class and stable same-name overload index, with smali fallback.";
|
||||
case "get_method_body" -> "Source for an exact class and stable same-name overload index; static initializers use smali to preserve folded field initialization.";
|
||||
default -> "Complete static references to an exact class or unique method; source lines are unresolved.";
|
||||
};
|
||||
Tool narrowed = new Tool(name, new ToolSchema(null, new JsonObject(properties),
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||
import ghidra.app.util.headless.HeadlessScript;
|
||||
|
||||
/** Disable the named auto-analyzers before default auto-analysis. */
|
||||
public final class ReaGhidraAnalysisOptions extends HeadlessScript {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
String[] arguments = getScriptArgs();
|
||||
if (arguments.length != 1 || arguments[0].isBlank()) {
|
||||
throw new IllegalArgumentException(
|
||||
"ReaGhidraAnalysisOptions requires one comma-separated analyzer-name argument");
|
||||
}
|
||||
String[] names = arguments[0].split(",");
|
||||
AutoAnalysisManager analysis = AutoAnalysisManager.getAnalysisManager(currentProgram);
|
||||
Map<String, String> options = getCurrentAnalysisOptionsAndValues(currentProgram);
|
||||
for (String requestedName : names) {
|
||||
monitor.checkCancelled();
|
||||
String name = requestedName.trim();
|
||||
if (analysis.getAnalyzer(name) == null || !options.containsKey(name)) {
|
||||
throw new IllegalArgumentException("Unknown Ghidra analyzer for this program: " + name);
|
||||
}
|
||||
}
|
||||
for (String requestedName : names) {
|
||||
monitor.checkCancelled();
|
||||
setAnalysisOption(currentProgram, requestedName.trim(), "false");
|
||||
}
|
||||
options = getCurrentAnalysisOptionsAndValues(currentProgram);
|
||||
for (String requestedName : names) {
|
||||
monitor.checkCancelled();
|
||||
String name = requestedName.trim();
|
||||
if (!"false".equals(options.get(name))) {
|
||||
throw new IllegalStateException("Ghidra analyzer remained enabled: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,10 +182,17 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
||||
}
|
||||
sessionDefaultAddressSpace =
|
||||
currentProgram.getAddressFactory().getDefaultAddressSpace();
|
||||
if (currentProgram.getExecutableFormat().equals("Raw Binary") &&
|
||||
!currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
|
||||
.getBoolean("REA DOS COM prepared", false)) {
|
||||
throw new IllegalStateException("REA DOS COM entry/context preparation failed");
|
||||
if (currentProgram.getExecutableFormat().equals("Raw Binary")) {
|
||||
boolean dosCom = currentProgram.getLanguageID().getIdAsString()
|
||||
.equals("x86:LE:16:Real Mode");
|
||||
String preparation = dosCom ? "REA DOS COM prepared" : "REA raw binary prepared";
|
||||
if (!currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
|
||||
.getBoolean(preparation, false)) {
|
||||
throw new IllegalStateException(
|
||||
dosCom ? "REA DOS COM entry/context preparation failed" :
|
||||
"REA raw binary entry preparation failed"
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!Application.getApplicationVersion().equals(descriptor.providerVersion)) {
|
||||
throw new IllegalStateException("Ghidra provider version does not match the session");
|
||||
@@ -443,6 +450,11 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
||||
String seeds = currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
|
||||
.getString("REA analysis seeds", null);
|
||||
if (seeds != null) result.add("analysis_seeds", JsonParser.parseString(seeds));
|
||||
// Written by ReaGhidraMipsSoftFloat after analysis; REA requires it
|
||||
// exactly when the committed profile declares soft-float storage.
|
||||
String softFloat = currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
|
||||
.getString("REA MIPS soft-float storage", null);
|
||||
if (softFloat != null) result.add("mips_soft_float_storage", JsonParser.parseString(softFloat));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -896,6 +908,19 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
||||
result.add("procedure", function == null ? JsonNull.INSTANCE : GSON.toJsonTree(canonicalAddress(function.getEntryPoint())));
|
||||
result.addProperty("architecture", currentProgram.getLanguageID().getIdAsString());
|
||||
result.addProperty("mode", currentProgram.getLanguage().getLanguageDescription().getVariant());
|
||||
if (currentProgram.getLanguageID().getIdAsString().startsWith("MIPS:")) {
|
||||
JsonArray decoderContext = new JsonArray();
|
||||
var modeRegister = currentProgram.getRegister("ISA_MODE");
|
||||
if (modeRegister != null) {
|
||||
var modeValue = currentProgram.getProgramContext().getValue(modeRegister, address, false);
|
||||
JsonObject context = new JsonObject();
|
||||
context.addProperty("register", modeRegister.getName());
|
||||
context.add("value", modeValue == null ? JsonNull.INSTANCE : GSON.toJsonTree(modeValue.toString()));
|
||||
context.addProperty("source", "ghidra-program-context");
|
||||
decoderContext.add(context);
|
||||
}
|
||||
result.add("decoder_context", decoderContext);
|
||||
}
|
||||
JsonArray operands = new JsonArray();
|
||||
JsonArray references = new JsonArray();
|
||||
JsonArray destinations = new JsonArray();
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import org.osgi.framework.BundleActivator;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
// Compile the no-op activator with REA's scripts. Otherwise Ghidra generates
|
||||
// one afterward and opens a second Java compiler and file manager.
|
||||
public final class ReaGhidraBundleActivator implements BundleActivator {
|
||||
@Override
|
||||
public void start(BundleContext context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(BundleContext context) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.program.database.SpecExtension;
|
||||
import ghidra.program.model.data.AbstractFloatDataType;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.data.DoubleDataType;
|
||||
import ghidra.program.model.data.FloatDataType;
|
||||
import ghidra.program.model.data.FunctionDefinition;
|
||||
import ghidra.program.model.data.IntegerDataType;
|
||||
import ghidra.program.model.data.ParameterDefinition;
|
||||
import ghidra.program.model.data.TypeDef;
|
||||
import ghidra.program.model.lang.CompilerSpec;
|
||||
import ghidra.program.model.lang.PrototypeModel;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Parameter;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.listing.VariableStorage;
|
||||
import ghidra.program.model.pcode.XmlEncode;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
/**
|
||||
* Soft-float o32 passes and returns float/double values exactly where same-size integers go.
|
||||
* Ghidra's MIPS32 compiler specifications model only hard-float o32, so every typed float
|
||||
* prototype (functions, externals, thunks via their targets, and function-pointer definitions)
|
||||
* would otherwise read $f12/$f14/$f0. Derive a program-specific prototype model from the
|
||||
* program's own default o32 model with its floating-point entries and rules removed, install it
|
||||
* as a specification extension, and assign it to every callable whose formal types use floats.
|
||||
*/
|
||||
public final class ReaGhidraMipsSoftFloat extends GhidraScript {
|
||||
static final String REPORT_KEY = "REA MIPS soft-float storage";
|
||||
static final String POLICY = "o32-soft-float-prototype-v1";
|
||||
static final String PROTOTYPE = "__rea_soft_float";
|
||||
private static final Pattern FPU_REGISTER = Pattern.compile("f\\d+(_\\d+)?");
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
String processor = currentProgram.getLanguage().getProcessor().toString();
|
||||
if (!processor.equals("MIPS") || currentProgram.getDefaultPointerSize() != 4)
|
||||
throw new IllegalStateException(
|
||||
"REA soft-float storage requires a 32-bit MIPS program, not " + processor);
|
||||
CompilerSpec spec = currentProgram.getCompilerSpec();
|
||||
PrototypeModel base = spec.getDefaultCallingConvention();
|
||||
new SpecExtension(currentProgram)
|
||||
.addReplaceCompilerSpecExtension(softFloatPrototype(base, spec), monitor);
|
||||
PrototypeModel soft = currentProgram.getCompilerSpec().getCallingConvention(PROTOTYPE);
|
||||
if (soft == null)
|
||||
throw new IllegalStateException("REA soft-float prototype was not installed");
|
||||
requireIntegerStorage(
|
||||
soft.getStorageLocations(
|
||||
currentProgram,
|
||||
new DataType[] {
|
||||
FloatDataType.dataType, IntegerDataType.dataType, DoubleDataType.dataType,
|
||||
FloatDataType.dataType, DoubleDataType.dataType
|
||||
},
|
||||
false),
|
||||
"soft-float prototype self-check");
|
||||
int functions = 0;
|
||||
List<Function> callables = new ArrayList<>();
|
||||
currentProgram.getFunctionManager().getFunctions(true).forEach(callables::add);
|
||||
currentProgram.getFunctionManager().getExternalFunctions().forEach(callables::add);
|
||||
for (Function function : callables) {
|
||||
// A thunk's signature is its target's; the target is in this list.
|
||||
if (function.isThunk() || !usesFloat(function)) continue;
|
||||
if (function.hasCustomVariableStorage()) function.setCustomVariableStorage(false);
|
||||
function.setCallingConvention(PROTOTYPE);
|
||||
List<VariableStorage> storage = new ArrayList<>();
|
||||
storage.add(function.getReturn().getVariableStorage());
|
||||
for (Parameter parameter : function.getParameters()) storage.add(parameter.getVariableStorage());
|
||||
requireIntegerStorage(storage.toArray(VariableStorage[]::new), function.getName(true));
|
||||
functions += 1;
|
||||
}
|
||||
int definitions = 0;
|
||||
Iterator<DataType> types = currentProgram.getDataTypeManager().getAllDataTypes();
|
||||
while (types.hasNext()) {
|
||||
if (!(types.next() instanceof FunctionDefinition definition) || !usesFloat(definition))
|
||||
continue;
|
||||
definition.setCallingConvention(PROTOTYPE);
|
||||
definitions += 1;
|
||||
}
|
||||
// Written last: any failure above leaves no report, and REA refuses the session.
|
||||
currentProgram.getOptions(Program.PROGRAM_INFO).setString(
|
||||
REPORT_KEY,
|
||||
"{\"policy\":\"" + POLICY + "\",\"functions\":" + functions +
|
||||
",\"definitions\":" + definitions + "}");
|
||||
println("REA MIPS soft-float prototype: functions=" + functions + " definitions=" + definitions);
|
||||
}
|
||||
|
||||
/** The default o32 model without floating-point registers or float-consuming rules. */
|
||||
private static String softFloatPrototype(PrototypeModel base, CompilerSpec spec) throws Exception {
|
||||
XmlEncode encoder = new XmlEncode();
|
||||
base.encode(encoder, spec.getPcodeInjectLibrary());
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
Document document =
|
||||
factory.newDocumentBuilder().parse(new InputSource(new StringReader(encoder.toString())));
|
||||
Element root = document.getDocumentElement();
|
||||
if (!root.getTagName().equals("prototype"))
|
||||
throw new IllegalStateException("Unexpected default prototype encoding " + root.getTagName());
|
||||
root.setAttribute("name", PROTOTYPE);
|
||||
List<Node> removed = new ArrayList<>();
|
||||
collect(root.getElementsByTagName("pentry"), "metatype", "float", null, removed);
|
||||
collect(root.getElementsByTagName("consume"), "storage", "float", "rule", removed);
|
||||
if (removed.isEmpty())
|
||||
throw new IllegalStateException("Default prototype has no floating-point entries to remove");
|
||||
for (Node node : removed) node.getParentNode().removeChild(node);
|
||||
StringWriter xml = new StringWriter();
|
||||
var transformer = TransformerFactory.newInstance().newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
transformer.transform(new DOMSource(document), new StreamResult(xml));
|
||||
return xml.toString();
|
||||
}
|
||||
|
||||
/** Select matching elements, or their nearest `ancestor` element when one is named. */
|
||||
private static void collect(
|
||||
NodeList nodes, String attribute, String value, String ancestor, List<Node> selected) {
|
||||
for (int index = 0; index < nodes.getLength(); index += 1) {
|
||||
Element element = (Element) nodes.item(index);
|
||||
if (!value.equals(element.getAttribute(attribute))) continue;
|
||||
Node target = element;
|
||||
while (ancestor != null && target != null && !ancestor.equals(target.getNodeName()))
|
||||
target = target.getParentNode();
|
||||
if (target != null && !selected.contains(target)) selected.add(target);
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireIntegerStorage(VariableStorage[] storage, String owner) {
|
||||
for (VariableStorage item : storage) {
|
||||
if (item.isBadStorage() || item.isUnassignedStorage())
|
||||
throw new IllegalStateException("REA soft-float storage unresolved for " + owner);
|
||||
List<Register> registers = item.getRegisters();
|
||||
if (registers == null) continue; // stack-only storage
|
||||
for (Register register : registers) {
|
||||
if (FPU_REGISTER.matcher(register.getName()).matches())
|
||||
throw new IllegalStateException(
|
||||
"REA soft-float storage still uses " + register.getName() + " for " + owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean usesFloat(Function function) {
|
||||
if (isFloat(function.getReturn().getFormalDataType())) return true;
|
||||
for (Parameter parameter : function.getParameters()) {
|
||||
if (isFloat(parameter.getFormalDataType())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean usesFloat(FunctionDefinition definition) {
|
||||
if (isFloat(definition.getReturnType())) return true;
|
||||
for (ParameterDefinition argument : definition.getArguments()) {
|
||||
if (isFloat(argument.getDataType())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isFloat(DataType type) {
|
||||
DataType current = type;
|
||||
while (current instanceof TypeDef typeDef) current = typeDef.getBaseDataType();
|
||||
return current instanceof AbstractFloatDataType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
/** Seed the caller-supplied raw-binary entry before default auto-analysis. */
|
||||
public final class ReaGhidraPrepareRaw extends GhidraScript {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
String[] arguments = getScriptArgs();
|
||||
if (arguments.length != 2) {
|
||||
throw new IllegalArgumentException(
|
||||
"Raw binary preparation requires the committed language and entry address"
|
||||
);
|
||||
}
|
||||
String expectedLanguage = arguments[0];
|
||||
String entryHex = arguments[1];
|
||||
if (!currentProgram.getLanguageID().getIdAsString().equals(expectedLanguage) ||
|
||||
!currentProgram.getExecutableFormat().equals("Raw Binary")) {
|
||||
throw new IllegalArgumentException(
|
||||
"Raw binary preparation requires the committed language and BinaryLoader import"
|
||||
);
|
||||
}
|
||||
Address entry = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(entryHex);
|
||||
Address start = currentProgram.getMinAddress();
|
||||
if (entry == null || !entry.equals(start)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Raw binary import must map file offset 0 at the caller-supplied entry"
|
||||
);
|
||||
}
|
||||
addEntryPoint(entry);
|
||||
if (!disassemble(entry) || createFunction(entry, "entry") == null) {
|
||||
throw new IllegalStateException("Raw binary entry cannot be decoded as a function");
|
||||
}
|
||||
currentProgram.getOptions(Program.PROGRAM_INFO).setBoolean("REA raw binary prepared", true);
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,11 @@ enum RunTokenReadError: Error {
|
||||
case ambiguousEnvironmentBoundary
|
||||
}
|
||||
|
||||
struct ParsedRunToken {
|
||||
let runId: String?
|
||||
let environmentEntryCount: Int
|
||||
}
|
||||
|
||||
@_silgen_name("csops")
|
||||
private func csops(
|
||||
_ pid: Int32,
|
||||
@@ -122,19 +127,67 @@ private func csops(
|
||||
|
||||
private let csOpsStatus: UInt32 = 0
|
||||
private let csPlatformBinary: UInt32 = 0x0400_0000
|
||||
private let csRestrict: UInt32 = 0x0000_0800
|
||||
|
||||
/**
|
||||
* Whether the target image is an Apple platform binary. Current macOS omits
|
||||
* the environment of platform binaries from KERN_PROCARGS2 for every caller,
|
||||
* so this distinguishes a policy-withheld environment from a process whose
|
||||
* environment is genuinely absent or transiently unreadable.
|
||||
*/
|
||||
func isPlatformBinary(pid: Int32) -> Bool {
|
||||
/** Read the target's current code-signing flags when csops permits inspection. */
|
||||
private func codeSigningFlags(pid: Int32) -> UInt32? {
|
||||
var flags: UInt32 = 0
|
||||
let result = withUnsafeMutablePointer(to: &flags) { pointer in
|
||||
csops(pid, csOpsStatus, UnsafeMutableRawPointer(pointer), MemoryLayout<UInt32>.size)
|
||||
}
|
||||
return result == 0 && (flags & csPlatformBinary) != 0
|
||||
return result == 0 ? flags : nil
|
||||
}
|
||||
|
||||
/** Derive one process-token observation from parser facts and native flags. */
|
||||
func processRunTokenObservation(
|
||||
pid: Int32,
|
||||
parse: Result<ParsedRunToken, RunTokenReadError>,
|
||||
codeSigningFlags: () -> UInt32?
|
||||
) -> RunTokenObservation {
|
||||
switch parse {
|
||||
case .success(let parsed):
|
||||
if let runId = parsed.runId {
|
||||
return RunTokenObservation(pid: pid, state: "readable", run_id: runId, reason: nil)
|
||||
}
|
||||
// A non-empty environment without REA's token proves this candidate is
|
||||
// unrelated. CS_RESTRICT cannot turn that readable fact into uncertainty.
|
||||
if parsed.environmentEntryCount > 0 {
|
||||
return RunTokenObservation(pid: pid, state: "readable", run_id: nil, reason: nil)
|
||||
}
|
||||
let flags = codeSigningFlags()
|
||||
if let flags, (flags & csPlatformBinary) != 0 {
|
||||
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
|
||||
}
|
||||
if let flags, (flags & csRestrict) != 0 {
|
||||
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
|
||||
}
|
||||
return RunTokenObservation(pid: pid, state: "readable", run_id: nil, reason: nil)
|
||||
case .failure(let failure):
|
||||
switch failure {
|
||||
case .environmentUnavailable:
|
||||
let flags = codeSigningFlags()
|
||||
if let flags, (flags & csPlatformBinary) != 0 {
|
||||
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
|
||||
}
|
||||
if let flags, (flags & csRestrict) != 0 {
|
||||
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
|
||||
}
|
||||
return unavailable(pid: pid, reason: "environment_unavailable")
|
||||
case .ambiguousEnvironmentBoundary:
|
||||
if let flags = codeSigningFlags(), (flags & csRestrict) != 0 {
|
||||
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
|
||||
}
|
||||
return unavailable(pid: pid, reason: "ambiguous_environment_boundary")
|
||||
case .duplicateToken:
|
||||
return unavailable(pid: pid, reason: "duplicate_run_token")
|
||||
case .invalidTokenEncoding:
|
||||
return unavailable(pid: pid, reason: "invalid_run_token_encoding")
|
||||
case .appleVectorUnavailable:
|
||||
return unavailable(pid: pid, reason: "apple_vector_unavailable")
|
||||
case .invalidBuffer:
|
||||
return unavailable(pid: pid, reason: "malformed_procargs")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func readRunToken(pid: Int32, pointerSize: Int) -> RunTokenObservation {
|
||||
@@ -151,33 +204,19 @@ private func readRunToken(pid: Int32, pointerSize: Int) -> RunTokenObservation {
|
||||
return unavailable(pid: pid, reason: "sysctl_failed_\(errno)")
|
||||
}
|
||||
bytes = Array(bytes.prefix(size))
|
||||
let parse: Result<ParsedRunToken, RunTokenReadError>
|
||||
do {
|
||||
let runId = try token(in: bytes, pointerSize: pointerSize)
|
||||
// A complete empty environment is readable for an ordinary image. Apple
|
||||
// platform images can expose the same layout after the kernel withholds
|
||||
// their environment, so they remain explicitly unverified without a token.
|
||||
if runId == nil && isPlatformBinary(pid: pid) {
|
||||
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
|
||||
}
|
||||
return RunTokenObservation(pid: pid, state: "readable", run_id: runId, reason: nil)
|
||||
} catch RunTokenReadError.duplicateToken {
|
||||
return unavailable(pid: pid, reason: "duplicate_run_token")
|
||||
} catch RunTokenReadError.invalidTokenEncoding {
|
||||
return unavailable(pid: pid, reason: "invalid_run_token_encoding")
|
||||
} catch RunTokenReadError.environmentUnavailable {
|
||||
return unavailable(
|
||||
pid: pid,
|
||||
reason: isPlatformBinary(pid: pid)
|
||||
? "platform_binary_environment_withheld"
|
||||
: "environment_unavailable"
|
||||
)
|
||||
} catch RunTokenReadError.appleVectorUnavailable {
|
||||
return unavailable(pid: pid, reason: "apple_vector_unavailable")
|
||||
} catch RunTokenReadError.ambiguousEnvironmentBoundary {
|
||||
return unavailable(pid: pid, reason: "ambiguous_environment_boundary")
|
||||
parse = .success(try parseProcessEnvironment(in: bytes, pointerSize: pointerSize))
|
||||
} catch let failure as RunTokenReadError {
|
||||
parse = .failure(failure)
|
||||
} catch {
|
||||
return unavailable(pid: pid, reason: "malformed_procargs")
|
||||
parse = .failure(.invalidBuffer)
|
||||
}
|
||||
return processRunTokenObservation(
|
||||
pid: pid,
|
||||
parse: parse,
|
||||
codeSigningFlags: { codeSigningFlags(pid: pid) }
|
||||
)
|
||||
}
|
||||
|
||||
func readRunTokens(pids: [Int32]) -> [RunTokenObservation] {
|
||||
@@ -201,7 +240,10 @@ func readRunTokens(pids: [Int32]) -> [RunTokenObservation] {
|
||||
}
|
||||
}
|
||||
|
||||
func token(in bytes: [UInt8], pointerSize: Int = MemoryLayout<UnsafeRawPointer>.size) throws -> String? {
|
||||
func parseProcessEnvironment(
|
||||
in bytes: [UInt8],
|
||||
pointerSize: Int = MemoryLayout<UnsafeRawPointer>.size
|
||||
) throws -> ParsedRunToken {
|
||||
guard bytes.count >= MemoryLayout<Int32>.size else { throw RunTokenReadError.invalidBuffer }
|
||||
let argc = bytes.withUnsafeBytes { $0.loadUnaligned(as: Int32.self).littleEndian }
|
||||
guard argc >= 0, Int(argc) <= bytes.count else { throw RunTokenReadError.invalidBuffer }
|
||||
@@ -277,7 +319,7 @@ func token(in bytes: [UInt8], pointerSize: Int = MemoryLayout<UnsafeRawPointer>.
|
||||
found = decoded
|
||||
}
|
||||
}
|
||||
return found
|
||||
return ParsedRunToken(runId: found, environmentEntryCount: environmentEntries.count)
|
||||
}
|
||||
|
||||
private func readString(
|
||||
|
||||
@@ -206,6 +206,11 @@ def inspect_elf(path, cache):
|
||||
"visibility": raw.st_other.visibility, "section_index": raw.st_shndx,
|
||||
})
|
||||
if h.sh_type in ("SHT_REL", "SHT_RELA"):
|
||||
if h.sh_size % h.sh_entsize != 0:
|
||||
raise LayoutFailure(
|
||||
"format",
|
||||
f"ELF relocation section {index} size is not a multiple of its {h.sh_entsize}-byte entries.",
|
||||
)
|
||||
if image.header.e_type == "ET_REL" and h.sh_info >= len(all_sections):
|
||||
raise LayoutFailure("format", f"ELF relocation section {index} references missing target section {h.sh_info}.")
|
||||
symbol_table = None
|
||||
|
||||
@@ -26,6 +26,7 @@ export default defineConfig({
|
||||
text: "Investigation guides",
|
||||
items: [
|
||||
{ text: "Native binaries", link: "/native-investigation" },
|
||||
{ text: "Go build metadata", link: "/go-binaries" },
|
||||
{
|
||||
text: "JavaScript and Electron",
|
||||
link: "/javascript-artifact-reconstruction",
|
||||
@@ -54,6 +55,7 @@ export default defineConfig({
|
||||
{ text: "IDA", link: "/ida-provider" },
|
||||
{ text: "Ghidra on Windows", link: "/windows-ghidra-p0" },
|
||||
{ text: "Ghidra DOS analysis", link: "/ghidra-dos" },
|
||||
{ text: "Ghidra raw binary", link: "/raw-binary" },
|
||||
{ text: "Ghidra NativeAOT", link: "/ghidra-nativeaot" },
|
||||
{ text: "Ghidra semantics", link: "/ghidra-provider" },
|
||||
],
|
||||
|
||||
@@ -88,7 +88,7 @@ existing analysis families directly, for example
|
||||
| `list_adb_packages` | read | `pm list packages -f` with scope; base APK device paths |
|
||||
| `resolve_adb_packages` | read | Case-insensitive package-name substring search with exact-match flag |
|
||||
| `inspect_adb_package` | read | `dumpsys package` projection: versions, install times, installer, flags, permission count |
|
||||
| `list_adb_directory` | read | `ls -la`: kinds, permissions, owner, group, sizes, symlink targets |
|
||||
| `list_adb_directory` | read | NUL-framed immediate children, including hidden entries: kinds, permissions, owner, group, sizes, symlink targets |
|
||||
| `pull_adb_package` | read | Complete split APK set from `pm path`, each file digested |
|
||||
| `pull_adb_file` | read | One absolute device path pulled and digested |
|
||||
| `capture_adb_screen` | read | One `screencap -p` frame (exec-out, no device-side file) with dimensions |
|
||||
@@ -102,13 +102,22 @@ existing analysis families directly, for example
|
||||
|
||||
## Interpret the result
|
||||
|
||||
- **Directory names retain whitespace and newlines.** Symlink targets do too,
|
||||
including trailing newlines. Records containing invalid UTF-8 remain as
|
||||
labelled hex in `unparsed_lines` and make directory coverage partial.
|
||||
- **Inferences carry their basis.** Device `kind` reports
|
||||
`kind_basis` (serial prefix or USB transport); APK `role` reports
|
||||
`role_basis: file_name`; `emulator_observed` is the build's own
|
||||
`ro.kernel.qemu` value, `null` when absent.
|
||||
- **Device paths are not host paths.** `base_apk_device_path` values live on
|
||||
the device filesystem. Only pulls and captures produce host-local paths,
|
||||
each paired with byte size and SHA-256 digest.
|
||||
each paired with byte size and SHA-256 digest. A pulled file's host name is
|
||||
the device path's final POSIX segment. REA preserves the original device
|
||||
path and filename; a missing final segment or `.`/`..` is refused before
|
||||
output creation. Windows hosts follow [Windows filename rules](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file),
|
||||
while POSIX hosts retain valid POSIX filenames such as names with spaces or
|
||||
colons. A package pull checks reported names against the destination
|
||||
filesystem before pulling, and refuses names that would share a local path.
|
||||
- **Splits are part of the answer.** `pm path` enumerates the base APK and
|
||||
every installed split; pulls cover the whole set. System packages whose
|
||||
APKs keep other names report the `unknown` role rather than guessing.
|
||||
|
||||
@@ -94,6 +94,12 @@ fallback for an overloaded method instead of attributing all bodies to one
|
||||
selected overload. Native/abstract methods with no decompiled body report
|
||||
`body_status: "not_available"` without inferring which implementation flag caused it.
|
||||
|
||||
Static initializers (`<clinit>`) return smali with the
|
||||
`static_initializer_disassembly` marker. Java decompilation can move their
|
||||
instructions into field declarations, leaving an empty or incomplete method
|
||||
body. The selected method's disassembly preserves those instructions and retains
|
||||
the same source byte limit and explicit truncation reporting.
|
||||
|
||||
## Interpret the result
|
||||
|
||||
- Package inspection retains decoded manifest XML, summary declarations,
|
||||
@@ -101,7 +107,11 @@ selected overload. Native/abstract methods with no decompiled body report
|
||||
upstream engine and should be checked against the retained XML when needed.
|
||||
Signature verification is explicitly `not_performed`.
|
||||
- Class search consumes all pages and checks their counts. An empty `query`
|
||||
inventories all class names; class names are decompiler representations.
|
||||
inventories all class-name rows; class names are decompiler representations.
|
||||
Repeated names remain repeated in `matches` so the inventory row count and
|
||||
producer evidence stay visible. `duplicate_names` identifies matching names
|
||||
with multiple rows and their occurrence counts; a name alone does not
|
||||
distinguish which provider class node a later lookup selects.
|
||||
- Class inventories read parsed metadata without generating source and return
|
||||
full reported display types, fields and inner class names. Synthetic members
|
||||
that code generation hides can appear in the inventory; it stays consistent
|
||||
|
||||
+42
-1
@@ -37,8 +37,44 @@ module page. See
|
||||
[JavaScript application workflows](javascript-application-workflows.md) for
|
||||
views, feature traces and comparisons that take the same saved Evidence.
|
||||
|
||||
## Managed assemblies (.NET)
|
||||
|
||||
For a .NET PE/CLI assembly, inspect its identity and deployment form before
|
||||
inspecting members. These commands read metadata and Common Intermediate
|
||||
Language (CIL) without loading or executing the assembly and require no native
|
||||
analysis engine:
|
||||
|
||||
```bash
|
||||
rea inspect-managed-artifact /absolute/path/Assembly.dll --json
|
||||
rea inspect-managed-members /absolute/path/Assembly.dll --json
|
||||
rea inspect-managed-members /absolute/path/Assembly.dll --type Example.Parser --json
|
||||
```
|
||||
|
||||
Replace `Example.Parser` with an exact `full_name` from the member inspection,
|
||||
or a declaring type name you already know. Omit `--type` to inspect all members,
|
||||
or repeat it to select several types.
|
||||
Selection limits decoding to the selected types; member references remain an
|
||||
assembly-wide inventory, so the output can still be large.
|
||||
|
||||
Choose the workflow from the artifact's contents rather than its `.exe` or
|
||||
`.dll` extension. The managed parser requires PE/CLI metadata; it does not
|
||||
unpack .NET single-file hosts or decode IL2CPP metadata. NativeAOT method bodies
|
||||
require native analysis. See the [managed-code guide](managed-code-analysis.md)
|
||||
for supported deployment forms, selection semantics, and coverage limits.
|
||||
|
||||
## Native analysis
|
||||
|
||||
Inspect embedded Go build metadata without installing Go or configuring a
|
||||
disassembler:
|
||||
|
||||
```bash
|
||||
rea inspect-go-binary /absolute/path/to/program --json
|
||||
```
|
||||
|
||||
The shared CLI/MCP workflow reports compiler version, modules, replacements,
|
||||
build settings, original bytes and SHA-256. See [Go binaries](go-binaries.md)
|
||||
for supported containers and absent or malformed metadata.
|
||||
|
||||
Configure [Hopper or Ghidra](installation.md#hopper), or the
|
||||
[IDA adapter](ida-provider.md), before analyzing a native target. Substitute
|
||||
your target, search text and function name or address in these examples:
|
||||
@@ -59,7 +95,8 @@ alone, use `rea instructions`. A macOS `.app` bundle can be supplied directly.
|
||||
Run `rea --help` or a command's `--help` for arguments and output options.
|
||||
The [generated catalog](product-catalog.json) lists all CLI commands and MCP
|
||||
tools. Provider-specific target support is described in the
|
||||
[native guide](native-investigation.md), [DOS guide](ghidra-dos.md), and
|
||||
[native guide](native-investigation.md), [DOS guide](ghidra-dos.md),
|
||||
[raw binary guide](raw-binary.md), and
|
||||
[Windows Ghidra guide](windows-ghidra-p0.md).
|
||||
|
||||
## Choose a provider
|
||||
@@ -113,6 +150,10 @@ an exact result when the target bytes, operation, parameters, provider and
|
||||
settings match. Mutations and cursor-dependent calls are excluded from the
|
||||
cache. Snapshot files are local and use owner-only permissions.
|
||||
|
||||
Dispatching a metadata write invalidates the active session's snapshot cache,
|
||||
even if its reply fails: the provider may already have applied the write.
|
||||
Close and reopen the target before importing or saving another snapshot.
|
||||
|
||||
Snapshots retain eligible target-scoped question histories in full, including
|
||||
mutation Evidence recorded while another target was active. Observations and
|
||||
related questions must still belong to the saved target. If those dependencies
|
||||
|
||||
+159
-15
@@ -13,13 +13,14 @@ retains class, byte order, `e_type`, raw `e_flags`, and inspected ABI declaratio
|
||||
Recognition does not mean every provider or MIPS variant is supported. Existing
|
||||
PE, managed and Mach-O architecture sets are unchanged.
|
||||
|
||||
The first Ghidra lane admits ELF32 `ET_EXEC`, little or big endian, with explicit
|
||||
o32 and `EF_MIPS_ARCH_32R2` flags and no machine-specific or ASE encoding flags.
|
||||
These are header declarations, not proof that every instruction conforms to
|
||||
that ISA. ELF64, n32/n64, unspecified ABI, MIPS16/microMIPS, machine-specific
|
||||
variants, shared/relocatable objects and PSP PRX are not covered by this lane.
|
||||
Their family may be identified, but provider selection returns the specific
|
||||
unsupported constraint rather than guessing a compatible profile.
|
||||
The Ghidra lane admits ELF32 `ET_EXEC` and `ET_REL`, little or big endian, with
|
||||
explicit o32 and `EF_MIPS_ARCH_32R2` flags and no machine-specific or ASE
|
||||
encoding flags. These are header declarations, not proof that every instruction
|
||||
conforms to that ISA. ELF64, n32/n64, unspecified ABI, microMIPS,
|
||||
machine-specific variants and PSP PRX are not covered by this standard-instruction
|
||||
lane. MIPS16 shared images have the separate bounded profile below; other shared
|
||||
or compressed images remain unsupported. Their family may be identified, but provider selection returns the
|
||||
specific unsupported constraint rather than guessing a compatible profile.
|
||||
|
||||
Explicit `EF_MIPS_FP64` and `EF_MIPS_NAN2008` declarations are refused. Their
|
||||
absence is insufficient: Clang can emit identical `e_flags` for FP32, FP64 and
|
||||
@@ -39,20 +40,124 @@ absent or supply a default ABI.
|
||||
The field layout follows
|
||||
[`Elf_MIPS_ABIFlags_v0`](https://github.com/bminor/glibc/blob/master/elf/elf.h).
|
||||
|
||||
Provider admission requires record version 0, MIPS32r2, 32-bit GPR/CPR1, no CPR2,
|
||||
and a double-precision or FPXX ABI declaration with no ISA extension, ASE or
|
||||
unknown general flags. Missing records remain unknown rather than defaulting
|
||||
to a compatible ABI, and this initial lane refuses them. Unknown record values
|
||||
are retained for diagnosis but do not gain provider support. These checks bind
|
||||
the declared interpretation; the integer-only fixture does not establish full
|
||||
Provider admission requires record version 0, MIPS32r2, 32-bit GPRs, no CPR2,
|
||||
no ISA extension, ASE or unknown general flags, and one of two floating-point
|
||||
declarations: 32-bit CPR1 with a double-precision or FPXX ABI, or no CPR1 with
|
||||
the soft-float ABI. Other combinations, including single-float, FP64 and
|
||||
unspecified ABIs, are refused. Missing records remain unknown rather than
|
||||
defaulting to a compatible ABI, and this lane refuses them. Unknown record
|
||||
values are retained for diagnosis but do not gain provider support. These checks
|
||||
bind the declared interpretation; the fixtures do not establish full
|
||||
floating-point behavioral conformance.
|
||||
|
||||
### MIPS16 shared images
|
||||
|
||||
A separate profile admits ELF32 `ET_DYN`, either byte order, declaring explicit
|
||||
o32, MIPS32r2 and exactly the MIPS16 ASE. Machine variants, n32, FP64 and NAN2008
|
||||
remain refused. Source inspection must examine the complete dynamic relocation
|
||||
declarations through uniquely mapped file-backed `PT_LOAD` ranges. This profile
|
||||
verifies `R_MIPS_NONE` and symbol-index-zero `R_MIPS_REL32`; section declarations
|
||||
must describe that same dynamic table. Unavailable section headers and additional
|
||||
section relocation tables (including `--emit-relocs` output) remain unsupported.
|
||||
Symbol-bearing REL32,
|
||||
other relocation types, RELA and PLT declarations remain unsupported. A real
|
||||
negative control shows that Ghidra can normalize a symbol-bearing MIPS16 function
|
||||
pointer to an even address, losing the source mode tag. REA refuses that shape
|
||||
rather than rewriting the source or silently repairing the analysis result.
|
||||
|
||||
The profile pins the endian-matched `MIPS:<LE|BE>:32:default` processor and default
|
||||
compiler specification, and checks the bridge-reported language after import.
|
||||
The generic processor preserves mixed MIPS32/MIPS16 code through per-address
|
||||
`ISA_MODE` context; a global `16e` processor would impose one mode across the
|
||||
image. A caller-selected incompatible processor or compiler specification is
|
||||
explicitly refused without replacing the requested choice.
|
||||
|
||||
An observed ABI flags record must agree with the header and MIPS16 ASE, and
|
||||
must declare the same verified floating-point combinations as the standard
|
||||
lane. With no observed record, every ABI declaration table must have been
|
||||
completely examined. The header still declares o32, but floating-point ABI
|
||||
semantics remain unknown and are stated in Evidence; selecting the default
|
||||
compiler specification does not establish floating-point storage. Partial
|
||||
section tables do not prove record absence. The existing soft-float prototype
|
||||
policy applies when the source explicitly declares soft-float.
|
||||
|
||||
Returned addresses are analysis coordinates, not runtime load addresses.
|
||||
`inspect_native_load_image` preserves original relocation bytes, current loaded
|
||||
memory bytes and provider status. External GOT bindings remain unverified
|
||||
without their defining images. The provider's `modified_sha256` describes its
|
||||
ephemeral relocated image and can differ from `original_sha256`; it does not
|
||||
mean that the caller's artifact was written. `inspect_native_instruction` retains the program
|
||||
language variant in `mode` and reports actual per-address `ISA_MODE` separately
|
||||
in `decoder_context`, including null when unavailable. Artifact bytes and digests
|
||||
remain unchanged; this workflow does not execute the shared library.
|
||||
|
||||
The optional real-provider check builds eight source-owned GNU controls, covering
|
||||
both byte orders, mixed MIPS32/MIPS16 code, declared and absent ABI records, and
|
||||
the rejected symbol-bearing and additional section relocations. It compares source instruction bytes,
|
||||
function symbols and the local function-pointer tag with actual CLI/MCP results,
|
||||
checks context through a snapshot round trip, and verifies process cleanup.
|
||||
It requires caller-supplied GNU MIPS cross-binutils and Ghidra; no fixture target
|
||||
is executed or patched after linking. Floating-point behavior of compressed
|
||||
functions is not established by these integer controls.
|
||||
|
||||
```bash
|
||||
REA_MIPS_AS=/absolute/path/to/mips-linux-gnu-as \
|
||||
REA_MIPS_LD=/absolute/path/to/mips-linux-gnu-ld \
|
||||
GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra \
|
||||
npm run verify:ghidra:mips-shared
|
||||
```
|
||||
|
||||
MIPS header and ABI record facts are committed in the analysis profile so
|
||||
interpretation changes invalidate profile-bound snapshots. The support-lane
|
||||
identity is versioned independently of file identity. Evidence, lifecycle
|
||||
outputs and saved snapshots preserve the `mips` family. Hopper and IDA adapters
|
||||
do not implicitly gain MIPS support. Windows Ghidra P0 remains PE x86/x86-64 only.
|
||||
|
||||
### Soft-float prototype
|
||||
|
||||
Ghidra's MIPS32 compiler specifications model hard-float o32 only: a typed
|
||||
`float` or `double` parameter is bound to `$f12`/`$f14` and returned in `$f0`.
|
||||
Soft-float o32 passes those values in integer registers and stack slots, so a
|
||||
typed prototype (for example from DWARF) would make the decompiler read FPU
|
||||
registers the code never writes and drop the real dataflow. That applies to
|
||||
functions, external functions and indirect calls through typed function
|
||||
pointers alike.
|
||||
|
||||
For soft-float targets REA commits
|
||||
`mips_soft_float_storage: o32-soft-float-prototype-v1` in the analysis profile
|
||||
and runs `ReaGhidraMipsSoftFloat.java` after auto-analysis. The script encodes
|
||||
the program's own default o32 prototype, removes its floating-point parameter
|
||||
and return entries and the rules that consume floating-point storage, and
|
||||
installs the result as the program-specific `__rea_soft_float` prototype through
|
||||
a Ghidra specification extension. Deriving it keeps the byte-order-specific
|
||||
aggregate rules and the hidden struct-return pointer. The script then assigns
|
||||
that prototype, with dynamic storage, to every non-thunk function and external
|
||||
function, and to every function-pointer definition, whose declared types use
|
||||
`float` or `double`. Thunks follow their targets. Types are kept.
|
||||
|
||||
The script fails, and writes no report, if the derived prototype still places a
|
||||
float in an FPU register or any reassigned function does. It records the
|
||||
function and definition counts last; REA requires that report exactly when the
|
||||
profile commits the policy and refuses the session otherwise, before serving any
|
||||
operation. Session Evidence carries the policy and counts as limitations.
|
||||
Callables without typed float signatures keep Ghidra's default prototype, under
|
||||
which Ghidra already infers integer-register parameters. Hard-float profiles do
|
||||
not gain the policy, so their digests are unchanged.
|
||||
|
||||
### Relocatable objects
|
||||
|
||||
`ET_REL` objects, such as Linux kernel modules (`.ko`), are imported by Ghidra's
|
||||
ELF loader, which assigns each allocated section an analysis address and applies
|
||||
the object's REL relocations there. Undefined symbols are placed in Ghidra's
|
||||
`EXTERNAL` block: a thunk for code, a label for data. These addresses are
|
||||
analysis coordinates, not the addresses a kernel or linker would choose, and
|
||||
session Evidence says so. `inspect_native_load_image` reports each relocation
|
||||
with Ghidra's status; HI16 entries report `PARTIAL` because Ghidra applies them
|
||||
together with their paired LO16. Relocatable targets commit
|
||||
`mips_support_lane: elf32-rel-o32-arch32r2-standard-v1`, separate from the
|
||||
executable lane, whose identity executable targets keep. Kernel modules are
|
||||
usually soft-float and therefore also use the soft-float storage policy.
|
||||
|
||||
## Use
|
||||
|
||||
With a caller-installed Ghidra and compatible JDK on a supported Linux/macOS host:
|
||||
@@ -70,7 +175,10 @@ REA's ELF parser.
|
||||
|
||||
## Verification
|
||||
|
||||
The source-owned freestanding fixture is `tests/conformance/c/mips.c`. The
|
||||
### Executables
|
||||
|
||||
The source-owned freestanding fixture is `tests/conformance/c/mips.c`; the
|
||||
soft-float variant also links `tests/conformance/c/mips-soft-float.c`. The
|
||||
optional cross-target lane requires a caller-supplied Clang with MIPS targets,
|
||||
LLD, GNU `readelf`, Ghidra and its compatible JDK; ordinary host-native checks
|
||||
do not acquire these tools. `REA_MIPS_CLANG` selects Clang and
|
||||
@@ -81,7 +189,8 @@ These are test-time prerequisites, not REA runtime providers.
|
||||
GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra npm run verify:ghidra:mips
|
||||
```
|
||||
|
||||
The lane compiles both byte orders and never executes the targets. GNU readelf
|
||||
The lane compiles both byte orders with hard-float and soft-float ABIs and never
|
||||
executes the targets. GNU readelf
|
||||
independently supplies header/ABI facts and symbol addresses, compared with the
|
||||
production REA target resolver before provider startup. Its version, bounded
|
||||
command output and selected tool path are retained in the verification report.
|
||||
@@ -104,9 +213,44 @@ all program-header payloads. It checks the actual Ghidra language, entry-address
|
||||
decompilation, distinct artifact digest and missing-section limitations through
|
||||
CLI Evidence and an MCP-produced snapshot. Caller-selected Ghidra heap limits
|
||||
are passed to these child workflows.
|
||||
For soft-float fixtures the lane also checks, through `inspect_native_api`, the
|
||||
exact parameter and return types and storage of float, double, mixed (pair
|
||||
alignment and stack spill) and struct-returning functions; that a caller's
|
||||
decompilation receives every source float constant without FPU inputs,
|
||||
including an indirect call through a typed function pointer; and that the
|
||||
snapshot profile and Evidence retain the policy and outcome counts.
|
||||
Pseudocode presence is a liveness check, not proof of semantic equivalence.
|
||||
Instruction decoding and reader agreement likewise do not establish runtime,
|
||||
delay-slot execution or complete floating-point semantics. Assertion regressions
|
||||
reject incorrect byte order, constants, destinations and loaded identities.
|
||||
Parser/profile tests are narrower checks, not substitutes for this real-provider
|
||||
run. Report the actual lanes run and leave unexecuted verification pending.
|
||||
|
||||
### Relocatable objects
|
||||
|
||||
This lane has its own command and fixture:
|
||||
|
||||
```bash
|
||||
GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra npm run verify:ghidra:mips-relocatable
|
||||
```
|
||||
|
||||
It compiles `tests/conformance/c/mips-relocatable.c` with `-c -G0` in both byte
|
||||
orders, plus a little-endian soft-float `-mlong-calls` variant matching Linux
|
||||
module options. GNU readelf independently supplies sections, symbols and REL
|
||||
entries. For every relocation in an allocated section, the lane reads the
|
||||
loaded word through `read_bytes` and requires it to encode `S + A`: `S` from
|
||||
Ghidra's own section placement (`list_segments`) or `EXTERNAL` placement, `A`
|
||||
from the original file bytes. HI16/LO16 are checked as the effective address
|
||||
each LO16 forms with each HI16 it resolves, including several HI16s before one
|
||||
LO16, so a missing signed-low carry fails and a HI16 without a following LO16
|
||||
fails. The lane also requires matching Ghidra relocation entries and statuses,
|
||||
section file offsets and sizes, coverage of a shared HI16, a LO16 resolving
|
||||
several pending HI16s (from an assembly block, since Clang does not emit that
|
||||
from C), a LO16 carry, external code and data and `R_MIPS_32` words, direct
|
||||
calls from the entry
|
||||
function, the relocatable support lane in the snapshot profile, the
|
||||
relocatable limitation in Evidence and an unchanged source object. The fixture
|
||||
also calls `powf`, an external whose float prototype comes from Ghidra's C
|
||||
library archive; every variant must decompile `powf(1.5,2.5)` without FPU
|
||||
inputs, and the soft-float variant must report that the soft-float prototype
|
||||
covered it.
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# Go binary build metadata
|
||||
|
||||
`inspect_go_binary` / `inspect-go-binary` answers which Go compiler version,
|
||||
modules, replacements and build settings are embedded in one selected local
|
||||
executable or library. It reads the file without running it and requires no
|
||||
Go installation, active binary target, Hopper, Ghidra or IDA.
|
||||
|
||||
```bash
|
||||
rea inspect-go-binary /absolute/path/to/application --json
|
||||
```
|
||||
|
||||
The CLI resolves relative paths against the caller's working directory. MCP
|
||||
requires an absolute filesystem path and preserves that exact selection:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "inspect_go_binary",
|
||||
"arguments": { "path": "/artifacts/application" }
|
||||
}
|
||||
```
|
||||
|
||||
## Observations
|
||||
|
||||
The inline Evidence result contains the artifact path, SHA-256, byte length,
|
||||
container format, architecture, word size and byte order. A recognized
|
||||
`build_info` record contains:
|
||||
|
||||
- `go_version`, preserved as embedded UTF-8 text rather than forced into a
|
||||
semantic version, or `null` when the embedded bytes are not UTF-8;
|
||||
- `go_version_bytes_base64`, preserving the exact compiler-string bytes;
|
||||
- exact file offsets and byte lengths for the header, compiler string and module
|
||||
string;
|
||||
- `module_text`, the decoded module-information body, or `null` when the complete
|
||||
body cannot be represented as UTF-8;
|
||||
- `module_bytes_base64`, the original module-string bytes including Go framing;
|
||||
- `module.path`, the embedded package path;
|
||||
- `module.main`, the main module's path, version, checksum and optional
|
||||
replacement;
|
||||
- `module.dependencies`, with the same fields and their original order;
|
||||
- `module.settings`, retaining ordered key/value entries and duplicate keys;
|
||||
- `module.unparsed_lines`, `module.unparsed_line_bytes_base64` and
|
||||
`module.complete`, so unfamiliar, malformed, conflicting or undecodable records
|
||||
do not disappear behind a complete result.
|
||||
|
||||
Missing checksums are `null`; empty fields and development/local replacement
|
||||
versions remain unchanged. Build settings such as `GOOS`, `GOARCH` or `vcs.revision`
|
||||
are reported when present. Their absence does not establish a build choice.
|
||||
Go strings may contain arbitrary bytes. Non-UTF-8 records remain available as
|
||||
base64 while other decodable module records are still reported; the reader does
|
||||
not insert replacement characters or reject the entire artifact for those
|
||||
strings.
|
||||
|
||||
## Supported boundary
|
||||
|
||||
The built-in TypeScript reader supports ELF, PE and thin Mach-O images with Go's
|
||||
inline or historical pointer build-info encoding. Source ranges refer to the
|
||||
original stable file snapshot, including pointer-backed strings. It does not
|
||||
scan arbitrary byte strings as compiler metadata or interpret a fat Mach-O
|
||||
container as one selected architecture.
|
||||
|
||||
Historical moduleless Go binaries can store their empty module-string header in
|
||||
declared zero-initialized virtual memory rather than file-backed bytes. The reader
|
||||
recognizes this only when the complete optional module header lies in that memory
|
||||
without a conflicting file mapping. Its module contents are empty and
|
||||
`module_location` has zero bytes, anchored at the build-info field containing the
|
||||
module-header pointer. Compiler strings and other unmapped pointers remain strict
|
||||
file-backed reads.
|
||||
|
||||
`build_info: null` means no recognized record was found in the inspected image.
|
||||
It does not prove that the binary is not Go. Stripping, old builds, deliberate
|
||||
removal and obfuscation can limit metadata. A malformed supported container or
|
||||
recognized build-info record returns an input-format failure. Unsupported
|
||||
containers receive a separate unsupported-target failure.
|
||||
|
||||
Symlinks and nonregular files are rejected. The shared stable reader detects
|
||||
replacement and observed in-place changes during acquisition. Missing files,
|
||||
host permission denial, changed artifacts and cancellation retain distinct
|
||||
errors. The selected file is never modified; this inspection creates no
|
||||
temporary files, starts no subprocesses and accesses no network endpoint.
|
||||
|
||||
Resource guards bound the selected file to 256 MiB, aggregate embedded strings
|
||||
to 1 MiB, structural table decoding to 16 MiB and the complete normalized metadata
|
||||
report to 8 MiB. Oversized results fail without partial success. MCP also applies
|
||||
its [response transport budget](mcp-contracts.md), which counts both text and
|
||||
structured content; a report within the metadata limit can still require export
|
||||
from retained Evidence. These limits are not operating-system memory or CPU
|
||||
isolation.
|
||||
|
||||
Embedded metadata is evidence of file content. It does not authenticate a
|
||||
compiler, dependency, commit or supply chain, and it does not establish runtime
|
||||
behavior. Function names, source mappings, types and decompiled code are outside
|
||||
this tool's coverage. Use the [native workflow](native-investigation.md) when
|
||||
those questions require a disassembler.
|
||||
|
||||
## Real producer verification
|
||||
|
||||
`npm run verify:go:binary` exercises compiler-built fixtures through the public
|
||||
CLI and MCP, comparing their metadata with Go's standard `debug/buildinfo`
|
||||
reader. The verification lane requires Go; normal inspection does not.
|
||||
@@ -98,7 +98,7 @@ For guided registration, set `REA_IDA_MCP_CONFIG` before `rea setup --client <cl
|
||||
|
||||
## Coverage and evidence
|
||||
|
||||
Both profiles support procedure/string inventory and literal or regex search, procedure resolution, pseudocode, assembly, raw function instructions, resolved callees, incoming address references, and a function dossier. Inventories and modern pseudocode/assembly follow upstream pagination. Upstream limits on modern xrefs and callees are retained as explicit truncation limitations. A transport-level truncated preview is rejected rather than reported as complete evidence; the error preserves upstream retrieval guidance.
|
||||
Both profiles support procedure/string inventory and literal or regex search, procedure resolution, pseudocode, assembly, raw function instructions, resolved callees, incoming address references, and a function dossier. Inventories and modern pseudocode/assembly follow upstream pagination. Upstream limits on modern xrefs and callees are retained as explicit truncation limitations. In the modern headless profile, targets that are not proven x86-only use processor-neutral `callgraph` edges instead of the x86 instruction checks in upstream `callees` (mrexodia/ida-pro-mcp#549). REA requests depth zero: the pinned upstream emits the root's outgoing edges without scanning child functions, then REA batches function-name lookup for those addresses. The graph covers recognized function destinations only; imports and other non-function targets remain unknown. A server without `callgraph` returns an empty relationship list with an explicit coverage limitation. Graph and path workflows retain those limitations alongside observed edges. A failed or inconsistent graph reply remains an error. A transport-level truncated preview is rejected rather than reported as complete evidence; the error preserves upstream retrieval guidance.
|
||||
|
||||
The legacy profile additionally supports direct callers. Its producer may report call-site or interior addresses; REA resolves the reported function symbol to its canonical entry. The modern profile does not prove direct callers from its code/data xref labels, so that capability is unavailable and the dossier marks it unknown.
|
||||
|
||||
|
||||
+129
-19
@@ -89,8 +89,11 @@ running server or its registration.
|
||||
|
||||
The npm release checked on **2026-10-11** was **6.4.0**, published from
|
||||
[`a56f7bb6`](https://github.com/morluto/rea/commit/a56f7bb668ab23fec8d33f7af353ac7ed2fe1895).
|
||||
The actual registry package accepted dry-run setup plans for all 17 client IDs
|
||||
listed below, including Qwen Code, Pi and Hermes. OpenCode's plan selected
|
||||
The actual registry package accepted dry-run setup plans for the 17 client IDs
|
||||
in that release, including Qwen Code, Pi and Hermes. DeepSeek Harness (`dsh`)
|
||||
is on main and is not in npm 6.4.0; `rea setup --client dsh` against `@latest`
|
||||
reports an unsupported client until a release containing it is published.
|
||||
OpenCode's plan selected
|
||||
`OPENCODE_CONFIG_DIR` even when that directory did not yet exist. These checks
|
||||
used an isolated home on macOS with Node 24; they verify planning and argument
|
||||
admission, without writing client configuration or establishing live integration
|
||||
@@ -162,6 +165,10 @@ listed after the table because its connector is not one of these files:
|
||||
| OMP | `omp` |
|
||||
| Pi | `pi` |
|
||||
| Hermes | `hermes` |
|
||||
| DeepSeek Harness | `dsh` |
|
||||
|
||||
`dsh` is main-only until a release after 6.4.0. The registry check above does
|
||||
not cover it.
|
||||
|
||||
For GitHub Copilot CLI 1.0.95, `gpt-4.1` model metadata can block ordinary
|
||||
chat with REA's full input-schema profile before sending a model request
|
||||
@@ -182,6 +189,35 @@ schema constraints; REA still validates complete canonical inputs. Full-profile
|
||||
`gpt-5.4` metadata is also verified locally. Live model API acceptance and
|
||||
Windows remain unverified; see [native client verification](testing.md).
|
||||
|
||||
Claude Code normally defers MCP tool definitions through tool search. It
|
||||
[turns tool search off](https://code.claude.com/docs/en/mcp#configure-tool-search)
|
||||
when `ANTHROPIC_BASE_URL` points to a non-first-party host, such as DeepSeek's
|
||||
Anthropic-compatible endpoint. It then sends every REA tool's name, description
|
||||
and input schema with every request
|
||||
([#2019](https://github.com/morluto/rea/issues/2019)). On main at 04089d5be,
|
||||
with 172 tools, that payload measured about 496 KB with the default profile and
|
||||
about 290 KB with `compact`. By the o200k and cl100k tokenizers, that is roughly
|
||||
120k and 68k tokens. Compaction cannot remove tool definitions. If they nearly
|
||||
fill the window Claude Code assumes, the context refills after every compaction
|
||||
and Claude Code reports that autocompact is thrashing. To avoid this:
|
||||
|
||||
- Make Claude Code assume the model's actual context window. DeepSeek's Claude
|
||||
Code guide selects `deepseek-flash[1m]`; the `[1m]` suffix makes Claude Code
|
||||
assume a one-million-token window. For other unrecognized model IDs, set
|
||||
`CLAUDE_CODE_MAX_CONTEXT_TOKENS` to the window the provider documents.
|
||||
- If the endpoint forwards `tool_reference` blocks, set `ENABLE_TOOL_SEARCH=true`
|
||||
to restore deferred loading. DeepSeek's Anthropic API guide does not list
|
||||
`tool_reference` among its supported content blocks, so this setting may cause
|
||||
its requests to fail.
|
||||
- To shrink the upfront catalog, add
|
||||
`"REA_MCP_INPUT_SCHEMA_PROFILE": "compact"` to the `env` object of the
|
||||
`mcpServers.rea` entry in `~/.claude.json` (`$CLAUDE_CONFIG_DIR/.claude.json`
|
||||
when that variable is set), then restart Claude Code. Setup keeps this
|
||||
override.
|
||||
|
||||
The sizes above come from REA's own `tools/list`. A live Claude Code session
|
||||
against DeepSeek's API is unverified.
|
||||
|
||||
For [Pi](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/mcp.md),
|
||||
setup writes a `type: "stdio"` entry under `mcpServers.rea` in the user-level
|
||||
`~/.pi/agent/mcp.json` and installs the bundled skill under `~/.agents/skills`.
|
||||
@@ -259,11 +295,15 @@ configuration and skill destinations; doctor and uninstall use those same paths.
|
||||
|
||||
### DeepSeek Harness
|
||||
|
||||
DeepSeek Harness uses a Cordis YAML patch sequence rather than a `mcpServers`
|
||||
object. REA's `setup --client` does not edit this format. For Harness
|
||||
`0.2.0-rc.2`, append the following entry to `$DSH_HOME/cordis.patch.yml`
|
||||
(`~/.dsh/cordis.patch.yml` when `DSH_HOME` is unset). Preserve existing patches
|
||||
and replace the executable paths with absolute paths from your installation:
|
||||
DeepSeek Harness registers MCP clients through a Cordis YAML patch sequence
|
||||
rather than a `mcpServers` object. `rea setup --client dsh` appends (or
|
||||
replaces) the REA entry in the home patch `$DSH_HOME/cordis.patch.yml`
|
||||
(`~/.dsh/cordis.patch.yml` when `DSH_HOME` is unset), preserving unrelated
|
||||
patches and comments. `rea doctor --client dsh` checks that same entry id.
|
||||
`rea uninstall` removes it by scanning supported clients; uninstall takes no
|
||||
`--client` flag. The home patch applies to every Harness profile, so the
|
||||
registration is written once and no per-profile edit is needed. The written
|
||||
entry is:
|
||||
|
||||
```yaml
|
||||
- insert:
|
||||
@@ -274,17 +314,20 @@ and replace the executable paths with absolute paths from your installation:
|
||||
transport: stdio
|
||||
command: /absolute/path/to/node
|
||||
args: ["/absolute/path/to/rea/scripts/rea.mjs", mcp]
|
||||
cwd: !!js process.cwd()
|
||||
```
|
||||
|
||||
Setup preserves caller-owned Cordis expressions and replaces launcher fields
|
||||
with literal values that doctor can verify. It refuses to edit an expression
|
||||
that produces the entire REA config, or a group-targeted REA insertion. Use a
|
||||
literal config and a root-level insertion as shown above.
|
||||
|
||||
For a global npm installation, the launcher is under
|
||||
`$(npm root -g)/rea-agents/scripts/rea.mjs`. Check the Node executable with
|
||||
`command -v node`. Register `serverName: rea` only once in each active profile,
|
||||
then restart Harness. It exposes tools as `mcp__rea__<tool_name>`.
|
||||
Run `rea setup --skill --dry-run`, review the destination, then
|
||||
`rea setup --skill --yes` to install the workflow in `~/.agents/skills`.
|
||||
Harness discovers that directory, or `$DSH_AGENTS_HOME/skills` when configured;
|
||||
its native `skill` tool can load `reverse-engineer-anything`.
|
||||
`$(npm root -g)/rea-agents/scripts/rea.mjs`. Restart Harness after setup; it
|
||||
exposes tools as `mcp__rea__<tool_name>`. Run `rea setup --skill --dry-run`,
|
||||
review the destination, then `rea setup --skill --yes` to install the workflow
|
||||
in `~/.agents/skills` (`$DSH_AGENTS_HOME/skills` when configured); Harness
|
||||
discovers that directory, and its native `skill` tool can load
|
||||
`reverse-engineer-anything`.
|
||||
|
||||
REA's optional [native client verification](testing.md#end-to-end-integration-and-golden-evidence)
|
||||
checks headless chat, skill loading, all tool input schemas and actual analysis
|
||||
@@ -352,6 +395,12 @@ Other integrations, including Qwen Code, use `~/.agents/skills`.
|
||||
A mixed selection plans each distinct destination. Selecting the skill without a client uses the shared
|
||||
directory. Setup leaves existing skill copies in other locations untouched.
|
||||
|
||||
`rea setup --skill-client claude_code --yes --json` refreshes and verifies the
|
||||
selected skill destination independently of MCP registration selection. The
|
||||
final `doctor.scope.skill_clients` records which skill destinations were
|
||||
checked; `doctor.scope.clients` continues to describe required registrations.
|
||||
An unselected shared skill does not affect a client-only refresh.
|
||||
|
||||
`doctor --skill --json` verifies the selected copies against the bundled
|
||||
instructions and references. Consumers should use `identity.skill.state`,
|
||||
`installed_version`, and `installed_tool_count` for skill readiness. The
|
||||
@@ -561,8 +610,13 @@ need Xvfb, Python 3, X11 and XTEST; approved setup installs those dependencies
|
||||
on supported distributions. Add the curl installer's reported executable
|
||||
directory, commonly `~/.local/bin`, to your shell `PATH` if needed.
|
||||
|
||||
REA starts Hopper when needed. On macOS, its launcher can bring a window or
|
||||
first-run dialog forward; choose demo mode or activate your existing license.
|
||||
REA starts Hopper when needed. On macOS, complete its demo or license prompt in
|
||||
the same user session before the first CLI/MCP operation or unattended job. Open
|
||||
Hopper manually if needed; its launcher can also bring the dialog forward.
|
||||
If startup reports `Cannot launch Hopper! Connection is invalid.` or waits for
|
||||
bridge readiness, check Hopper for a pending demo, license, or loader dialog.
|
||||
Complete the dialog and retry. If REA reports `cleanup_incomplete`, follow the
|
||||
retained-resource guidance below before reopening the target.
|
||||
Hopper serializes analysis requests. Cancelling a wait can leave provider work
|
||||
running, which the session reports. Successful decompilation is cached until
|
||||
a relevant rename or comment changes it.
|
||||
@@ -591,6 +645,11 @@ if document closure is unconfirmed, REA retains the backing image and reports
|
||||
MCP-exit behavior.
|
||||
Startup deadlines report missing bridge readiness and preserve the launcher
|
||||
outcome; a successful helper exit does not prove that a loader dialog completed.
|
||||
If an owned provider process exits, request failures preserve its observed exit
|
||||
code or signal and bounded output in `details.launcher`. An exit can precede
|
||||
output-stream closure; `output_closed: false` keeps that uncertainty explicit.
|
||||
Later requests retain the known exit instead of reducing it to socket loss.
|
||||
An external GUI's helper exit does not establish that the GUI itself exited.
|
||||
Cursor navigation returns the observed object start when Hopper snaps an interior
|
||||
address; adjacent-object navigation rejects unmapped inputs and document ends.
|
||||
Native API text rejects NUL characters and unpaired Unicode surrogates before
|
||||
@@ -763,6 +822,32 @@ is no fixed per-operation or response-size ceiling. Unresolved computed calls
|
||||
remain unknown, reference-kind provenance is preserved, and provider-specific
|
||||
pseudocode is never treated as original source or Hopper-equivalent text.
|
||||
|
||||
### Missing macOS native decompiler
|
||||
|
||||
If `rea doctor --json` reports `native_decompiler` as missing on macOS x64,
|
||||
the extracted Ghidra archive may include the decompiler source without the
|
||||
macOS executable. For Ghidra 12.1.4, you can build it manually with an existing
|
||||
Xcode Command Line Tools installation, `make`, and `bison`:
|
||||
|
||||
```bash
|
||||
cd "$GHIDRA_INSTALL_DIR/Ghidra/Features/Decompiler/src/decompile/cpp"
|
||||
make ghidra_opt
|
||||
decompiler_dir="$GHIDRA_INSTALL_DIR/Ghidra/Features/Decompiler/os/mac_x86_64"
|
||||
mkdir -p "$decompiler_dir"
|
||||
test ! -e "$decompiler_dir/decompile" && test ! -L "$decompiler_dir/decompile" &&
|
||||
install -m 755 ghidra_opt "$decompiler_dir/decompile"
|
||||
rea doctor --json
|
||||
```
|
||||
|
||||
The copy step requires the destination to be absent and preserves an existing
|
||||
decompiler. This recipe is specific to macOS x64: the
|
||||
[Ghidra 12.1.4 Makefile](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/Ghidra/Features/Decompiler/src/decompile/cpp/Makefile)
|
||||
explicitly selects `-arch x86_64` on Darwin. For other architectures or a Gradle
|
||||
build, follow the matching release's
|
||||
[native component instructions](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/GhidraDocs/GettingStarted.md#building-native-components).
|
||||
REA accepts the matching executable under either `os/<platform>/` or
|
||||
`build/os/<platform>/`; it does not build the component during setup or diagnosis.
|
||||
|
||||
### Ghidra import language and analysis seeds
|
||||
|
||||
Ghidra detects the language from the executable header. To set it (for example, Borland C++ PE images):
|
||||
@@ -784,6 +869,27 @@ Each line is `address<TAB>kind[<TAB>name]`, where kind is `function`, `code` or
|
||||
|
||||
Label seeds can name mapped uninitialized storage such as `.bss`; function and code seeds require initialized bytes.
|
||||
|
||||
To skip a named Ghidra auto-analyzer (for example, when default auto-analysis
|
||||
stalls in one analyzer on a Swift/Objective-C arm64 Mach-O):
|
||||
|
||||
```bash
|
||||
export REA_GHIDRA_DISABLED_ANALYZERS="Objective-C Message Analyzer"
|
||||
```
|
||||
|
||||
The value is a comma-separated list of exact Ghidra analyzer names. A pre-script
|
||||
disables each name before auto-analysis, the analysis profile records the
|
||||
override in `analyzer_preset` (for example
|
||||
`ghidra-default+disabled:Objective-C Message Analyzer`), and a session fails to
|
||||
open when its committed profile and the setting differ. A name starts with a
|
||||
letter or digit and contains only letters, digits, spaces, periods, underscores,
|
||||
and hyphens; at most 32 names are admitted, each at most 120 characters.
|
||||
Names must identify analyzers registered for the selected program. Unknown names
|
||||
or options that remain enabled abort the headless import before auto-analysis
|
||||
and the bridge, so an unsuccessful override cannot produce a session claiming
|
||||
that the analyzer was disabled. For Windows PE resource analysis, the exact name
|
||||
is `WindowsResourceReference`. Disabling an analyzer skips the facts it would
|
||||
have produced.
|
||||
|
||||
### Ghidra heap and CPU controls
|
||||
|
||||
Set resource controls in the environment that launches the CLI or MCP server,
|
||||
@@ -845,8 +951,12 @@ verification workflows.
|
||||
|
||||
Unexpected request failures retain their original internal cause. CLI and MCP
|
||||
errors expose Error names, messages, and available codes under
|
||||
`details.diagnostics.failure_cause`; other rejection values retain their
|
||||
primitive value or an explicit type. Shutdown warnings include the failure
|
||||
`details.diagnostics.failure_cause`, including bounded nested causes and explicit
|
||||
truncation markers. Darwin ownership-helper compilation failures also retain
|
||||
the compiler outcome, exit code or signal, and bounded stderr; a timed-out
|
||||
compiler remains distinguishable from a compiler that exited with an error.
|
||||
Other rejection values retain their primitive value or an explicit type.
|
||||
Shutdown warnings include the failure
|
||||
kind, message, and diagnostics, while process and temporary-project cleanup
|
||||
continues. These messages redact known bridge authentication tokens and
|
||||
preserve local paths and other analysis context.
|
||||
|
||||
@@ -66,6 +66,45 @@ code-point order. Dependency manifests remain package observations but do not
|
||||
declare Electron main or renderer entries. If only dependency manifests are
|
||||
present, the inventory artifact remains the graph root.
|
||||
|
||||
## Select sources before analysis
|
||||
|
||||
For a question about known entry sources, supply exact artifact-relative file
|
||||
paths in `source_paths`, or repeat `--source-path` on either CLI route:
|
||||
|
||||
```bash
|
||||
rea analyze /absolute/path/to/app.asar \
|
||||
--source-path package.json --source-path main.js --json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"input_path": "/absolute/path/to/app.asar",
|
||||
"source_paths": ["package.json", "main.js"],
|
||||
"detail": "summary"
|
||||
}
|
||||
```
|
||||
|
||||
REA inventories and verifies the complete artifact, including unselected bytes,
|
||||
but allocates source text and performs static analysis only for selected files.
|
||||
Include supporting manifests, HTML or source maps explicitly when needed;
|
||||
imports do not expand the selection. Paths must match relevant inventoried
|
||||
files exactly, including nested ASAR prefixes. Unknown paths, aliases and globs
|
||||
return an `invalid_request` correction. Selected files are analyzed in request
|
||||
order before unselected scopes are projected. Repeated paths preserve the
|
||||
caller's request and first-occurrence priority, analyzing the file once.
|
||||
Omit the selection for the existing complete
|
||||
application workflow.
|
||||
|
||||
The result and summary retain `source_selection.requested_paths` and
|
||||
`source_selection.unselected_files`. Every relevant file keeps its original
|
||||
inventory identity; unselected files report `text_status:
|
||||
"outside-analysis-scope"`. Their relationships remain unknown, and application
|
||||
coverage is partial while any file remains outside scope. Selection does not
|
||||
claim whole-application completeness or change integrity policy. Summary detail
|
||||
only reduces delivery; source selection reduces text acquisition and analysis.
|
||||
Explicit native provider or snapshot routing rejects `--source-path` rather
|
||||
than ignoring it.
|
||||
|
||||
## ASAR integrity
|
||||
|
||||
ASAR inventory checks Electron integrity metadata for embedded archive entries
|
||||
@@ -135,6 +174,24 @@ default). Reaching it fails the current source with the same memory resource
|
||||
failure and reports `worker_process_memory_limit_bytes`, also a coverage limit;
|
||||
the next source starts a new analyzer. Bun elsewhere enforces no analyzer limit.
|
||||
|
||||
The default semantic capacities are 100000 nodes across the application and
|
||||
20000 per source. Large bundles may explicitly select `max_semantic_nodes` and
|
||||
`max_semantic_nodes_per_file` (CLI: `--max-semantic-nodes` and
|
||||
`--max-semantic-nodes-per-file`), each from 1 to 1000000. The per-file offer is
|
||||
also bounded by the remaining application capacity. Raising these controls does
|
||||
not bypass worker heap, execution deadline or parent result admission. Truncated
|
||||
coverage reports both effective node ceilings; omitted relationships remain
|
||||
unknown.
|
||||
|
||||
While sources remain, the sequential worker reserves one next compact static
|
||||
checkpoint at its 8 MiB bound with the same 6x retention expansion used at
|
||||
admission. Earlier checkpoints are already charged to retained bytes; the
|
||||
remaining inventory does not multiply the reserve. Streamed file transfers keep the default 512 MiB
|
||||
allowance, and can grow with the parent's offered retention budget (8x byte
|
||||
expansion), up to 2 GiB. Individual records remain bounded to 8 MiB. The parent
|
||||
still independently checks actual bytes, record overhead, digests and source
|
||||
identities before adopting results.
|
||||
|
||||
A source that exhausts the analyzer heap does not terminate the CLI/MCP
|
||||
process. Completed static facts are retained, other files can still be analyzed,
|
||||
and unexamined semantic relationships remain explicit unknowns. The parent
|
||||
@@ -159,9 +216,11 @@ completed partial Evidence. MCP failure details return
|
||||
after recording succeeds, so `inspect_analysis_view` can open that analysis
|
||||
directly without serializing the entire partial graph into the error response.
|
||||
CLI failure output retains the complete partial Evidence.
|
||||
Cancellation after source analysis begins likewise
|
||||
retains completed facts; an SDK client may reject its cancelled request before
|
||||
receiving that reply. On the same MCP connection, inspect the Evidence bundle
|
||||
Cancellation after source analysis begins likewise retains completed facts,
|
||||
including cancellation during analyzer cleanup, graph construction, or Evidence
|
||||
sealing. REA finishes projecting those owned facts cooperatively to return partial
|
||||
Evidence. An SDK client may reject its cancelled request before receiving that
|
||||
reply. On the same MCP connection, inspect the Evidence bundle
|
||||
and use the retained analysis ID with `inspect_analysis_view`. Owned analyzer
|
||||
cleanup is verified before its private files are released. Cleanup uncertainty
|
||||
is reported separately and retains ownership for retry.
|
||||
@@ -231,6 +290,11 @@ unknown instead of silently treating them as absent or verified.
|
||||
Plain `.ts` artifact sources use TypeScript syntax without JSX, including
|
||||
angle-bracket type assertions. `.tsx` and `.jsx` retain JSX parsing.
|
||||
|
||||
Relative TypeScript imports can name their emitted `.js`, `.mjs`, or `.cjs`
|
||||
counterparts while resolving to inventoried source files. This substitution
|
||||
requires import or require semantics. Literal Electron preload, renderer, and
|
||||
utility-process runtime paths retain the runtime filename selected by the caller.
|
||||
|
||||
Selected bounded text is then parsed as inert data to recover:
|
||||
|
||||
- `package.json` metadata and declared main or renderer entrypoints;
|
||||
@@ -320,18 +384,68 @@ Source-map contents are parsed as local input. The graph stores original source
|
||||
|
||||
Only explicitly present BrowserWindow values are observations. REA does not
|
||||
substitute version-dependent Electron defaults for omitted `webPreferences`.
|
||||
Dynamic option objects, bridge keys, API objects, and IPC channel expressions
|
||||
Unresolved option objects, bridge keys, API objects, and IPC channel expressions
|
||||
remain unknown and make coverage partial.
|
||||
|
||||
Electron API identity follows proven lexical module aliases. Bundled CommonJS
|
||||
namespace imports are also followed through the interop helpers that bundlers
|
||||
and compilers emit (`__toESM`, `__importStar`, `_interopRequireWildcard`),
|
||||
including the `electron = __toESM(electron)` rewrap that Rolldown emits. This
|
||||
step is inferred: a helper is recognized by its emitted name and a single
|
||||
program-scope function definition, and its body is not evaluated, so a local
|
||||
function that reuses one of these names is trusted to pass the module through.
|
||||
Other reassigned bindings, dynamic selections, and visibly overwritten namespace
|
||||
members are unresolved.
|
||||
Recognized literal module-table bootstraps also support bounded lexical
|
||||
substitution through module exports, import helper bodies, local wrapper calls,
|
||||
returned closures, object spreads, and callback parameters. Recovered Electron
|
||||
paths retain source ranges, module identities, literal channels and branch
|
||||
conditions as static candidates. Unknown spreads, reassigned bindings, accessors,
|
||||
dynamic channels and unsupported syntax do not become exact implementations.
|
||||
This pass neither executes the bundle nor proves that runtime guards pass.
|
||||
An ambiguous or unresolved handler retains its channel registration but does
|
||||
not acquire a path to one arbitrarily selected callback. Unknown factory
|
||||
conditions retain both return branches, including fallthrough. Short-circuit
|
||||
selection distinguishes truthiness from nullishness; uncertain `??` paths
|
||||
retain a nullish guard rather than a falsy guard.
|
||||
|
||||
These paths appear as contextual `javascript-expression` nodes. An exact `api`
|
||||
seed such as `desktop.copy` can trace an exposed member through a unique IPC
|
||||
pairing to an external API call. `addListener` is normalized to `on`. Semantic
|
||||
projection retains these boundary paths before bulk function definitions, within
|
||||
the existing per-file node budget; omitted general semantics remain explicitly
|
||||
partial. Wrapper expansion is bounded by one million visits/substitutions and
|
||||
128 nested substitutions, with exhaustion reported as unknown coverage.
|
||||
|
||||
Electron API identity follows proven lexical module aliases. Namespace interop
|
||||
also preserves identity when the complete Rolldown helper operations, lexical
|
||||
roles, live getters, and captured intrinsic origins match, or when a stable
|
||||
helper simply returns its module parameter. Harmless single-statement control
|
||||
blocks preserve recognition; added operations still require proof. Literal
|
||||
member and destructuring projections are retained from proven results.
|
||||
Omitted or literal node-mode arguments preserve named exports; a module-valued
|
||||
default projection requires a truthy literal node mode. Computed or extra
|
||||
arguments remain unresolved.
|
||||
Recognition uses structure rather than emitted names; other helper bodies and ambiguous
|
||||
runtime fallbacks remain unresolved. Captured function identities survive later global-property
|
||||
replacement, including copies through initialized aliases. Reads before an
|
||||
alias initializer remain unresolved. Visible intrinsic writes before capture or
|
||||
conversion are checked in their owning callable scope, including identifier leaves in nested/default/rest
|
||||
destructuring and loop targets. Property keys and shadowed local bindings do not
|
||||
overwrite the global intrinsic identity. Cross-call side effects, including
|
||||
writes inside directly invoked local functions, are not evaluated. Other
|
||||
reassignments, dynamic selections, and visibly overwritten namespace members
|
||||
are unresolved.
|
||||
Known Object/Reflect mutators remain recognizable through literal destructuring
|
||||
of intrinsic objects and stable bound-function aliases. Calls retain bound
|
||||
argument prefixes and the original bound receiver through `call`, `apply`, and
|
||||
`Reflect.apply`, including generic `Function.prototype` invocation and binding
|
||||
creation. Arguments follow each actual callable invocation rather than property
|
||||
chain depth; rebinding retains the original bound receiver.
|
||||
Visibly replaced invocation intrinsics produce unknown effects instead
|
||||
of using their former call semantics; captured function aliases retain their
|
||||
earlier identity. Unexamined targets remain unresolved. `Reflect.set` examines
|
||||
its explicit receiver as well as its target, retaining unknown receiver effects.
|
||||
Creating an unused binding does not itself mutate an intrinsic. Variable
|
||||
redeclarations retain a single unconditional initializer
|
||||
when they introduce no assignment or second initializer.
|
||||
Stable identifier chains to fresh object, array, function,
|
||||
undecorated class or RegExp syntax keep those targets separate from intrinsic
|
||||
objects when initialized before use; opaque, decorated, reassigned or
|
||||
not-yet-initialized targets remain unresolved. Writes during class initialization
|
||||
are still retained. Function declarations are available before their textual
|
||||
position; variable and class initializers must precede use.
|
||||
Direct and destructuring assignment, loop, update, and delete targets are checked
|
||||
through namespace aliases; unrelated member writes do not discard observed API
|
||||
identity. This static check does
|
||||
@@ -340,7 +454,7 @@ do not establish that an application has no Electron boundary usage.
|
||||
|
||||
IPC pairing is an inference, not an observation. A renderer `invoke` pairs only
|
||||
with one unique `ipcMain.handle`/`handleOnce` candidate on the same exact literal
|
||||
channel; a renderer send pairs only with one unique `ipcMain.on`/`once`
|
||||
channel; a renderer send pairs only with one unique `ipcMain.on`/`once`/`addListener`
|
||||
candidate. Dynamic channels are never paired, and multiple compatible handlers
|
||||
are reported as ambiguous without adding a caller-to-handler edge. A matching
|
||||
channel still does not prove registration order, reachability, or runtime use.
|
||||
@@ -425,16 +539,53 @@ such as `./web3:app.js` still names a local artifact.
|
||||
Package `exports` fallback arrays are supported both at the top level and under
|
||||
the root `"."` entry. The resolver selects targets in declared order using the
|
||||
same conditional and invalid-entry handling as nested exports arrays.
|
||||
Nonempty arrays containing only inactive conditions remain unmatched, allowing
|
||||
the enclosing condition object to continue to a later active condition.
|
||||
Empty arrays and explicit null targets keep their blocking outcome.
|
||||
Refusal evidence distinguishes those source values and retains condition names
|
||||
from unmatched nested targets rather than reporting array indices.
|
||||
For imports and requires, the active Node conditions include `module-sync`;
|
||||
earlier active conditions retain precedence over later ones.
|
||||
|
||||
ESM relative module paths and selected package exports targets use URL suffix handling
|
||||
and one percent-decoding pass. CommonJS relative paths and legacy package main
|
||||
Package self-references use the importer's enclosing package scope before
|
||||
installed dependencies. ESM requires the parsed package name to equal the
|
||||
metadata name; a scope-only name such as `@scope` cannot capture `@scope/pkg`.
|
||||
CommonJS retains Node's raw-request name/prefix matching. Both paths preserve
|
||||
the original specifier and select exports under their own loader conditions.
|
||||
|
||||
Exact package-private `#alias` specifiers resolve against the importer's
|
||||
enclosing `package.json` `imports` map — the nearest same-container package
|
||||
scope, stopping at a `node_modules` directory so a nested package cannot inherit
|
||||
an ancestor's aliases. Selected targets are package-local `./` files with no
|
||||
extension or index fallback, using the same condition, array, and null selection
|
||||
as exports subpaths. A missing key, explicit null, or out-of-scope alias keeps
|
||||
an unresolved edge with that reason. Wildcard keys (`#internal/*`), recursive
|
||||
`#` targets, and external-package import targets remain unresolved.
|
||||
|
||||
Exact keys such as `#/x` are literal lookup identities. `#` and names ending
|
||||
in `/` are invalid. Imports arrays skip invalid parent-relative, absolute, and
|
||||
URL targets before selecting a later package-local file; an unsupported bare
|
||||
package or recursive alias keeps its unresolved outcome rather than selecting
|
||||
a later fallback.
|
||||
|
||||
ESM relative module paths, legacy package subpaths without exports, and selected
|
||||
package exports targets use URL suffix handling and one percent-decoding pass.
|
||||
Bare specifiers retain their complete query/fragment text for exports key matching.
|
||||
For dependency lookup, ESM constructs a package metadata URL instead of treating
|
||||
the parsed package name as a literal directory. A root request such as `legacy?x`
|
||||
does not load either `node_modules/legacy?x` or `node_modules/legacy`; an exact
|
||||
self-reference with that metadata name can still select its exports first.
|
||||
Without an exports map, ESM subpaths select exact files; directory imports and
|
||||
extension fallback are refused. CommonJS relative paths and legacy package main
|
||||
fields retain literal filename punctuation. Exports targets must start with
|
||||
`./` and exclude `.`, `..`, and `node_modules` path segments, including their
|
||||
encoded forms, before URL normalization. Arrays skip invalid exports targets
|
||||
in declared order; URL decoding and file lookup failures for a selected target
|
||||
do not select a later entry. Rejected references retain the package metadata
|
||||
in declared order, including URLs that escape the package root after normalization.
|
||||
Selected package targets use the normalized URL path for lookup; tabs and
|
||||
newlines in the URL text are removed by the URL parser. CommonJS package targets
|
||||
reject encoded separators anywhere in the selected URL; ESM checks its pathname.
|
||||
URL decoding and file lookup failures for a selected target do not select a
|
||||
later entry. Rejected references retain the package metadata
|
||||
path, original target, failed constraint, and importing source location.
|
||||
|
||||
HTML script references resolve to exact inventoried files after applying the
|
||||
|
||||
@@ -36,7 +36,9 @@ application starts, and REA never resumes it or sends
|
||||
`Runtime.runIfWaitingForDebugger`. Resume a paused target with a separate
|
||||
caller-owned debugger before requesting application runtime observations.
|
||||
|
||||
The caller supplies the literal-loopback HTTP endpoint directly. Selecting an endpoint exposes all targets it serves and their local script locations or renderer origins. A target must match the exact requested target ID and expose a same-port WebSocket reported by that endpoint. Targets already marked attached are rejected rather than displacing another debugger. Local paths are canonicalized after symlink resolution.
|
||||
The caller supplies the literal-loopback HTTP endpoint directly. Selecting an endpoint exposes all targets it serves and their local script locations or renderer origins. A target must match the exact requested target ID and expose a same-port WebSocket reported by that endpoint. Targets explicitly marked attached are rejected rather than displacing another debugger. Node's `/json/list` omits attachment state, so REA preserves it as `null` (unknown), reports that limitation, and permits observation unless the endpoint explicitly reports `attached: true`. Local paths are canonicalized after symlink resolution.
|
||||
|
||||
The `attached` field in JavaScript runtime and browser target results is nullable: `true` and `false` are reported only when discovery supplied a boolean; `null` means the endpoint omitted or could not report the state. CDP `TargetInfo` worker records require an `attached` boolean; REA excludes a worker record with a missing or malformed value as an invalid protocol value.
|
||||
|
||||
`list_javascript_runtime_targets` returns every available target in one inline
|
||||
array.
|
||||
|
||||
@@ -58,6 +58,34 @@ reports `resource-limit` with unavailable or partial coverage; omitted facts do
|
||||
not become empty observations. Ambiguous PE section mappings are rejected
|
||||
rather than interpreted according to section order.
|
||||
|
||||
### Selected member inspection
|
||||
|
||||
When whole-assembly member decoding reaches the representation budget, select
|
||||
known declaring types before their signatures and CIL bodies are decoded:
|
||||
|
||||
```bash
|
||||
rea inspect-managed-members /absolute/path/Assembly.dll --type Example.Parser
|
||||
```
|
||||
|
||||
MCP uses `inspect_managed_members` with
|
||||
`{"path":"/absolute/path/Assembly.dll","type_names":["Example.Parser"]}`.
|
||||
Repeat `--type` or supply multiple unique `type_names` for several types.
|
||||
Names match the reported `full_name` exactly. Top-level names are namespace
|
||||
qualified; nested types currently report bare names, which can select several
|
||||
types with that name.
|
||||
|
||||
The result preserves the requested names in `selection.type_names` and reports
|
||||
unmatched names separately. If metadata names are unreadable, limitations retain
|
||||
that uncertainty. With no selection, `selection` is null and whole-assembly
|
||||
inspection keeps its existing resource policy. Selection restricts types,
|
||||
fields, methods, call edges and field accesses; member references remain an
|
||||
assembly-wide inventory, so output size is not guaranteed to be small.
|
||||
|
||||
Coverage describes the selected members. Comparisons leave members outside the
|
||||
opposite observation's selection unknown, and application graphs report their
|
||||
selected scope as partial. Neither makes an absence claim about the whole
|
||||
assembly. Older member Evidence without `selection` remains readable.
|
||||
|
||||
## Analysis objective
|
||||
|
||||
REA's managed-code track is intended to answer five different questions without
|
||||
@@ -126,6 +154,24 @@ Member inspection binds build-local tokens, signatures, row offsets, method
|
||||
bodies, and decoded relationships to that artifact and MVID. Missing or malformed
|
||||
facts remain explicit in coverage and per-member status.
|
||||
|
||||
Metadata table streams may carry the CLR-compatible `ExtraData` extension
|
||||
(`HeapSizes` flag `0x40`), which Microsoft readers honor although it is not
|
||||
specified by ECMA-335. REA skips its fixed four-byte word after the row counts
|
||||
before decoding table rows; the word's value is not a length. An incomplete
|
||||
word is reported as a metadata coverage failure at its file offset, without
|
||||
reading beyond the declared table stream.
|
||||
|
||||
For unoptimized `#-` metadata, nonempty `FieldPtr` and `MethodPtr` tables
|
||||
translate TypeDef list positions to physical member definitions. REA resolves
|
||||
these indirections for member ownership and local attribute-constructor types;
|
||||
field and method tokens still identify the original definition rows.
|
||||
`field_list` and `method_list` retain the metadata's list coordinates, which
|
||||
refer to the pointer table when present rather than necessarily contiguous
|
||||
definition rows. Malformed or ambiguous pointers leave the owner unknown with
|
||||
partial coverage while preserving readable member and attribute facts.
|
||||
The retained inverse indexes are admitted against the existing representation
|
||||
budget before allocation.
|
||||
|
||||
`exported_types` reports ExportedType rows as declared. Facade assemblies such
|
||||
as .NET `System.Runtime.dll` provide their types this way: `forwarder: true`
|
||||
marks a type forwarder, and `implementation_token` names the AssemblyRef, File,
|
||||
|
||||
+103
-19
@@ -113,17 +113,48 @@ Schema does not express those cross-field invariants.
|
||||
|
||||
Call `binary_session` with `{}` and read `result.tool_availability` to choose a
|
||||
callable operation for the current target, provider, host, and negotiated client
|
||||
capabilities. The default result includes the complete inventory with each
|
||||
tool's availability, reason, and remediation. Each entry also reports required
|
||||
and optional negotiated client features plus the currently missing features.
|
||||
The optional inputs `expected_package_version`, `expected_catalog_digest`, and
|
||||
capabilities. The default result contains the complete inventory in groups that
|
||||
share status facts. `tool_availability.client_requirements` is the baseline for
|
||||
each group; a group's optional `client_requirements` replaces that baseline for
|
||||
the group's tools. Expand each group's `names` into one tool entry, carrying its
|
||||
surface, availability, reason, remediation, default-mode status, modes, and
|
||||
client 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.
|
||||
|
||||
The `effects` and `annotations` arrays list the true members of their fixed
|
||||
vocabularies. A known member omitted from either array is false. This applies
|
||||
only to these closed flag sets; it does not make an omitted tool, provider, mode,
|
||||
or limitation false. Keep availability reasons, remediation, client-feature
|
||||
requirements, and mode facts inline with the grouped tool names. The optional
|
||||
inputs `expected_package_version`, `expected_catalog_digest`, and
|
||||
`expected_server_path` compare the live session with the caller's expectations.
|
||||
|
||||
`server_identity.catalog` contains catalog counts and schema digests. Grouped
|
||||
tool availability carries the name set and current per-tool facts; `tools/list`
|
||||
is the canonical source for complete tool definitions, including input and
|
||||
output schemas. Clients that previously read `server_identity.catalog.tools`
|
||||
should expand `tool_availability.groups` and use `tools/list` for schemas. The
|
||||
same catalog identity summary is returned by CLI `capabilities` and `providers`;
|
||||
MCP tool definitions remain available through `tools/list`.
|
||||
|
||||
Provider capabilities are grouped by provider identity and shared facts. In each
|
||||
set, `shared_limitations` is the ordered common prefix; append a group's ordered
|
||||
`limitations` suffix to recover each operation's exact limitations. Group
|
||||
`effects` arrays use the same true-only closed-vocabulary rule as tool
|
||||
availability. Expand every group operation while retaining its provider
|
||||
identity, availability, reason, rejection code, effects, and ordered
|
||||
limitations.
|
||||
|
||||
`binary_session.analysis_provider_candidates` is authoritative for deep-engine
|
||||
discovery. Target-free discovery is sorted by provider ID, reports host
|
||||
availability and `unknown` target support, and does not create an analysis
|
||||
client. `open_binary.provider_id` accepts a concrete provider ID or `auto`; it
|
||||
uses the same parser and selection policy as CLI `--provider` and
|
||||
discovery. Each candidate's capability sets describe that possible provider;
|
||||
they do not select it. The top-level `capabilities` describe the current route,
|
||||
while `analysis_provider_binding` identifies the concrete selected route. These
|
||||
fields are not interchangeable. Target-free discovery is sorted by provider ID,
|
||||
reports host availability and `unknown` target support, and does not create an
|
||||
analysis client. `open_binary.provider_id` accepts a concrete provider ID or
|
||||
`auto`; it uses the same parser and selection policy as CLI `--provider` and
|
||||
`REA_ANALYSIS_PROVIDER`. A successful deep open exposes one immutable provider,
|
||||
concrete version, selection source, and complete analysis profile through
|
||||
`analysis_provider_binding`. Ambiguity and unknown, unavailable, or unsupported
|
||||
@@ -150,19 +181,26 @@ structured data and matching text projection. Oversized errors are retained
|
||||
as Evidence before delivery of a bounded diagnostic with an export reference;
|
||||
if retention is unavailable or fails, the diagnostic reports that reason.
|
||||
|
||||
REA accepts ordinary `tools/call` progress tokens. Updates are monotonic,
|
||||
rate-bounded to at most one intermediate update per 100 ms, and always allow a
|
||||
terminal update. Unknown totals are omitted; REA does not fabricate percentages.
|
||||
REA accepts ordinary `tools/call` progress tokens. The numeric `progress` counts
|
||||
emitted status observations and strictly increases, including when a waiting
|
||||
phase has completed no additional work. Updates are rate-bounded to at most one
|
||||
intermediate update per 100 ms and always allow a terminal update. `total` is
|
||||
omitted because the number of future status observations is unknown. Read
|
||||
`message` for the phase and observed work; REA does not fabricate percentages.
|
||||
`capture_process_scenario` uses that boundary only when the client supplies a
|
||||
progress token. Notifications set `progress` to the count of collected terminal
|
||||
frames, process samples, and interaction events, omit `total`, and put the
|
||||
lifecycle phase, elapsed time, and final cleanup status in `message`. A client
|
||||
progress token. Notifications put the lifecycle phase, elapsed time, and final
|
||||
cleanup status in `message`. A client
|
||||
that does not send a progress token receives no progress notifications. The
|
||||
tool result is unchanged either way.
|
||||
Provider calls receive the request cancellation signal. Artifact traversal,
|
||||
hashing, version comparisons, Hopper requests, and process capture
|
||||
check the same signal. Cancellation is distinct from timeout. A cleanup failure
|
||||
uses `cleanup_incomplete` and lists only the owned resource kinds that remain.
|
||||
An atomic Evidence bundle or snapshot write can publish its complete output
|
||||
before staging cleanup fails. In that case, `details.published: true`,
|
||||
`details.published_path`, and `details.bytes` retain the publication receipt.
|
||||
Use that receipt to distinguish completed publication from a write that never
|
||||
committed. Staging resources remain owned until cleanup is verified.
|
||||
Native call tracing and process capture retain available observations in
|
||||
`details.partial_observation` on failure, including when cleanup succeeds.
|
||||
The observation reports its partial coverage; cleanup details describe host
|
||||
@@ -230,6 +268,18 @@ engine readiness or a percentage of Ghidra auto-analysis. A longer server
|
||||
startup allowance does not extend the client's deadline; progress also does not
|
||||
extend it unless the client explicitly implements that policy.
|
||||
|
||||
While Ghidra startup is pending, REA sends a waiting notification every ten
|
||||
seconds when the request has a progress token. Its message gives elapsed waiting
|
||||
time; import, auto-analysis and bridge readiness may all still be pending.
|
||||
Waiting notifications stop when startup settles or the caller cancels. They do
|
||||
not measure engine work completed.
|
||||
|
||||
The pinned SDK can reset the request's inactivity timeout on progress with
|
||||
`resetTimeoutOnProgress: true`. Also set `maxTotalTimeout` to bound the complete
|
||||
request; the provider's startup deadline still applies. These options belong in
|
||||
the same second argument of `callTool`. Without that explicit client policy, the
|
||||
default 60-second request deadline still expires despite waiting notifications.
|
||||
|
||||
The 240-second setting is a measured example, not a universal timeout: a
|
||||
controlled Linux x64/WSL2 fixture took about 64 seconds for its first overview
|
||||
with Ghidra 12.1.4 on a mounted NTFS installation, native JDK 21, two CPUs, and a
|
||||
@@ -265,9 +315,10 @@ observed process-family member remained after final close. This was a small
|
||||
1,024-byte fixture, so the longer request setting remains an example, not a
|
||||
guarantee for larger targets. No macOS cold-start recovery was exercised.
|
||||
|
||||
With a supplied progress token, the Windows run received an operation-start
|
||||
With a supplied progress token, that earlier Windows run received an operation-start
|
||||
notification before timeout and start/completion notifications for the
|
||||
successful retry. It received no intermediate import or auto-analysis progress.
|
||||
successful retry, before startup waiting notifications were added. It received
|
||||
no intermediate import or auto-analysis progress.
|
||||
These operation markers do not measure analysis work completed or extend the
|
||||
client's deadline. Each new provider session still imports the target into a
|
||||
fresh temporary project.
|
||||
@@ -295,6 +346,16 @@ directly to `compare_functions`, and `inspect_artifact` Evidence to
|
||||
`compare_artifacts`. Use `get_evidence_bundle` when the task needs broader
|
||||
retained session history or an explicit bundle for transfer.
|
||||
|
||||
`get_call_graph` returns a `graph` keyed by traversal depth and a top-level
|
||||
`limitations` array with deduplicated execution caveats. Each node keeps its
|
||||
relationship coverage and read-specific `limitations`, alongside observed
|
||||
`calls`. When a provider does not classify its caveats, they remain on that
|
||||
node and coverage is `unknown`. An empty call list with partial or unknown
|
||||
coverage is not proof that the function has no calls.
|
||||
`trace_call_path` reports each limited expansion in `residual_unknowns` and
|
||||
sets `truncated: true`; any path already supported by observed edges remains
|
||||
visible. Tool errors remain in their existing error or failure fields.
|
||||
|
||||
REA prepares complete MCP results within the pinned stdio client's 10 MiB
|
||||
receive-buffer budget, including both text and structured representations and
|
||||
room for the JSON-RPC envelope. Tool errors carry only their text
|
||||
@@ -306,8 +367,18 @@ session. Its exact reference is reported in
|
||||
`details.reported_limits.evidence_reference`; call `inspect_analysis_view` with
|
||||
that reference for a summary, one section/module, or a stable page, or call
|
||||
`export_evidence_bundle` with a destination path. Complete
|
||||
bundle exports stream canonical JSON into an atomically published file. A broad
|
||||
follow-up or `get_evidence_bundle` can also exceed the response budget; exporting
|
||||
bundle exports stream canonical JSON into an atomically published file.
|
||||
|
||||
For oversized native function Evidence, `details.reported_limits.continuation`
|
||||
contains a ready-to-call `inspect_analysis_view` request for the retained
|
||||
pseudocode's first page. Use its `name` and `arguments` directly, then follow
|
||||
`normalized_result.coverage.next_offset` for subsequent pages. The suggested
|
||||
limit is the view contract's default, not a guarantee that arbitrary source or
|
||||
metadata fits; reduce the limit or export when needed. Continuations are
|
||||
offered for completed `analyze_function` results only after session recording is
|
||||
acknowledged; the complete result and its retention reference remain intact.
|
||||
|
||||
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.
|
||||
To discover existing records without their large payloads, call
|
||||
`get_evidence_bundle` with `detail: "summary"`. Its `result.records` contain
|
||||
@@ -348,6 +419,13 @@ analysis, so later views and application workflows read it without repeating
|
||||
analysis or moving the complete record through the response. A server without
|
||||
session retention refuses summary detail with `capability_unavailable`.
|
||||
|
||||
`inspect_analysis_view` also accepts `api-surface` for retained HAR capture Evidence,
|
||||
grouping request lines by method, origin and format-rule path template. Templated
|
||||
segments are inferences. Because paging follows the full grouping, every page is
|
||||
inferred Evidence whenever any group is templated, and derived Evidence only when
|
||||
no group was templated.
|
||||
See [derived API surfaces](web-network-captures.md#derived-api-surface).
|
||||
|
||||
`inspect_analysis_view` projects a caller-selected view of already completed
|
||||
`inspect_binary_layout`, `analyze_javascript_application`, or `analyze_function`
|
||||
Evidence. Native views select procedure, pseudocode, assembly, basic blocks,
|
||||
@@ -435,7 +513,13 @@ Tool annotations describe effects and are hints, not authorization controls
|
||||
([MCP ToolAnnotations](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations)).
|
||||
`readOnlyHint` includes session state: an analysis call that records additive
|
||||
Evidence is marked non-read-only even when it leaves the target unchanged.
|
||||
`destructiveHint` describes possible data loss, not ordinary Evidence recording.
|
||||
`destructiveHint` is true when a tool may discard data (overwriting or clearing
|
||||
analyst metadata, replacing files, dropping session state), runs or drives code
|
||||
REA does not own (`executesForeignCode`: process, LLDB, Electron, browser, and
|
||||
native UI scenarios), or changes an attached Android device (`mutatesDevice`:
|
||||
push, install, uninstall, start, and stop). Ordinary Evidence recording is not
|
||||
destructive. The effect record in `binary_session` and the generated catalog
|
||||
carries each flag separately.
|
||||
Effect metadata covers possible behavior across supported inputs: DMG inventory
|
||||
can launch `hdiutil` and create an owned temporary mount directory; extraction
|
||||
creates a fresh output directory on every call. Comparing supplied web captures
|
||||
|
||||
@@ -6,11 +6,20 @@ coverage and unknowns. The CLI and MCP use the same application workflows.
|
||||
|
||||
## Static inspection
|
||||
|
||||
`inspect_macho` retains otool segment `file_offset` values relative to the
|
||||
selected Mach-O slice. Its evidence file-offset ranges address the original
|
||||
input file and include the observed lipo slice offset for universal binaries.
|
||||
If that offset is unavailable, segment evidence locations are omitted with an
|
||||
explicit limitation; architecture inventory locations remain available.
|
||||
`inspect_macho.selected_architecture` identifies the exact selected lipo slice
|
||||
and its `container_file_offset` in the input file. A thin Mach-O has a known
|
||||
offset of zero; an unknown universal-slice offset stays null. The Evidence
|
||||
subject format describes the input container: `mach-o-universal` for FAT files,
|
||||
including those with one slice. The normalized result describes the selected
|
||||
Mach-O image. Architecture
|
||||
`file_offset` values are container-relative. Entrypoint, segment and section
|
||||
`file_offset` values retain their source meaning and are relative to the
|
||||
selected Mach-O slice. Raw load-command fields retain their original values.
|
||||
Evidence locations map the LC_MAIN entrypoint and nonempty file-backed segment
|
||||
ranges to container offsets by adding the observed slice offset. Zero-fill
|
||||
segments do not produce file ranges. When the selected slice's container offset
|
||||
is unknown, those mapped locations are omitted with an explicit limitation;
|
||||
architecture inventory locations remain available.
|
||||
|
||||
Hopper's `address_to_file_offset` combines the native image-relative mapping with
|
||||
original-file coordinates only after checking the source against the session's
|
||||
@@ -53,6 +62,10 @@ complete observation.
|
||||
classes are preserved. Select `root`, `offset` and `limit` explicitly.
|
||||
UID 0 cannot distinguish conditional nil from ordinary nil. Limits are
|
||||
64 MiB of input, 200,000 objects/references and 128 nesting levels.
|
||||
XML dictionary entries decoded as `__proto__` are omitted with an explicit
|
||||
count because REA results cannot represent that key. XML tag whitespace and
|
||||
comments do not change that handling. Other values and the original archive
|
||||
digest are preserved; key interpretation follows the pinned plist decoder.
|
||||
- `decode_interface_builder` / `rea decode-interface-builder <app>` reads
|
||||
compiled storyboard and nib resources into objects, outlets, actions,
|
||||
connections and controller/class names. `verify:interface-builder` compiles
|
||||
@@ -192,8 +205,14 @@ rea inspect-native-data-type <binary> --address <typed-data-address> --provider
|
||||
Instruction facts include bytes, length, decoder text, ordered register/scalar/
|
||||
address tokens, typed references and flow destinations. Mid-instruction, data,
|
||||
outside-memory and undecodable addresses have separate outcomes. Effective
|
||||
memory base/index/displacement roles and per-instruction context mode remain
|
||||
unavailable; `mode` is the program language variant.
|
||||
memory base/index/displacement roles remain unavailable. Ghidra reports its program
|
||||
language variant in `mode`; other providers retain their own mode label. Ghidra
|
||||
MIPS results additionally expose `decoder_context`
|
||||
with the observed `ISA_MODE` register at the queried address and its provider
|
||||
source. A null value is unavailable, not mode zero; an empty array reports no
|
||||
available context register, and omission preserves older captures or providers
|
||||
that do not expose this facet. The context is an analysis observation, not proof
|
||||
of a runtime processor state.
|
||||
|
||||
`inspect_native_instruction` also returns `incoming_references` to the exact
|
||||
queried address, including data, interior-instruction and undecodable addresses.
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Headerless raw-binary analysis with Ghidra
|
||||
|
||||
REA admits a headerless executable when the caller supplies the CPU family and
|
||||
the address where file offset 0 should be mapped. That interpretation is not
|
||||
inferred from a `.bin` suffix or from unrecognized bytes.
|
||||
|
||||
## Open and inspect
|
||||
|
||||
```bash
|
||||
rea inspect /absolute/path/to/ecu.bin --target-format raw-binary --architecture arm --entry-point 0x8000000 --provider ghidra --json
|
||||
rea function /absolute/path/to/ecu.bin 0x8000000 --target-format raw-binary --architecture arm --entry-point 0x8000000 --provider ghidra --json
|
||||
rea inspect-native-load-image /absolute/path/to/ecu.bin --target-format raw-binary --architecture arm --entry-point 0x8000000 --provider ghidra --json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "open_binary",
|
||||
"arguments": {
|
||||
"path": "/absolute/path/to/ecu.bin",
|
||||
"format": "raw-binary",
|
||||
"architecture": "arm",
|
||||
"entry_point": "0x8000000",
|
||||
"provider_id": "ghidra"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`architecture` is one of `x86`, `x86_64`, `arm`, or `arm64`. `entry_point` is a
|
||||
hexadecimal address of the form `0x…`. The complete file is mapped starting at
|
||||
that address, and Ghidra seeds an `entry` function there before auto-analysis.
|
||||
ARM and AArch64 entries must be 4-byte aligned; x86 entries may be byte-aligned.
|
||||
`open_binary` and an open `binary_session` return the canonical `entry_point`
|
||||
for raw targets so callers can verify the selected mapping directly.
|
||||
Omitted `format` keeps header-based detection; architecture and entry without
|
||||
`format=raw-binary` are rejected.
|
||||
|
||||
Ghidra imports a private snapshot with `BinaryLoader` and these little-endian
|
||||
generic languages and compiler specifications:
|
||||
|
||||
| architecture | language_id | compiler_spec_id |
|
||||
| ------------ | ------------------- | ---------------- |
|
||||
| x86 | `x86:LE:32:default` | `gcc` |
|
||||
| x86_64 | `x86:LE:64:default` | `gcc` |
|
||||
| arm | `ARM:LE:32:v7` | `default` |
|
||||
| arm64 | `AARCH64:LE:64:v8A` | `default` |
|
||||
|
||||
Compiler specifications select analysis conventions; they do not identify the
|
||||
original compiler or ABI of a headerless target. Ghidra's x86 languages do not
|
||||
provide a compiler specification named `default`.
|
||||
|
||||
Those settings, the caller-supplied entry, and `language_selection:
|
||||
caller-supplied-v1` are committed in the analysis profile. A different entry
|
||||
or architecture is a different profile; snapshots from header-detected targets
|
||||
do not replay.
|
||||
|
||||
The mapped start is independent of Ghidra's program image base, which remains
|
||||
`0x0` for BinaryLoader. Ghidra may report additional language-defined entry
|
||||
points, including unmapped ARM vectors. Load-image verification requires the
|
||||
caller-supplied entry and preserves those other entries as observations.
|
||||
Independent raw verification streams the complete private snapshot digest and
|
||||
extent with bounded memory and supports cancellation during the read.
|
||||
|
||||
Hopper and IDA refuse raw-binary targets. Windows Ghidra P0 continues to admit
|
||||
PE applications only.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Headers are not discovered from file bytes. This increment does not guess
|
||||
load addresses, vector tables, or memory maps.
|
||||
- Big-endian, Thumb-only, Cortex-M, and other Ghidra processor variants are
|
||||
unsupported.
|
||||
- Reset vectors, memory-mapped I/O, and firmware boot sequences are not
|
||||
emulated.
|
||||
- Independent load-image verification covers source identity, mapped file
|
||||
bytes, the bound language, and the caller-supplied entry. Original bytes
|
||||
stay unchanged.
|
||||
+48
-16
@@ -112,8 +112,12 @@ Set `REA_VERIFY_HERMES_STICKY_PROFILE=1` to exercise native Hermes selection of
|
||||
a named sticky profile with `HERMES_HOME` still pointing to its root.
|
||||
|
||||
`verify:gemini-client` requires an installed Gemini CLI (verified with
|
||||
`@google/gemini-cli@0.63.0`); select it with `REA_VERIFY_GEMINI_COMMAND`.
|
||||
The optional POSIX lane uses native `GEMINI_CLI_HOME` discovery in an isolated
|
||||
`@google/gemini-cli@0.63.0` on POSIX and native Windows); select it with
|
||||
`REA_VERIFY_GEMINI_COMMAND`. A selected `.js`, `.mjs` or `.cjs` entry point runs
|
||||
under the verifier's Node. On Windows, select the npm install's
|
||||
`node_modules/@google/gemini-cli/bundle/gemini.js`, because only a `gemini.cmd`
|
||||
shim reaches `PATH`.
|
||||
The optional lane uses native `GEMINI_CLI_HOME` discovery in an isolated
|
||||
Git project, checks setup plans, backups and idempotence, activates the installed
|
||||
personal skill, validates all forwarded input JSON Schemas against their declared
|
||||
dialect, forwards the complete REA catalog and checks full JavaScript
|
||||
@@ -121,7 +125,7 @@ Evidence for a Unicode path. Use `-- chat` for ordinary chat with REA enabled,
|
||||
or `REA_VERIFY_RUNTIME_ROOT` for a production-only installed REA package.
|
||||
The local fixture exercises the real Gemini API adapter, including its
|
||||
`untrusted_context` tool-result envelope. Token counts are synthetic; live
|
||||
Google API and native Windows compatibility remain unverified. The lane disables
|
||||
Google API compatibility remains unverified. The lane disables
|
||||
the client's memory-based relaunch to preserve the caller's Node heap budget.
|
||||
|
||||
`verify:opencode-client` requires an installed OpenCode (verified with
|
||||
@@ -139,8 +143,11 @@ directory, so default OS-home discovery remains unverified. Live model APIs and
|
||||
native Windows are also unverified.
|
||||
|
||||
`verify:claude-client` requires installed Claude Code (verified with
|
||||
`@anthropic-ai/claude-code@2.1.296`); select it with `REA_VERIFY_CLAUDE_COMMAND`.
|
||||
The optional POSIX lane uses native `CLAUDE_CONFIG_DIR` discovery, an independent
|
||||
`@anthropic-ai/claude-code@2.1.296` on POSIX and native Windows); select it
|
||||
with `REA_VERIFY_CLAUDE_COMMAND`. On Windows, select
|
||||
`claude.exe`: npm installs put only a `claude.cmd` shim on `PATH`, and the real
|
||||
executable is under `node_modules/@anthropic-ai/claude-code/bin`.
|
||||
The optional lane uses native `CLAUDE_CONFIG_DIR` discovery, an independent
|
||||
Git workspace, user settings and default built-in tools. It preserves caller
|
||||
preferences and backups, checks idempotent setup, waits through the native
|
||||
`WaitForMcpServers` tool when discovery is pending, and validates the complete
|
||||
@@ -149,8 +156,8 @@ native `Skill`, checks its full body and verifies named-schema JavaScript Eviden
|
||||
for a Unicode path. Use `-- chat` for ordinary chat with REA enabled, or
|
||||
`REA_VERIFY_RUNTIME_ROOT` for a production-only installed package. The local
|
||||
Anthropic Messages/SSE fixture preserves native resource-hint envelopes in its
|
||||
request artifacts. Usage is synthetic; live Anthropic API, native Windows and
|
||||
bare-mode personal-skill activation remain unverified by this lane.
|
||||
request artifacts. Usage is synthetic; live Anthropic API and bare-mode
|
||||
personal-skill activation remain unverified by this lane.
|
||||
|
||||
`verify:copilot-client` requires installed GitHub Copilot CLI (verified with
|
||||
`@github/copilot@1.0.95`); select it with `REA_VERIFY_COPILOT_COMMAND`.
|
||||
@@ -265,9 +272,13 @@ unverified; skill discovery uses an explicit isolated custom directory.
|
||||
`verify:deepseek-client` requires an installed DeepSeek Harness (`dsh`;
|
||||
verified with `@deepseek-ai/dsh@0.2.0-rc.2`) and Git. Set
|
||||
`REA_VERIFY_DEEPSEEK_COMMAND` to select its executable. This optional POSIX lane
|
||||
installs the skill through `rea setup --skill`, writes the native Cordis MCP
|
||||
patch into an isolated `DSH_HOME`, and binds `HOME` and `USERPROFILE` to the
|
||||
disposable account. The native default `~/.agents/skills` search path discovers
|
||||
seeds caller model settings and a stale Cordis registration in an isolated
|
||||
`DSH_HOME`, then runs public `setup --client dsh` dry-run, setup, repeated setup,
|
||||
and scoped doctor. It checks the backup and unrelated patches before booting
|
||||
Harness with the generated registration. After native execution it uninstalls
|
||||
through the public CLI and checks that only caller patches remain. `HOME` and
|
||||
`USERPROFILE` point to the disposable account. The native default
|
||||
`~/.agents/skills` search path discovers
|
||||
the installed skill without a `DSH_AGENTS_HOME` override. A separate Git root prevents inherited project skills from
|
||||
masking a missing installation. The default `call` mode checks skill loading,
|
||||
complete catalog discovery, the actual forwarded input schemas and regexes,
|
||||
@@ -278,11 +289,19 @@ context capacity declared by its default DeepSeek Flash model; it does not
|
||||
establish live DeepSeek API or native Windows coverage. Receipts include
|
||||
request artifacts, result digests and owned-process lineage.
|
||||
|
||||
The DSH acceptance journey also exercises public setup, approval, doctor,
|
||||
generated-launcher MCP startup and uninstall without an installed Harness.
|
||||
That journey proves REA workflow behavior, not native Cordis expression
|
||||
evaluation. Filesystem regressions retain tagged values and unrelated rows
|
||||
across alias dependencies and check refusal of unsafe YAML without mutation.
|
||||
|
||||
Full E2E tests invoke the production command dispatcher and real providers,
|
||||
without fake launchers, runners or responses. `verify:keyed-archive` writes an
|
||||
actual Foundation binary archive, runs the CLI and a separate stdio MCP
|
||||
subprocess, checks parity and pagination, rejects an escaping path and checks
|
||||
an XML graph golden. `verify:asset-catalog` compiles source-owned colors with
|
||||
without fake launchers, runners or responses. `verify:keyed-archive` writes
|
||||
Foundation binary, XML, and integer archives using one run-owned Swift module
|
||||
cache. It checks CLI and separate stdio MCP parity, pagination, escaping-path
|
||||
rejection, an XML graph golden, and numeric meaning for safe controls, unsafe
|
||||
integers, boxed numbers, and integral-valued reals. `verify:asset-catalog`
|
||||
compiles source-owned colors with
|
||||
`actool`, invokes real `assetutil`, then checks CLI/MCP results, exact catalog
|
||||
digest, every raw metadata field, pagination and malformed input rejection.
|
||||
Neither artifact workflow requires Hopper or launches it. Both run in macOS CI.
|
||||
@@ -458,7 +477,7 @@ prove behavior on the verified Ghidra 12.1.4 and JDK 21 build.
|
||||
| Ghidra lane | Supported runner/target | Additional local tools |
|
||||
| ------------------------------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
||||
| `npm run verify:ghidra` | Linux x64/arm64 ELF or macOS x64/arm64 Mach-O | Host C compiler, Ghidra 12.1.4, and full JDK 21 |
|
||||
| `npm run verify:ghidra:seeds` | POSIX host; source-owned 32-bit x86 raw fixture; function order and BSS labels | Ghidra 12.1.4 and full JDK 21 |
|
||||
| `npm run verify:ghidra:seeds` | POSIX; raw x86 fixture; seed ordering, BSS labels, analyzer readback and unknown-name rejection | Ghidra 12.1.4 and full JDK 21 |
|
||||
| `npm run verify:ghidra:swift` | macOS x64/arm64 Mach-O | Host Swift compiler, Ghidra 12.1.4, and full JDK 21 |
|
||||
| `npm run verify:ghidra:switch` | Linux x64 ELF; GCC/Clang optimized and stripped switch fixtures | GCC, Clang, GNU nm/objdump/strip, Ghidra 12.1.4, and full JDK 21 |
|
||||
| `npm run verify:ghidra:aarch64-jump-table` | Any supported Ghidra host; AArch64 ELF; signed/unsigned byte/halfword tables; host ARM64 Mach-O | Clang with AArch64 target support, Ghidra 12.1.4, and full JDK 21 |
|
||||
@@ -793,6 +812,16 @@ truncation constraint, while leading-zero coordinates still resolve correctly.
|
||||
It has the same Ghidra/JDK prerequisites as the MZ lane. Neither lane claims DOS
|
||||
runtime or PC-98 device execution.
|
||||
|
||||
`npm run verify:ghidra:raw` uses a generated ARM little-endian headerless image
|
||||
through the compiled CLI and stdio MCP server. With the same Ghidra/JDK
|
||||
prerequisites, it checks preparation and bridge startup, verified load mappings,
|
||||
instruction and byte readback, canonical-entry session reuse, replacement when
|
||||
the entry changes without `provider_id`, unchanged source bytes, and owned
|
||||
cleanup. Six-byte x86 and x86_64 controls also verify supported compiler
|
||||
specifications, independent mappings, unchanged bytes, and decompilation of a
|
||||
known return value. AArch64 remains covered by deterministic tests rather than
|
||||
this real-provider lane. See [raw binary analysis](raw-binary.md).
|
||||
|
||||
## Apple Interface Builder archives
|
||||
|
||||
`npm run verify:interface-builder` compiles the source-owned AppKit XIB into a
|
||||
@@ -1371,7 +1400,10 @@ generation. It installs nothing. Without configuration, the default invocation
|
||||
reports a named skip; `--require-tools` makes missing configuration fail.
|
||||
|
||||
The verifier creates and validates real modules with custom sections, multiple
|
||||
import/export kinds, escaped names and empty modules. It compares exact WAT,
|
||||
import/export kinds, escaped names, an empty module, and a bulk-memory
|
||||
DataCount section. Debug-name fixtures cover compiler-generated module names
|
||||
and preserved name-section payloads with empty, multiline, Unicode, long and
|
||||
NUL-containing names. It compares exact WAT,
|
||||
section output and selected/tool digests through the compiled CLI and real MCP
|
||||
SDK, validates advertised schemas, checks identical CLI/MCP Evidence IDs and
|
||||
session bundle readback, retains invalid/truncated module diagnostics, preserves
|
||||
|
||||
@@ -55,6 +55,11 @@ satisfy. A plan without execution does not establish runtime behavior.
|
||||
- Keep observed, derived, and inferred relationships distinct. Preserve
|
||||
unresolved edges and partial provider facts instead of requiring every
|
||||
provider to supply the same metadata.
|
||||
- For caller/callee reads, `relationship_coverage` describes only the
|
||||
provider-observed direct relationship set. `complete` means that set was
|
||||
returned without a known read limit; it does not claim every possible runtime
|
||||
edge. Provider and execution caveats stay in `limitations` and do not imply
|
||||
incomplete relationship coverage. Missing coverage remains `unknown`.
|
||||
- Declare process, filesystem, network, UI, and mutation effects truthfully;
|
||||
enforce the requested target and lifecycle.
|
||||
- Add limits only for real format, protocol, authority, or measured resource
|
||||
|
||||
@@ -86,8 +86,22 @@ harm_rank,location,trigger,current_copy,user_risk,proposed_replacement,implement
|
||||
77,install.sh:node missing,node is absent,"Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+ is required; install it from nodejs.org and retry.",Low risk; exact versions and recovery,Keep current copy,unchanged,verified by shell branch inspection
|
||||
78,install.sh:npm missing,npm is absent,npm is required; install it with Node.js and retry.,Low risk; exact recovery,Keep current copy,unchanged,verified by shell branch inspection
|
||||
79,install.sh:node unreadable,node exists but does not report a parseable version,could not determine the active Node.js version.,No cause-local recovery,"The active Node.js version could not be read. Check that node works and is on PATH, then retry.",implemented,verified by exact unreadable-Node-version installer test
|
||||
80,install.sh:Node 22 floor,Node 22 minor version is below 19,"Node.js VERSION is unsupported; use Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+.",Low risk; exact detected version and supported alternatives,Keep current copy,unchanged,verified by exact installer test using Node 20 and branch inspection
|
||||
81,install.sh:Node 24 floor,Node 24 minor version is below 11,"Node.js VERSION is unsupported; use Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+.",Low risk; exact detected version and floor,Keep current copy,unchanged,verified by shell branch inspection
|
||||
80,install.sh:Node 22 floor,Node 22 minor version is below 19,"Node.js VERSION is unsupported; use Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+.",No install guidance; operator must guess between nvm/fnm/system packages/nodejs.org,"Node.js VERSION is unsupported.
|
||||
|
||||
REA requires Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+. Install one via:
|
||||
nvm: nvm install 24 && nvm use 24 (https://github.com/nvm-sh/nvm)
|
||||
fnm: fnm install 24 && fnm use 24 (https://github.com/Schniz/fnm)
|
||||
Direct download for OS/ARCH:
|
||||
https://nodejs.org/dist/latest-v24.x/ (grab the OS-ARCH tarball)
|
||||
Then rerun this installer.",implemented,verified by exact installer test using Node 20 against the `linux/x64` fake host and by shell branch inspection
|
||||
81,install.sh:Node 24 floor,Node 24 minor version is below 11,"Node.js VERSION is unsupported; use Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+.",No install guidance; operator must guess between nvm/fnm/system packages/nodejs.org,"Node.js VERSION is unsupported.
|
||||
|
||||
REA requires Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+. Install one via:
|
||||
nvm: nvm install 24 && nvm use 24 (https://github.com/nvm-sh/nvm)
|
||||
fnm: fnm install 24 && fnm use 24 (https://github.com/Schniz/fnm)
|
||||
Direct download for OS/ARCH:
|
||||
https://nodejs.org/dist/latest-v24.x/ (grab the OS-ARCH tarball)
|
||||
Then rerun this installer.",implemented,verified by shell branch inspection
|
||||
82,install.sh:version syntax,Explicit version is not exact semantic version,version must be an exact semantic version.,Low risk; exact correction,Keep current copy,unchanged,verified by shell branch inspection
|
||||
83,install.sh:release lookup,Latest GitHub release request fails,could not resolve the latest GitHub release.,No fallback despite supporting pinned versions,"The latest REA release could not be resolved. Check network access or pass --version VERSION, then retry.",implemented,verified by exact release-lookup installer test
|
||||
84,install.sh:release response,Latest release response is not valid expected JSON,Node JSON parser stack followed by the safe release-response message,Leaks a parser stack and buries recovery guidance,The release response was invalid. Retry later or pass --version VERSION.,implemented,verified by exact malformed-release test with parser stderr suppressed
|
||||
|
||||
|
+11
-1
@@ -4,6 +4,15 @@
|
||||
local WASM bytes using caller-supplied WABT 1.0.42. No active binary target is
|
||||
required. REA does not install tools or fetch artifacts and does not execute WASM.
|
||||
|
||||
`open_binary` is not the WASM inspection route. A recognized WASM v1 header
|
||||
returns `target_unavailable` with category `unsupported_target` and constraint
|
||||
`wasm_requires_static_inspection`, including the selected path in a valid
|
||||
`inspect_wasm_artifact` call. Use that target-free tool or
|
||||
`rea inspect-wasm-artifact <path> --format json` with the toolchain below.
|
||||
Check `binary_session` for configured capability availability. A recognized
|
||||
header is not validation of the complete module; missing WABT is reported
|
||||
separately as an unavailable capability, not as missing native engine support.
|
||||
|
||||
Set `REA_WABT_BIN_DIRECTORY` to an absolute directory containing executable
|
||||
`wasm-validate`, `wasm-objdump` and `wasm2wat` (with `.exe` on Windows). Missing
|
||||
executables, unsupported banners and inaccessible tools produce actionable
|
||||
@@ -37,7 +46,8 @@ into a private workspace as read-only `module.wasm`; its digest is rechecked
|
||||
before and after each tool. WABT validates with `--enable-all`,
|
||||
reports sections through `wasm-objdump -h -x`, and decodes through
|
||||
`wasm2wat --enable-all`. The result retains selected byte SHA-256 and size,
|
||||
section payload ranges, full header/detail output, exact import/export WAT
|
||||
section payload ranges, full header/detail output (including module-name
|
||||
preambles), exact import/export WAT
|
||||
forms, decoded WAT and its UTF-8 SHA-256. Producer format drift and invalid or
|
||||
truncated modules are failures, with validation diagnostics and partial Evidence
|
||||
retaining selected byte identity and tool provenance.
|
||||
|
||||
@@ -100,6 +100,11 @@ resources follow the same explicit declarations without changing error types;
|
||||
sensitive issue pointers identify a real ancestor instead of a fabricated
|
||||
coordinate. Header/cookie authentication values and known
|
||||
transport URL userinfo are excluded structurally, including native backups.
|
||||
HAR redaction and API-surface projection share the same recorded-URL parser.
|
||||
Canonical userinfo spans are removed without rewriting the remaining source
|
||||
text. Credential-bearing spellings without a safe raw span, such as slashless
|
||||
or extra-slash HTTP URLs, are excluded as a whole transport field. Ordinary
|
||||
extension text and noncredential path/query values remain unchanged.
|
||||
|
||||
Generated free-text limitations follow the same literal declarations, including
|
||||
global, per-record and Evidence explanations added during projection. Contract
|
||||
@@ -112,6 +117,75 @@ wire fragments. Native dictionary duplicate keys have the unchanged upstream
|
||||
decoder's last-value semantics; this adapter does not claim to recover discarded
|
||||
duplicates or the original numeric spelling of parsed native floats.
|
||||
|
||||
## Derived API surface
|
||||
|
||||
Pass retained HAR Evidence to `inspect_analysis_view` with
|
||||
`view: {"kind":"api-surface","offset":0,"limit":50}`. Omitted offset and limit
|
||||
are 0 and 50. The view groups request lines by method, WHATWG URL origin and
|
||||
path template, sorted by origin, template and method. Query strings, fragments,
|
||||
credentials, headers and bodies are not copied into groups.
|
||||
|
||||
| Rule | Exact segment format | Placeholder |
|
||||
| ---------- | ---------------------------------------------------- | ------------ |
|
||||
| `numeric` | ASCII digits only | `{numeric}` |
|
||||
| `uuid` | RFC 9562 textual UUID, any version, case-insensitive | `{uuid}` |
|
||||
| `hex` | At least 32 hexadecimal digits | `{hex}` |
|
||||
| `iso-date` | Valid calendar date `YYYY-MM-DD` | `{iso-date}` |
|
||||
|
||||
Rules apply in table order to the recorded, still-encoded request path, read
|
||||
before WHATWG dot-segment removal, so `/a/%2e%2e/admin` and `/admin` stay distinct. Segment indexes start
|
||||
at zero after the leading slash. Each matched segment lists its observed literals
|
||||
and `basis: "inferred"`. `templated_groups` counts templated groups across every
|
||||
page. Because paging follows the full grouping, every page is inferred Evidence
|
||||
when that count is nonzero, and derived Evidence only when no group was templated. Version prefixes, short opaque IDs
|
||||
and digit-bearing route names stay literal. Samples merge only when method,
|
||||
origin, and exact segment rules match. The view does not infer further
|
||||
cross-sample patterns.
|
||||
|
||||
Groups cite parent record ordinals and distinct statuses and MIME types (lowercase
|
||||
type/subtype without parameters). An invalid type/subtype essence is unknown;
|
||||
parameters do not affect this projection. Flags report single samples, mixed
|
||||
statuses or mixed content types.
|
||||
`coverage` pages groups; `records` separately reports examined, total and excluded
|
||||
parent records. Ordinal subsets carry a limitation. Null URLs are excluded as
|
||||
`url-redacted`; unparseable URLs, URLs without an origin, spellings without
|
||||
`scheme://authority` (such as `https:host/path`), and authorities containing a
|
||||
tab or newline as `url-unparseable`.
|
||||
Unavailable request objects or methods are explicitly excluded. Unavailable
|
||||
response fields, including HAR's `status: 0` and an empty `mimeType`, remain
|
||||
unknown, not invented statuses or MIME types.
|
||||
|
||||
The view makes no handler-identity, schema or causality claim. It performs no
|
||||
network activity. Native mitmproxy captures return a typed unsupported error.
|
||||
Parents inspected with `sensitive_values`, or whose Evidence does not record
|
||||
`sensitive_value_count`, return a typed unsupported error whose reason is the
|
||||
canonical token `sensitive_values_unretained`. The view would write path
|
||||
templates and limitation text that it cannot check against declarations the
|
||||
parent does not retain, so the refusal does not repeat declaration text.
|
||||
|
||||
One accumulator owns group identity, observed-value/status/MIME sets, ordinals,
|
||||
exclusions and deduplicated limitations. It scans path spans without splitting
|
||||
literal paths into arrays. Before schema parsing, it reserves the capture clone
|
||||
and validation scratch by walking the parent without cloning it. Inline requests
|
||||
borrow the Evidence value through CLI/MCP envelope validation; the application
|
||||
admits the entire portable envelope before authenticated parsing or hashing.
|
||||
The envelope clone, hash scratch and capture clone stay charged during grouping.
|
||||
Temporary URL/path/MIME work and retained state must
|
||||
fit together within a 96 MiB projection representation budget. A whole group or
|
||||
merge is admitted before allocating its retained sets, map entries or array
|
||||
values. Fixed group storage, distinct text, exclusions and limitations count as
|
||||
well as matched segments. Per-record reservations are released after each
|
||||
record; repeated values do not accumulate another retained charge. Only the
|
||||
requested page is materialized as output groups.
|
||||
|
||||
The representation budget uses conservative storage and worst-case JSON-escaping
|
||||
allowances; it is not an enforced V8 heap or process-memory limit. It is separate
|
||||
from the decoder's actual reply-byte limit and MCP transport admission. Exceeding
|
||||
it returns `resource_constraint` without a partial view. Limitations on the
|
||||
parent envelope, capture and examined records are deduplicated onto the view.
|
||||
Portable captures must retain unique ordinals within their declared total;
|
||||
caller-selected subsets can preserve any requested ordinal order.
|
||||
|
||||
## Resource and lifecycle boundaries
|
||||
|
||||
Complete evidence has a 32 MiB input, 96 MiB decoder reply and 64-level nesting
|
||||
|
||||
+136
-88
@@ -1,4 +1,12 @@
|
||||
#!/bin/bash
|
||||
{
|
||||
# Truncation guard: this brace group opens on line 2 and closes on the last
|
||||
# line, so bash must read the entire file before it runs any command. On the
|
||||
# `curl ... | bash` path, a download cut off anywhere before the final `}` is
|
||||
# an unterminated compound command, which bash reports as a syntax error at
|
||||
# EOF without running anything. Without the guard, a cut right after `set`
|
||||
# would run the bare builtin and print every shell variable, and a cut right
|
||||
# after `main` would run the installer without the caller's flags.
|
||||
set -euo pipefail
|
||||
|
||||
PACKAGE="rea-agents"
|
||||
@@ -17,108 +25,148 @@ fail() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Resolve the current host's Node.js tarball label pair; a label we cannot map
|
||||
# is kept verbatim so the operator can still read it. Falls back on the raw
|
||||
# `uname` outputs rather than inventing a label the Node.js release index does
|
||||
# not publish, so a mismatch is obvious instead of silently wrong.
|
||||
node_host_labels() {
|
||||
case "$(uname -m)" in
|
||||
arm64|aarch64) node_arch_label="arm64" ;;
|
||||
x86_64|amd64) node_arch_label="x64" ;;
|
||||
*) node_arch_label="$(uname -m)" ;;
|
||||
esac
|
||||
case "$(uname -s)" in
|
||||
Darwin) node_os_label="darwin" ;;
|
||||
Linux) node_os_label="linux" ;;
|
||||
*) node_os_label="$(uname -s | tr '[:upper:]' '[:lower:]')" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Exit with actionable remediation when the active Node.js is unsupported.
|
||||
# The engine floor stays in sync with package.json#engines.node — change both.
|
||||
fail_node_version() {
|
||||
local observed="$1"
|
||||
node_host_labels
|
||||
printf 'REA installation failed: Node.js %s is unsupported.\n\n' "$observed" >&2
|
||||
printf 'REA requires Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+. Install one via:\n' >&2
|
||||
printf ' nvm: nvm install 24 && nvm use 24 (https://github.com/nvm-sh/nvm)\n' >&2
|
||||
printf ' fnm: fnm install 24 && fnm use 24 (https://github.com/Schniz/fnm)\n' >&2
|
||||
printf ' Direct download for %s/%s:\n' "$node_os_label" "$node_arch_label" >&2
|
||||
printf ' https://nodejs.org/dist/latest-v24.x/ (grab the %s-%s tarball)\n' "$node_os_label" "$node_arch_label" >&2
|
||||
printf 'Then rerun this installer.\n' >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: install.sh [--version VERSION] [--dry-run] [--no-setup] [--no-prompt] [--verbose]
|
||||
|
||||
Installs only the REA CLI. Run `rea setup` separately to configure Hopper and agents.
|
||||
Installs the REA CLI, then starts `rea setup` interactively to configure Hopper
|
||||
and agents when a terminal is available. Pass --no-setup (or --no-prompt) to
|
||||
skip setup and run `rea setup` later.
|
||||
EOF
|
||||
}
|
||||
|
||||
while (($# > 0)); do
|
||||
case "$1" in
|
||||
--version)
|
||||
(($# >= 2)) || fail "--version requires a value."
|
||||
version="$2"
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
dry_run=true
|
||||
shift
|
||||
;;
|
||||
--no-setup|--no-prompt)
|
||||
start_setup=false
|
||||
shift
|
||||
;;
|
||||
--verbose)
|
||||
verbose=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*) fail "unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
main() {
|
||||
while (($# > 0)); do
|
||||
case "$1" in
|
||||
--version)
|
||||
(($# >= 2)) || fail "--version requires a value."
|
||||
version="$2"
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
dry_run=true
|
||||
shift
|
||||
;;
|
||||
--no-setup|--no-prompt)
|
||||
start_setup=false
|
||||
shift
|
||||
;;
|
||||
--verbose)
|
||||
verbose=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*) fail "unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$verbose" == true ]]; then
|
||||
set -x
|
||||
fi
|
||||
if [[ "$verbose" == true ]]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
platform="$(uname -s)"
|
||||
[[ "$platform" == "Darwin" || "$platform" == "Linux" ]] || fail "REA supports macOS and selected 64-bit Linux distributions."
|
||||
command -v curl >/dev/null 2>&1 || fail "curl is required. Install curl, then rerun this installer."
|
||||
command -v node >/dev/null 2>&1 || fail "Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+ is required; install it from https://nodejs.org and retry."
|
||||
command -v npm >/dev/null 2>&1 || fail "npm is required; install it with Node.js and retry."
|
||||
platform="$(uname -s)"
|
||||
[[ "$platform" == "Darwin" || "$platform" == "Linux" ]] || fail "REA supports macOS and selected 64-bit Linux distributions."
|
||||
command -v curl >/dev/null 2>&1 || fail "curl is required. Install curl, then rerun this installer."
|
||||
command -v node >/dev/null 2>&1 || fail "Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+ is required; install it from https://nodejs.org and retry."
|
||||
command -v npm >/dev/null 2>&1 || fail "npm is required; install it with Node.js and retry."
|
||||
|
||||
node_version="$(node -p 'process.versions.node' 2>/dev/null || true)"
|
||||
IFS=. read -r node_major node_minor _node_patch <<<"$node_version"
|
||||
[[ "$node_major" =~ ^[0-9]+$ && "$node_minor" =~ ^[0-9]+$ ]] || fail "the active Node.js version could not be read. Check that node works and is on PATH, then retry."
|
||||
if ! [[ "$_node_patch" =~ ^[0-9]+(\+[0-9A-Za-z.-]+)?$ ]] ||
|
||||
! ((node_major == 22 && node_minor >= 19 || node_major == 24 && node_minor >= 11 || node_major >= 26)); then
|
||||
fail "Node.js $node_version is unsupported; use Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+."
|
||||
fi
|
||||
node_version="$(node -p 'process.versions.node' 2>/dev/null || true)"
|
||||
IFS=. read -r node_major node_minor _node_patch <<<"$node_version"
|
||||
[[ "$node_major" =~ ^[0-9]+$ && "$node_minor" =~ ^[0-9]+$ ]] || fail "the active Node.js version could not be read. Check that node works and is on PATH, then retry."
|
||||
if ! [[ "$_node_patch" =~ ^[0-9]+(\+[0-9A-Za-z.-]+)?$ ]] ||
|
||||
! ((node_major == 22 && node_minor >= 19 || node_major == 24 && node_minor >= 11 || node_major >= 26)); then
|
||||
fail_node_version "$node_version"
|
||||
fi
|
||||
|
||||
if [[ -n "$version" ]]; then
|
||||
[[ "$version" =~ ^${version_pattern}$ ]] || fail "version must be an exact semantic version."
|
||||
else
|
||||
release_json="$(curl -fsSL --proto '=https' --tlsv1.2 --retry 3 -H 'Accept: application/vnd.github+json' "https://api.github.com/repos/$REPOSITORY/releases/latest")" || fail "the latest REA release could not be resolved. Check network access or pass --version VERSION, then retry."
|
||||
tag="$(printf '%s' "$release_json" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{const v=JSON.parse(s).tag_name;if(typeof v!=="string")process.exit(1);process.stdout.write(v)})' 2>/dev/null)" || fail "the release response was invalid. Retry later or pass --version VERSION."
|
||||
[[ "$tag" =~ ^rea-agents-(${version_pattern})$ ]] || fail "the latest release tag was invalid. Retry later or pass --version VERSION."
|
||||
version="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
if [[ -n "$version" ]]; then
|
||||
[[ "$version" =~ ^${version_pattern}$ ]] || fail "version must be an exact semantic version."
|
||||
else
|
||||
release_json="$(curl -fsSL --proto '=https' --tlsv1.2 --retry 3 -H 'Accept: application/vnd.github+json' "https://api.github.com/repos/$REPOSITORY/releases/latest")" || fail "the latest REA release could not be resolved. Check network access or pass --version VERSION, then retry."
|
||||
tag="$(printf '%s' "$release_json" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{const v=JSON.parse(s).tag_name;if(typeof v!=="string")process.exit(1);process.stdout.write(v)})' 2>/dev/null)" || fail "the release response was invalid. Retry later or pass --version VERSION."
|
||||
[[ "$tag" =~ ^rea-agents-(${version_pattern})$ ]] || fail "the latest release tag was invalid. Retry later or pass --version VERSION."
|
||||
version="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
|
||||
# npm's exact-version resolver cleans build metadata from the requested version,
|
||||
# so it may install the base release and make the post-install identity check fail.
|
||||
if [[ "$version" =~ \+ ]]; then
|
||||
fail "npm cannot install an exact version with build metadata. Pass a version without build metadata, then retry."
|
||||
fi
|
||||
# npm's exact-version resolver cleans build metadata from the requested version,
|
||||
# so it may install the base release and make the post-install identity check fail.
|
||||
if [[ "$version" =~ \+ ]]; then
|
||||
fail "npm cannot install an exact version with build metadata. Pass a version without build metadata, then retry."
|
||||
fi
|
||||
|
||||
install_args=(install --global)
|
||||
if [[ "$platform" == "Linux" ]]; then
|
||||
install_args+=(--prefix "$HOME/.local")
|
||||
install_bin="$HOME/.local/bin/rea"
|
||||
else
|
||||
npm_prefix="$(npm prefix --global)" || fail "the npm global prefix could not be read. Repair the npm configuration, then retry."
|
||||
install_bin="$npm_prefix/bin/rea"
|
||||
fi
|
||||
install_args+=("$PACKAGE@$version")
|
||||
install_args=(install --global)
|
||||
if [[ "$platform" == "Linux" ]]; then
|
||||
install_args+=(--prefix "$HOME/.local")
|
||||
install_bin="$HOME/.local/bin/rea"
|
||||
else
|
||||
npm_prefix="$(npm prefix --global)" || fail "the npm global prefix could not be read. Repair the npm configuration, then retry."
|
||||
install_bin="$npm_prefix/bin/rea"
|
||||
fi
|
||||
install_args+=("$PACKAGE@$version")
|
||||
|
||||
printf 'REA install plan\n'
|
||||
printf ' Version: %s\n' "$version"
|
||||
printf ' Runtime: Node.js %s\n' "$node_version"
|
||||
printf ' Command:'
|
||||
printf ' %q' npm "${install_args[@]}"
|
||||
printf '\n'
|
||||
printf ' Binary: %s\n' "$install_bin"
|
||||
printf ' Setup: %s\n' "$([[ "$start_setup" == true ]] && printf 'start when a terminal is available' || printf 'skipped')"
|
||||
printf 'REA install plan\n'
|
||||
printf ' Version: %s\n' "$version"
|
||||
printf ' Runtime: Node.js %s\n' "$node_version"
|
||||
printf ' Command:'
|
||||
printf ' %q' npm "${install_args[@]}"
|
||||
printf '\n'
|
||||
printf ' Binary: %s\n' "$install_bin"
|
||||
printf ' Setup: %s\n' "$([[ "$start_setup" == true ]] && printf 'start when a terminal is available' || printf 'skipped')"
|
||||
|
||||
if [[ "$dry_run" == true ]]; then
|
||||
printf 'Dry run complete; no changes made.\n'
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$dry_run" == true ]]; then
|
||||
printf 'Dry run complete; no changes made.\n'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf 'Installing %s@%s...\n' "$PACKAGE" "$version"
|
||||
npm "${install_args[@]}" || fail "npm could not install REA. Check registry access and npm permissions, then retry."
|
||||
[[ -x "$install_bin" ]] || fail "npm completed without installing the rea command. Check the npm global bin directory and PATH, then retry."
|
||||
installed_version="$("$install_bin" --version 2>/dev/null | tr -d '[:space:]')" || fail "the installed REA version could not be read. Reinstall the requested version, then retry."
|
||||
[[ "$installed_version" == "$version" ]] || fail "installed version $installed_version does not match $version. Reinstall the requested version, then retry."
|
||||
printf 'Installing %s@%s...\n' "$PACKAGE" "$version"
|
||||
npm "${install_args[@]}" || fail "npm could not install REA. Check registry access and npm permissions, then retry."
|
||||
[[ -x "$install_bin" ]] || fail "npm completed without installing the rea command. Check the npm global bin directory and PATH, then retry."
|
||||
installed_version="$("$install_bin" --version 2>/dev/null | tr -d '[:space:]')" || fail "the installed REA version could not be read. Reinstall the requested version, then retry."
|
||||
[[ "$installed_version" == "$version" ]] || fail "installed version $installed_version does not match $version. Reinstall the requested version, then retry."
|
||||
|
||||
printf 'REA %s is installed.\n' "$version"
|
||||
if [[ "$start_setup" == true && -r /dev/tty && -w /dev/tty && -t 1 ]]; then
|
||||
printf 'Starting REA setup...\n'
|
||||
"$install_bin" setup </dev/tty >/dev/tty
|
||||
else
|
||||
printf 'Run %s setup to configure Hopper and agents.\n' "$install_bin"
|
||||
fi
|
||||
printf 'REA %s is installed.\n' "$version"
|
||||
if [[ "$start_setup" == true && -r /dev/tty && -w /dev/tty && -t 1 ]]; then
|
||||
printf 'Starting REA setup...\n'
|
||||
"$install_bin" setup </dev/tty >/dev/tty
|
||||
else
|
||||
printf 'Run %s setup to configure Hopper and agents.\n' "$install_bin"
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
exit $?
|
||||
}
|
||||
|
||||
Generated
+1
-1
@@ -6707,7 +6707,7 @@
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.2.12",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/rolldown/-/rolldown-1.2.12.tgz",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz",
|
||||
"integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
|
||||
+10
-2
@@ -51,10 +51,14 @@
|
||||
"bridge/process/ProcessRunTokenReader.swift",
|
||||
"bridge/process/ProcessRunTokenReaderMain.swift",
|
||||
"bridge/ghidra/ReaGhidraBridge.java",
|
||||
"bridge/ghidra/ReaGhidraBundleActivator.java",
|
||||
"bridge/ghidra/ReaGhidraExtensions.java",
|
||||
"bridge/ghidra/ReaGhidraPrepareCom.java",
|
||||
"bridge/ghidra/ReaGhidraApplySeeds.java",
|
||||
"bridge/ghidra/ReaGhidraAnalysisOptions.java",
|
||||
"bridge/ghidra/ReaGhidraNoReturnFix.java",
|
||||
"bridge/ghidra/ReaGhidraMipsSoftFloat.java",
|
||||
"bridge/ghidra/ReaGhidraPrepareRaw.java",
|
||||
"bridge/android/ReaJadxBridge.java",
|
||||
"bridge/mitmproxy/capture.py",
|
||||
"scripts/rea.mjs",
|
||||
@@ -161,11 +165,14 @@
|
||||
"verify:ghidra": "npm run build:cached && node scripts/verify-real-ghidra.mjs",
|
||||
"verify:ghidra:seeds": "npm run build:cached && node scripts/verify/ghidra/seeds.mjs",
|
||||
"verify:ghidra:mips": "npm run build:cached && node scripts/verify-real-ghidra-mips.mjs",
|
||||
"verify:ghidra:mips-shared": "npm run build:cached && node scripts/verify-real-ghidra-mips-shared.mjs",
|
||||
"verify:ghidra:mips-relocatable": "npm run build:cached && node scripts/verify-real-ghidra-mips-relocatable.mjs",
|
||||
"verify:ghidra:swift": "npm run build:cached && node scripts/verify-real-ghidra-swift.mjs",
|
||||
"verify:ida": "npm run build:cached && node scripts/verify-real-ida.mjs",
|
||||
"verify:ghidra:switch": "npm run build:cached && node scripts/verify-real-ghidra-switch.mjs",
|
||||
"verify:ghidra:dos": "npm run build:cached && node scripts/verify-real-ghidra-dos.mjs",
|
||||
"verify:ghidra:com": "npm run build:cached && node scripts/verify-real-ghidra-com.mjs",
|
||||
"verify:ghidra:raw": "npm run build:cached && node scripts/verify-real-ghidra-raw.mjs",
|
||||
"verify:ghidra:aarch64-jump-table": "npm run build:cached && node scripts/verify-real-ghidra.mjs --aarch64-jump-table",
|
||||
"verify:interface-builder": "npm run build:cached && node scripts/verify/apple/interface-builder.mjs",
|
||||
"verify:ghidra:cross-format": "npm run build:cached && node scripts/verify-real-ghidra.mjs --cross-format",
|
||||
@@ -244,13 +251,14 @@
|
||||
"verify:browser:runtime": "npm run build:cached && node scripts/verify-browser-runtime.mjs",
|
||||
"verify:web:network-captures": "npm run build:cached && node scripts/verify-web-network-captures.mjs",
|
||||
"verify:binary:layout": "npm run build:cached && node scripts/verify-binary-layout.mjs",
|
||||
"verify:go:binary": "npm run build:cached && node scripts/verify-go-binary.mjs",
|
||||
"verify:wasm:package": "npm run build:cached && node scripts/verify-packaged-wasm-artifact.mjs",
|
||||
"verify:wasm:artifact": "npm run build:cached && node scripts/verify-wasm-artifact.mjs",
|
||||
"verify:evm:interface": "npm run build:cached && node scripts/verify-evm-interface.mjs",
|
||||
"verify:recorded:crash": "npm run build:cached && node scripts/verify-recorded-crash.mjs",
|
||||
"mcp-catalog:check:built": "node scripts/generate-mcp-tool-catalog.mjs --check",
|
||||
"compile": "node scripts/clean-build-output.mjs && tsc -p tsconfig.build.json && node scripts/bundle-mcp-runtime.mjs",
|
||||
"compile:termux": "node scripts/clean-build-output.mjs && node --max-old-space-size=3072 node_modules/typescript-termux/lib/tsc.js -p tsconfig.build.json && node scripts/bundle-mcp-runtime.mjs",
|
||||
"compile": "node scripts/clean-build-output.mjs && tsc -p tsconfig.build.json && node scripts/bundle-runtime.mjs",
|
||||
"compile:termux": "node scripts/clean-build-output.mjs && node --max-old-space-size=3072 node_modules/typescript-termux/lib/tsc.js -p tsconfig.build.json && node scripts/bundle-runtime.mjs",
|
||||
"artifacts:mcp-catalog": "node scripts/generate-mcp-tool-catalog.mjs",
|
||||
"artifacts:skills": "node scripts/generate-skill-metadata.mjs",
|
||||
"artifacts:product-catalog": "node scripts/generate-product-catalog.mjs",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const bundleMcpRuntime: (root: string) => Promise<void>;
|
||||
@@ -1,104 +0,0 @@
|
||||
import { parse } from "@babel/parser";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
/**
|
||||
* Reduce filesystem resolution during MCP startup. Keep dependencies external,
|
||||
* dynamic imports lazy, and the original compiled files for CLI/worker entrypoints.
|
||||
*/
|
||||
export const bundleMcpRuntime = async (root) => {
|
||||
const compiledRoot = join(root, "dist");
|
||||
const { rolldown, RUNTIME_MODULE_ID } = await import("rolldown");
|
||||
const { imports = {} } = JSON.parse(
|
||||
await readFile(join(root, "package.json"), "utf8"),
|
||||
);
|
||||
const bundle = await rolldown({
|
||||
input: { main: join(compiledRoot, "main.js") },
|
||||
platform: "node",
|
||||
treeshake: false,
|
||||
external: (source) => !source.startsWith(".") && !isAbsolute(source),
|
||||
plugins: [
|
||||
{
|
||||
name: "preserve-compiled-module-locations",
|
||||
transform(code, id) {
|
||||
if (id === RUNTIME_MODULE_ID) return null;
|
||||
if (!code.includes("import.meta")) return null;
|
||||
const sourcePath = relative(compiledRoot, id);
|
||||
if (
|
||||
isAbsolute(sourcePath) ||
|
||||
sourcePath === ".." ||
|
||||
sourcePath.startsWith(`..${sep}`)
|
||||
)
|
||||
throw new Error(`MCP bundle input is outside dist: ${id}`);
|
||||
// All chunks are directly under dist/mcp. Preserve the original
|
||||
// module URL, including URL escaping, for bridges and createRequire.
|
||||
const moduleUrl = `new URL(${JSON.stringify(`../${sourcePath.split(sep).map(encodeURIComponent).join("/")}`)}, import.meta.url).href`;
|
||||
const edits = [];
|
||||
const visit = (node) => {
|
||||
if (node === null || typeof node !== "object") return;
|
||||
const isMeta = (value) =>
|
||||
value?.type === "MetaProperty" &&
|
||||
value.meta.name === "import" &&
|
||||
value.property.name === "meta";
|
||||
if (
|
||||
node.type === "CallExpression" &&
|
||||
node.callee.type === "MemberExpression" &&
|
||||
!node.callee.computed &&
|
||||
isMeta(node.callee.object) &&
|
||||
node.callee.property.name === "resolve" &&
|
||||
node.arguments.length === 1 &&
|
||||
node.arguments[0].type === "StringLiteral" &&
|
||||
Object.hasOwn(imports, node.arguments[0].value)
|
||||
)
|
||||
// Package imports resolve in the same package scope after bundling.
|
||||
return;
|
||||
if (
|
||||
node.type === "MemberExpression" &&
|
||||
!node.computed &&
|
||||
isMeta(node.object) &&
|
||||
node.property.name === "url"
|
||||
) {
|
||||
edits.push({ start: node.start, end: node.end });
|
||||
return;
|
||||
}
|
||||
if (isMeta(node))
|
||||
throw new Error(
|
||||
`Unsupported import.meta use in MCP bundle: ${id}`,
|
||||
);
|
||||
for (const value of Object.values(node)) {
|
||||
if (Array.isArray(value)) value.forEach(visit);
|
||||
else visit(value);
|
||||
}
|
||||
};
|
||||
visit(parse(code, { sourceType: "module" }).program);
|
||||
for (const { start, end } of edits.sort((a, b) => b.start - a.start))
|
||||
code = code.slice(0, start) + moduleUrl + code.slice(end);
|
||||
return { code, map: null };
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
try {
|
||||
await bundle.write({
|
||||
dir: join(compiledRoot, "mcp"),
|
||||
format: "esm",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-[hash].js",
|
||||
codeSplitting: true,
|
||||
strictExecutionOrder: true,
|
||||
minify: false,
|
||||
sourcemap: false,
|
||||
});
|
||||
} finally {
|
||||
await bundle.close();
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
process.argv[1] !== undefined &&
|
||||
pathToFileURL(resolve(process.argv[1])).href === import.meta.url
|
||||
)
|
||||
await bundleMcpRuntime(
|
||||
resolve(dirname(fileURLToPath(import.meta.url)), ".."),
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export const bundleRuntime: (root: string) => Promise<void>;
|
||||
@@ -0,0 +1,140 @@
|
||||
import { parse } from "@babel/parser";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
/**
|
||||
* Reduce filesystem resolution during CLI and MCP startup. Keep dependencies
|
||||
* external, dynamic imports lazy, and original compiled worker entrypoints.
|
||||
*/
|
||||
export const bundleRuntime = async (root) => {
|
||||
const compiledRoot = resolve(root, "dist");
|
||||
// CLI, MCP, workers and updater fixtures share one release-identity owner.
|
||||
const metadataModule = join(compiledRoot, "generatedPackageMetadata.js");
|
||||
const isMetadataModule = (id) =>
|
||||
isAbsolute(id) && resolve(id) === metadataModule;
|
||||
const { rolldown, RUNTIME_MODULE_ID } = await import("rolldown");
|
||||
const { imports = {} } = JSON.parse(
|
||||
await readFile(join(root, "package.json"), "utf8"),
|
||||
);
|
||||
for (const [directory, entries] of [
|
||||
["mcp", { main: "main.js" }],
|
||||
[
|
||||
"cli-runtime",
|
||||
{
|
||||
cli: "cli.js",
|
||||
cliOutput: "cliOutput.js",
|
||||
streamedJsonOutput: "cli/streamedJsonOutput.js",
|
||||
mcpDoctor: "mcpDoctor.js",
|
||||
},
|
||||
],
|
||||
]) {
|
||||
const bundle = await rolldown({
|
||||
input: Object.fromEntries(
|
||||
Object.entries(entries).map(([name, path]) => [
|
||||
name,
|
||||
join(compiledRoot, path),
|
||||
]),
|
||||
),
|
||||
platform: "node",
|
||||
treeshake: false,
|
||||
external: (source) =>
|
||||
isMetadataModule(source) ||
|
||||
(!source.startsWith(".") && !isAbsolute(source)),
|
||||
plugins: [
|
||||
{
|
||||
name: "preserve-compiled-module-locations",
|
||||
transform(code, id) {
|
||||
if (id === RUNTIME_MODULE_ID) return null;
|
||||
if (!code.includes("import.meta")) return null;
|
||||
const sourcePath = relative(compiledRoot, id);
|
||||
if (
|
||||
isAbsolute(sourcePath) ||
|
||||
sourcePath === ".." ||
|
||||
sourcePath.startsWith(`..${sep}`)
|
||||
)
|
||||
throw new Error(`Runtime bundle input is outside dist: ${id}`);
|
||||
// Each output directory is directly under dist. Preserve the original
|
||||
// module URL, including URL escaping, for bridges and createRequire.
|
||||
const moduleUrl = `new URL(${JSON.stringify(`../${sourcePath.split(sep).map(encodeURIComponent).join("/")}`)}, import.meta.url).href`;
|
||||
const edits = [];
|
||||
const visit = (node) => {
|
||||
if (node === null || typeof node !== "object") return;
|
||||
const isMeta = (value) =>
|
||||
value?.type === "MetaProperty" &&
|
||||
value.meta.name === "import" &&
|
||||
value.property.name === "meta";
|
||||
if (
|
||||
node.type === "CallExpression" &&
|
||||
node.callee.type === "MemberExpression" &&
|
||||
!node.callee.computed &&
|
||||
isMeta(node.callee.object) &&
|
||||
node.callee.property.name === "resolve" &&
|
||||
node.arguments.length === 1 &&
|
||||
node.arguments[0].type === "StringLiteral" &&
|
||||
Object.hasOwn(imports, node.arguments[0].value)
|
||||
)
|
||||
// Package imports resolve in the same package scope after bundling.
|
||||
return;
|
||||
if (
|
||||
node.type === "MemberExpression" &&
|
||||
!node.computed &&
|
||||
isMeta(node.object) &&
|
||||
node.property.name === "url"
|
||||
) {
|
||||
edits.push({ start: node.start, end: node.end });
|
||||
return;
|
||||
}
|
||||
if (isMeta(node))
|
||||
throw new Error(
|
||||
`Unsupported import.meta use in runtime bundle: ${id}`,
|
||||
);
|
||||
for (const value of Object.values(node)) {
|
||||
if (Array.isArray(value)) value.forEach(visit);
|
||||
else visit(value);
|
||||
}
|
||||
};
|
||||
visit(parse(code, { sourceType: "module" }).program);
|
||||
for (const { start, end } of edits.sort(
|
||||
(a, b) => b.start - a.start,
|
||||
))
|
||||
code = code.slice(0, start) + moduleUrl + code.slice(end);
|
||||
return { code, map: null };
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
try {
|
||||
await bundle.write({
|
||||
dir: join(compiledRoot, directory),
|
||||
format: "esm",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames:
|
||||
directory === "mcp"
|
||||
? "[name]-[hash].js"
|
||||
: (chunk) => {
|
||||
// Keep lazy facades distinguishable when source basenames repeat.
|
||||
if (typeof chunk.facadeModuleId !== "string")
|
||||
return "[name]-[hash].js";
|
||||
const path = relative(compiledRoot, chunk.facadeModuleId);
|
||||
return `${path.replace(/\.js$/u, "").split(sep).join("__")}-[hash].js`;
|
||||
},
|
||||
paths: (id) =>
|
||||
isMetadataModule(id) ? "../generatedPackageMetadata.js" : id,
|
||||
minifyInternalExports: directory === "mcp",
|
||||
codeSplitting: true,
|
||||
strictExecutionOrder: true,
|
||||
minify: false,
|
||||
sourcemap: false,
|
||||
});
|
||||
} finally {
|
||||
await bundle.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
process.argv[1] !== undefined &&
|
||||
pathToFileURL(resolve(process.argv[1])).href === import.meta.url
|
||||
)
|
||||
await bundleRuntime(resolve(dirname(fileURLToPath(import.meta.url)), ".."));
|
||||
@@ -150,7 +150,16 @@ async function verifyCase(executable, entrypoint, scenario) {
|
||||
phase = "public-map-assertions";
|
||||
observed = item?.mappings;
|
||||
assert.equal(item.status, "included");
|
||||
assert.deepEqual(item.mappings, expected);
|
||||
assert.equal(item.mappings.length, expected.length);
|
||||
for (const [index, coordinates] of expected.entries()) {
|
||||
const mapping = item.mappings[index];
|
||||
for (const [key, value] of Object.entries(coordinates))
|
||||
assert.deepEqual(mapping[key], value);
|
||||
// This compiler fixture has one source declaration in one wrapped leaf.
|
||||
assert.equal(mapping.source_index, 0);
|
||||
assert.equal(mapping.declaration_index, 0);
|
||||
}
|
||||
assert.equal(item.original_sources.length, 1);
|
||||
assert.equal(item.artifact.sha256, sha(site.map));
|
||||
assert.equal(item.artifact.bytes, Buffer.byteLength(site.map));
|
||||
assert.equal(item.original_sources[0].artifact.sha256, sha(site.original));
|
||||
|
||||
@@ -27,6 +27,7 @@ export const toolFamilyCatalog = (sources) => {
|
||||
contracts: [
|
||||
...sources.artifactContracts.ARTIFACT_TOOL_CONTRACTS,
|
||||
...sources.evmContracts.EVM_TOOL_CONTRACTS,
|
||||
...sources.goContracts.GO_TOOL_CONTRACTS,
|
||||
...sources.wasmContracts.WASM_TOOL_CONTRACTS,
|
||||
],
|
||||
},
|
||||
@@ -188,6 +189,10 @@ export const providerCatalog = (sources) => {
|
||||
identity: sources.evmProvider.EVMOLE_PROVIDER_IDENTITY,
|
||||
contracts: sources.evmContracts.EVM_TOOL_CONTRACTS,
|
||||
},
|
||||
{
|
||||
identity: sources.goProvider.GO_BINARY_PROVIDER_IDENTITY,
|
||||
contracts: sources.goContracts.GO_TOOL_CONTRACTS,
|
||||
},
|
||||
{
|
||||
identity: sources.artifactProviders.ARTIFACT_GRAPH_PROVIDER,
|
||||
contracts: sources.artifactContracts.ARTIFACT_TOOL_CONTRACTS,
|
||||
|
||||
@@ -49,6 +49,8 @@ const SOURCE_PATHS = {
|
||||
wasmProvider: "dist/wasm/WabtRelease.js",
|
||||
evmContracts: "dist/contracts/evm/evmToolContracts.js",
|
||||
evmProvider: "dist/evm/EvmoleRelease.js",
|
||||
goContracts: "dist/contracts/go/goToolContracts.js",
|
||||
goProvider: "dist/go/GoBinaryProvider.js",
|
||||
artifactContracts: "dist/contracts/artifactToolContracts.js",
|
||||
managedContracts: "dist/contracts/managed/managedToolContracts.js",
|
||||
firmwareContracts: "dist/contracts/firmware/firmwareToolContracts.js",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { analysisErrorProjectionSchema } from "../../dist/contracts/errorSchemas.js";
|
||||
import { toolContract } from "../../dist/contracts/toolContracts.js";
|
||||
import {
|
||||
expandProviderCapabilities,
|
||||
expandToolAvailability,
|
||||
providerCapabilitySet,
|
||||
} from "../../dist/contracts/toolOutputSchemaPrimitives.js";
|
||||
import { compareUnicodeCodePoints } from "../../dist/domain/unicodeCodePointOrder.js";
|
||||
|
||||
/** Read the textual JSON projection from one MCP tool call. */
|
||||
export const mcpTextValue = (result) => {
|
||||
@@ -10,6 +16,10 @@ export const mcpTextValue = (result) => {
|
||||
|
||||
const jsonValue = (result) => JSON.parse(mcpTextValue(result));
|
||||
|
||||
/** Sort canonical contract names using REA's code-point ordering. */
|
||||
export const sortVerifierNames = (names) =>
|
||||
[...names].sort(compareUnicodeCodePoints);
|
||||
|
||||
/** Read the complete canonical diagnostic from an MCP tool failure. */
|
||||
export const requireMcpToolError = (result) => {
|
||||
if (result.isError !== true) throw new Error("Expected an MCP tool failure");
|
||||
@@ -35,6 +45,44 @@ const operationResultKey = (contract) => {
|
||||
return evidence ? "normalized_result" : "result";
|
||||
};
|
||||
|
||||
/** Expand compact session metadata once at the verifier boundary. */
|
||||
export const expandMcpSessionStatus = (status) => ({
|
||||
...status,
|
||||
capabilities: expandProviderCapabilities(status.capabilities),
|
||||
analysis_provider_candidates: expandProviderCandidates(
|
||||
status.analysis_provider_candidates,
|
||||
),
|
||||
tool_availability: expandToolAvailability(status.tool_availability),
|
||||
});
|
||||
|
||||
/** Expand provider capability groups while retaining candidate identity/state. */
|
||||
export const expandProviderCandidates = (candidates) =>
|
||||
candidates.map((candidate) => ({
|
||||
...candidate,
|
||||
capabilities: expandProviderCapabilities(
|
||||
providerCapabilitySet.array().parse(candidate.capabilities),
|
||||
),
|
||||
}));
|
||||
|
||||
/** Expand CLI provider capability groups at the same owning boundary. */
|
||||
export const expandProviderOutput = (providers) => ({
|
||||
...providers,
|
||||
capabilities: expandProviderCapabilities(
|
||||
providerCapabilitySet.array().parse(providers.capabilities),
|
||||
),
|
||||
analysis_provider_candidates: expandProviderCandidates(
|
||||
providers.analysis_provider_candidates,
|
||||
),
|
||||
});
|
||||
|
||||
/** Expand the CLI capabilities command's compact provider descriptor groups. */
|
||||
export const expandCapabilitiesOutput = (capabilities) => ({
|
||||
...capabilities,
|
||||
capabilities: expandProviderCapabilities(
|
||||
providerCapabilitySet.array().parse(capabilities.capabilities),
|
||||
),
|
||||
});
|
||||
|
||||
/** Validate an Evidence-producing tool output and return its normalized result. */
|
||||
export const requireMcpEvidenceResult = (result, operation) => {
|
||||
const contract = toolContract(operation);
|
||||
@@ -48,7 +96,10 @@ export const requireMcpLifecycleResult = (result, operation) => {
|
||||
const contract = toolContract(operation);
|
||||
if (operationResultKey(contract) !== "result")
|
||||
throw new Error(`${operation} requires the Evidence result extractor`);
|
||||
return requireOutput(result, operation, contract).result;
|
||||
const projected = requireOutput(result, operation, contract).result;
|
||||
return operation === "binary_session"
|
||||
? expandMcpSessionStatus(projected)
|
||||
: projected;
|
||||
};
|
||||
|
||||
/** Project a mixed verifier call using its exact named canonical output schema. */
|
||||
@@ -58,7 +109,10 @@ export const requireMcpOperationResult = (
|
||||
contract = toolContract(operation),
|
||||
) => {
|
||||
const key = operationResultKey(contract);
|
||||
return requireOutput(result, operation, contract)[key];
|
||||
const projected = requireOutput(result, operation, contract)[key];
|
||||
return operation === "binary_session"
|
||||
? expandMcpSessionStatus(projected)
|
||||
: projected;
|
||||
};
|
||||
|
||||
/** Verify provider identity directly from the tool result. */
|
||||
|
||||
@@ -6,6 +6,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
const PURE_LAYERS = new Set(["domain", "contracts"]);
|
||||
const MIGRATED_PROVIDER_ROOTS = new Set([
|
||||
"evm",
|
||||
"go",
|
||||
"wasm",
|
||||
"android",
|
||||
"firmware",
|
||||
|
||||
@@ -11,6 +11,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseEvidence } from "../../dist/domain/evidence.js";
|
||||
import { analysisProfileSchema } from "../../dist/domain/analysisProfile.js";
|
||||
import { nativeValueTraceSchema } from "../../dist/domain/native/nativeValueTrace.js";
|
||||
import { requireMcpLifecycleResult } from "./mcp-verifier-results.mjs";
|
||||
import { verifyGhidraBoundaries } from "./real-ghidra-boundaries.mjs";
|
||||
|
||||
const defaultEntrypoint = fileURLToPath(new URL("../rea.mjs", import.meta.url));
|
||||
@@ -172,13 +173,16 @@ async function verifyNativeValueAdapters(
|
||||
{ timeout: 180000 },
|
||||
);
|
||||
assert.notEqual(opened.isError, true, JSON.stringify(opened));
|
||||
const inventory = await client.callTool({
|
||||
const inventoryReply = await client.callTool({
|
||||
name: "binary_session",
|
||||
arguments: {},
|
||||
});
|
||||
assert.notEqual(inventory.isError, true, JSON.stringify(inventory));
|
||||
const inventory = requireMcpLifecycleResult(
|
||||
inventoryReply,
|
||||
"binary_session",
|
||||
);
|
||||
assert.ok(
|
||||
inventory.structuredContent?.result?.tool_availability.some(
|
||||
inventory.tool_availability.some(
|
||||
(tool) => tool.name === "trace_native_values" && tool.available,
|
||||
),
|
||||
"Native value tracing is unavailable in real provider discovery",
|
||||
@@ -267,11 +271,14 @@ async function verifyNativeValueAdapters(
|
||||
arguments: {},
|
||||
});
|
||||
assert.notEqual(closed.isError, true, JSON.stringify(closed));
|
||||
const status = await client.callTool({
|
||||
const statusReply = await client.callTool({
|
||||
name: "binary_session",
|
||||
arguments: {},
|
||||
});
|
||||
assert.equal(status.structuredContent?.result?.open, false);
|
||||
assert.equal(
|
||||
requireMcpLifecycleResult(statusReply, "binary_session").open,
|
||||
false,
|
||||
);
|
||||
assert.deepEqual(
|
||||
(await readdir(env.TMPDIR)).filter((name) =>
|
||||
name.startsWith("rea-ghidra-"),
|
||||
|
||||
@@ -13,8 +13,6 @@ import { promisify } from "node:util";
|
||||
import Ajv from "ajv/dist/2020.js";
|
||||
import addFormats from "ajv-formats";
|
||||
|
||||
import { analysisErrorProjectionSchema } from "../../dist/contracts/errorSchemas.js";
|
||||
|
||||
import { verifyLegacyGhidraReferenceSnapshot } from "./ghidra-reference-snapshot-e2e.mjs";
|
||||
import { verifyGhidraSnapshotLifecycle } from "./real-ghidra-snapshot-lifecycle.mjs";
|
||||
import { verifyGhidraTargetAdmission } from "./real-ghidra-target-admission.mjs";
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { requireFunctionDossierOracle } from "../../dist/application/RealHopperAssertions.js";
|
||||
|
||||
/**
|
||||
* `requireFunctionDossierOracle` is only used inside `verifyRealHopperFixture`,
|
||||
* which runs against the real Hopper bridge after `build:cached`. Loading it
|
||||
* lazily keeps this module importable from `test:fast` (which docs promise to
|
||||
* run "without build" — see docs/testing.md § test scripts) so that mock-only
|
||||
* callers such as `requireCurrentDocument` and `resolveFixtureProcedure` do
|
||||
* not require `dist/`.
|
||||
*/
|
||||
const loadFunctionDossierOracle = () =>
|
||||
import("../../dist/application/RealHopperAssertions.js").then(
|
||||
(module) => module.requireFunctionDossierOracle,
|
||||
);
|
||||
|
||||
/** Require truthful tri-state permissions from real Hopper memory regions. */
|
||||
export const requireTruthfulMemoryRegions = (segments) => {
|
||||
@@ -87,6 +99,7 @@ export async function verifyRealHopperFixture({
|
||||
oracle,
|
||||
normalizedResult,
|
||||
}) {
|
||||
const requireFunctionDossierOracle = await loadFunctionDossierOracle();
|
||||
const main = await resolveFixtureProcedure(
|
||||
client,
|
||||
options,
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function verifyHopperWorkflows(call, invalid, procedure) {
|
||||
);
|
||||
assert.equal(cycle.length, 2, "fixture omitted its source-owned call cycle");
|
||||
const cycleGraph = await call("get_call_graph", { address: cycle[0].value });
|
||||
const cycleNodes = Object.values(cycleGraph).flat();
|
||||
const cycleNodes = Object.values(cycleGraph.graph).flat();
|
||||
assert.equal(cycleNodes.length, 2);
|
||||
assert.deepEqual(
|
||||
cycleNodes.map((node) => node.address).sort(),
|
||||
|
||||
@@ -3,6 +3,17 @@ import { lstat } from "node:fs/promises";
|
||||
import { promisify } from "node:util";
|
||||
import { Ajv2020 } from "ajv/dist/2020.js";
|
||||
|
||||
/**
|
||||
* `mcp-verifier-results.mjs` reaches into `dist/` at module load, so eager
|
||||
* importing it propagates that build dependency to every consumer in this
|
||||
* file. `verifyCompleteToolCatalog` is the only consumer, so we lazy-load
|
||||
* the module there and keep the other helpers (`exec`, `run`, `json`,
|
||||
* `pathExists`) usable from `test:fast` callers that only need `dist/`-free
|
||||
* utilities — see docs/testing.md § test scripts, which promises `test:fast`
|
||||
* runs "without build".
|
||||
*/
|
||||
const loadMcpVerifierResults = () => import("./mcp-verifier-results.mjs");
|
||||
|
||||
/** Promisified child_process execution helper. */
|
||||
export const exec = promisify(execFile);
|
||||
|
||||
@@ -31,6 +42,8 @@ export const json = (text) => JSON.parse(text);
|
||||
|
||||
/** Verify schema validity and that the MCP catalog matches session metadata. */
|
||||
export const verifyCompleteToolCatalog = async (client, options) => {
|
||||
const { requireMcpLifecycleResult, sortVerifierNames } =
|
||||
await loadMcpVerifierResults();
|
||||
const listed = await client.listTools(undefined, options);
|
||||
const ajv = new Ajv2020({ strict: false, validateFormats: false });
|
||||
for (const tool of listed.tools) {
|
||||
@@ -46,11 +59,10 @@ export const verifyCompleteToolCatalog = async (client, options) => {
|
||||
{ name: "binary_session", arguments: {} },
|
||||
options,
|
||||
);
|
||||
const availability = status.structuredContent?.result?.tool_availability;
|
||||
if (!Array.isArray(availability))
|
||||
throw new Error("packaged MCP omitted tool availability");
|
||||
const expected = availability.map(({ name }) => name).sort();
|
||||
const observed = listed.tools.map(({ name }) => name).sort();
|
||||
const expanded = requireMcpLifecycleResult(status, "binary_session");
|
||||
const availability = expanded.tool_availability;
|
||||
const expected = sortVerifierNames(availability.map(({ name }) => name));
|
||||
const observed = sortVerifierNames(listed.tools.map(({ name }) => name));
|
||||
if (JSON.stringify(observed) !== JSON.stringify(expected))
|
||||
throw new Error(
|
||||
"packaged MCP tool inventory diverged from session metadata",
|
||||
|
||||
@@ -47,6 +47,10 @@ if (await pathExists(join(root, "src"))) {
|
||||
for (const path of [
|
||||
"dist/main.js",
|
||||
"dist/mcp/main.js",
|
||||
"dist/cli-runtime/cli.js",
|
||||
"dist/cli-runtime/cliOutput.js",
|
||||
"dist/cli-runtime/streamedJsonOutput.js",
|
||||
"dist/cli-runtime/mcpDoctor.js",
|
||||
"dist/mcpDoctor.js",
|
||||
"dist/cli.js",
|
||||
"dist/cliOutput.js",
|
||||
|
||||
+11
-6
@@ -33,8 +33,12 @@ if (
|
||||
const runtimeFiles = isMcpMode
|
||||
? ["dist/mcp/main.js"]
|
||||
: isMcpDoctorMode
|
||||
? ["dist/mcp/main.js", "dist/mcpDoctor.js"]
|
||||
: ["dist/cli.js", "dist/cliOutput.js", "dist/cli/streamedJsonOutput.js"];
|
||||
? ["dist/mcp/main.js", "dist/cli-runtime/mcpDoctor.js"]
|
||||
: [
|
||||
"dist/cli-runtime/cli.js",
|
||||
"dist/cli-runtime/cliOutput.js",
|
||||
"dist/cli-runtime/streamedJsonOutput.js",
|
||||
];
|
||||
|
||||
if (!(await compiledRuntimeExists(runtimeFiles))) {
|
||||
process.stderr.write(
|
||||
@@ -45,7 +49,8 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
|
||||
const { runEntrypoint } = await import("../dist/mcp/main.js");
|
||||
await runEntrypoint();
|
||||
} else if (isMcpDoctorMode) {
|
||||
const { runProductionMcpDoctorCli } = await import("../dist/mcpDoctor.js");
|
||||
const { runProductionMcpDoctorCli } =
|
||||
await import("../dist/cli-runtime/mcpDoctor.js");
|
||||
const result = await runProductionMcpDoctorCli(args.slice(2), {
|
||||
dispatcherPath: fileURLToPath(import.meta.url),
|
||||
packageRoot,
|
||||
@@ -53,14 +58,14 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
|
||||
process.stdout.write(result.output);
|
||||
process.exitCode = result.exitCode;
|
||||
} else {
|
||||
const { createCli } = await import("../dist/cli.js");
|
||||
const { createCli } = await import("../dist/cli-runtime/cli.js");
|
||||
const { createStreamedCliJsonOutput } =
|
||||
await import("../dist/cli/streamedJsonOutput.js");
|
||||
await import("../dist/cli-runtime/streamedJsonOutput.js");
|
||||
const {
|
||||
renderCliOutputArgumentError,
|
||||
renderEmptyFilteredCliOutput,
|
||||
validateCliOutputArguments,
|
||||
} = await import("../dist/cliOutput.js");
|
||||
} = await import("../dist/cli-runtime/cliOutput.js");
|
||||
const outputArguments = validateCliOutputArguments(args);
|
||||
if (!outputArguments.ok) {
|
||||
process.stdout.write(renderCliOutputArgumentError(outputArguments));
|
||||
|
||||
@@ -401,6 +401,22 @@ class WebsiteTranslationChecks(unittest.TestCase):
|
||||
server.shutdown()
|
||||
worker.join()
|
||||
|
||||
def test_every_reading_page_offers_light_and_dark_theme_colors(self):
|
||||
site = ROOT / "website/_site"
|
||||
english_pages = reading_pages(ROOT / "website/public")
|
||||
self.assertTrue(english_pages)
|
||||
for language in languages(ROOT):
|
||||
code = language["code"]
|
||||
for original in english_pages:
|
||||
relative = original.relative_to(ROOT / "website/public")
|
||||
for path in {original, site / relative if code == "en" else site / code / relative}:
|
||||
with self.subTest(language=code, page=str(path.relative_to(ROOT))):
|
||||
doc = Document(path.read_text())
|
||||
media = sorted(node.attrs.get("media") for node in doc.nodes if node.tag == "meta" and node.attrs.get("name") == "theme-color")
|
||||
self.assertEqual(media, ["(prefers-color-scheme: dark)", "(prefers-color-scheme: light)"])
|
||||
styles = (ROOT / "website/public/assets/styles.css").read_text()
|
||||
self.assertTrue("@media (prefers-color-scheme: dark)" in styles, "styles.css has no dark color scheme block")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
+80
-67
@@ -1,7 +1,5 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdtemp, readFile, rm, stat } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { readFile, stat } from "node:fs/promises";
|
||||
|
||||
import { ApktoolResourceAnalysisService } from "../dist/application/apktool/ApktoolResourceAnalysisService.js";
|
||||
import { createApktoolResourceAnalysisProvider } from "../dist/composition/apktool.js";
|
||||
@@ -11,8 +9,8 @@ if (options.help) {
|
||||
process.stdout.write(
|
||||
"Usage: npm run verify:apktool [-- --apk PATH]\n" +
|
||||
"Requires a caller-selected apktool launcher (REA_APKTOOL_COMMAND or\n" +
|
||||
"PATH) and a Java runtime. Without --apk, a small signed probe APK is\n" +
|
||||
"expected at the path given by --apk; nothing is downloaded or built.\n",
|
||||
"PATH) and a Java runtime. Without --apk, only client availability is\n" +
|
||||
"checked. Supply a local APK to verify decoding; nothing is downloaded or built.\n",
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -38,70 +36,80 @@ const run = async (operation, input) => {
|
||||
return result.value.normalized_result;
|
||||
};
|
||||
|
||||
const client = await run("inspect_apktool_client", {});
|
||||
if (client !== null) {
|
||||
check(
|
||||
"apktool launcher reports a version",
|
||||
typeof client.client.apktool_version === "string" &&
|
||||
client.client.apktool_version !== "",
|
||||
`command=${client.client.command} version=${client.client.apktool_version}`,
|
||||
);
|
||||
}
|
||||
try {
|
||||
const client = await run("inspect_apktool_client", {});
|
||||
if (client !== null) {
|
||||
check(
|
||||
"apktool launcher reports a version",
|
||||
typeof client.client.apktool_version === "string" &&
|
||||
client.client.apktool_version !== "",
|
||||
`command=${client.client.command} version=${client.client.apktool_version}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (options.apk !== null) {
|
||||
const decoded = await run("decode_android_resources", {
|
||||
path: options.apk,
|
||||
include_strings: true,
|
||||
});
|
||||
if (decoded !== null) {
|
||||
const bytes = await stat(options.apk).then((info) => info.size);
|
||||
const digest = createHash("sha256")
|
||||
.update(await readFile(options.apk))
|
||||
.digest("hex");
|
||||
check(
|
||||
"decoded target identity matches the file on disk",
|
||||
decoded.target.bytes === bytes && decoded.target.sha256 === digest,
|
||||
`${bytes} bytes`,
|
||||
);
|
||||
check(
|
||||
"metadata carries version and SDK facts",
|
||||
typeof decoded.metadata.version_name === "string" &&
|
||||
decoded.metadata.min_sdk_version !== null &&
|
||||
decoded.metadata.target_sdk_version !== null,
|
||||
`version=${String(decoded.metadata.version_name)} sdk=${String(decoded.metadata.min_sdk_version)}..${String(decoded.metadata.target_sdk_version)}`,
|
||||
);
|
||||
check(
|
||||
"decoded manifest names the package",
|
||||
typeof decoded.metadata.package_name === "string" &&
|
||||
decoded.manifest !== null &&
|
||||
decoded.manifest.includes(decoded.metadata.package_name),
|
||||
String(decoded.metadata.package_name),
|
||||
);
|
||||
check(
|
||||
"string resources projected",
|
||||
decoded.strings.length > 0,
|
||||
`${decoded.strings.length} string(s)`,
|
||||
);
|
||||
if (decoded.locales.length > 0) {
|
||||
const locale = await run("decode_android_resources", {
|
||||
path: options.apk,
|
||||
include_strings: true,
|
||||
locale: decoded.locales[0],
|
||||
});
|
||||
if (locale !== null)
|
||||
check(
|
||||
`locale table projected for ${String(decoded.locales[0])}`,
|
||||
Array.isArray(locale.strings),
|
||||
`${locale.strings.length} string(s)`,
|
||||
);
|
||||
if (options.apk !== null) {
|
||||
const decoded = await run("decode_android_resources", {
|
||||
path: options.apk,
|
||||
include_strings: true,
|
||||
});
|
||||
if (decoded !== null) {
|
||||
const bytes = await stat(options.apk).then((info) => info.size);
|
||||
const digest = createHash("sha256")
|
||||
.update(await readFile(options.apk))
|
||||
.digest("hex");
|
||||
check(
|
||||
"decoded target identity matches the file on disk",
|
||||
decoded.target.bytes === bytes && decoded.target.sha256 === digest,
|
||||
`${bytes} bytes`,
|
||||
);
|
||||
check(
|
||||
"metadata carries version and SDK facts",
|
||||
typeof decoded.metadata.version_name === "string" &&
|
||||
decoded.metadata.min_sdk_version !== null &&
|
||||
decoded.metadata.target_sdk_version !== null,
|
||||
`version=${String(decoded.metadata.version_name)} sdk=${String(decoded.metadata.min_sdk_version)}..${String(decoded.metadata.target_sdk_version)}`,
|
||||
);
|
||||
check(
|
||||
"decoded manifest names the package",
|
||||
typeof decoded.metadata.package_name === "string" &&
|
||||
decoded.manifest !== null &&
|
||||
decoded.manifest.includes(decoded.metadata.package_name),
|
||||
String(decoded.metadata.package_name),
|
||||
);
|
||||
check(
|
||||
"string resources projected",
|
||||
decoded.strings.length > 0,
|
||||
`${decoded.strings.length} string(s)`,
|
||||
);
|
||||
if (decoded.locales.length > 0) {
|
||||
const locale = await run("decode_android_resources", {
|
||||
path: options.apk,
|
||||
include_strings: true,
|
||||
locale: decoded.locales[0],
|
||||
});
|
||||
if (locale !== null)
|
||||
check(
|
||||
`locale table projected for ${String(decoded.locales[0])}`,
|
||||
Array.isArray(locale.strings),
|
||||
`${locale.strings.length} string(s)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await provider.close();
|
||||
} catch {
|
||||
// The provider owns per-call workspaces only; closing is best-effort.
|
||||
} catch (cause) {
|
||||
failures += 1;
|
||||
process.stdout.write(
|
||||
`[FAIL] verifier execution — ${cause instanceof Error ? cause.message : String(cause)}\n`,
|
||||
);
|
||||
} finally {
|
||||
try {
|
||||
await provider.close();
|
||||
} catch (cause) {
|
||||
failures += 1;
|
||||
process.stdout.write(
|
||||
`[FAIL] provider cleanup — ${cause instanceof Error ? cause.message : String(cause)}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (failures > 0) {
|
||||
@@ -116,7 +124,12 @@ function parseArguments(argv) {
|
||||
const argument = argv[index];
|
||||
if (argument === "--help" || argument === "-h") parsed.help = true;
|
||||
else if (argument === "--apk") {
|
||||
parsed.apk = argv[index + 1] ?? null;
|
||||
const path = argv[index + 1];
|
||||
if (path === undefined) {
|
||||
process.stderr.write("--apk requires a local APK path\n");
|
||||
process.exit(2);
|
||||
}
|
||||
parsed.apk = path;
|
||||
index += 1;
|
||||
} else {
|
||||
process.stderr.write(`unknown argument: ${argument}\n`);
|
||||
|
||||
@@ -662,6 +662,16 @@ try {
|
||||
bytes.writeBigUInt64LE(8n, header + 56);
|
||||
return [`undersized-entry-${type}`, bytes, "invalid_input"];
|
||||
});
|
||||
const nonintegralSymbolEntries = ["SHT_SYMTAB", "SHT_DYNSYM"].map((type) => {
|
||||
const section = protectedReport.sections.find((item) => item.type === type);
|
||||
assert.notEqual(section, undefined);
|
||||
const bytes = Buffer.from(sectionBearingBytes);
|
||||
const header = Number(BigInt(section.header_location.offset));
|
||||
const size = bytes.readBigUInt64LE(header + 32);
|
||||
assert.ok(size + 1n <= BigInt(bytes.length) - BigInt(section.offset));
|
||||
bytes.writeBigUInt64LE(size + 1n, header + 32);
|
||||
return [`nonintegral-entry-count-${type}`, bytes, "invalid_input"];
|
||||
});
|
||||
const relocationSection = relocatable.sections.find(
|
||||
(item) => item.type === "SHT_RELA",
|
||||
);
|
||||
@@ -677,6 +687,17 @@ try {
|
||||
bytes.writeBigUInt64LE(8n, header + 56);
|
||||
return [`undersized-entry-${type}`, bytes, "invalid_input"];
|
||||
});
|
||||
const nonintegralRelocationEntries = [
|
||||
["RELA", 4, 24],
|
||||
["REL", 9, 16],
|
||||
].map(([type, sectionType, entrySize]) => {
|
||||
const bytes = Buffer.from(object);
|
||||
const header = Number(BigInt(relocationSection.header_location.offset));
|
||||
bytes.writeUInt32LE(sectionType, header + 4);
|
||||
bytes.writeBigUInt64LE(BigInt(entrySize + 1), header + 32);
|
||||
bytes.writeBigUInt64LE(BigInt(entrySize), header + 56);
|
||||
return [`nonintegral-entry-count-${type}`, bytes, "invalid_input"];
|
||||
});
|
||||
const invalidRelocationReferences = [
|
||||
["missing-symbol-table", relocatable.sections.length + 100],
|
||||
["wrong-symbol-table-kind", 1],
|
||||
@@ -1066,7 +1087,9 @@ try {
|
||||
],
|
||||
...zeroSymbolEntries,
|
||||
...undersizedSymbolEntries,
|
||||
...nonintegralSymbolEntries,
|
||||
...undersizedRelocationEntries,
|
||||
...nonintegralRelocationEntries,
|
||||
...invalidRelocationReferences,
|
||||
["unterminated-dynamic-segment", shortDynamic, "invalid_input"],
|
||||
["unterminated-interpreter", unterminatedInterpreter, "invalid_input"],
|
||||
|
||||
@@ -5,18 +5,19 @@ import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import Ajv2020 from "ajv/dist/2020.js";
|
||||
import { CATALOG_IDENTITY } from "../dist/catalogIdentity.js";
|
||||
import {
|
||||
TOOL_CONTRACTS,
|
||||
toolContract,
|
||||
} from "../dist/contracts/toolContracts.js";
|
||||
import { exec } from "./lib/verify-package-core.mjs";
|
||||
import {
|
||||
expandMcpSessionStatus,
|
||||
sortVerifierNames,
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { createAnthropicModelFixture } from "./verify/clients/anthropic-model-fixture.mjs";
|
||||
|
||||
assert(
|
||||
process.platform !== "win32",
|
||||
"Native Claude Code verifier requires POSIX; Windows is unverified",
|
||||
);
|
||||
const repo = fileURLToPath(new URL("..", import.meta.url));
|
||||
const runtimeRoot = process.env.REA_VERIFY_RUNTIME_ROOT ?? repo;
|
||||
const command = process.env.REA_VERIFY_CLAUDE_COMMAND ?? "claude";
|
||||
@@ -46,8 +47,13 @@ try {
|
||||
await exec(command, ["--version"], { env: environment, timeout: 30_000 })
|
||||
).stdout.trim();
|
||||
} catch (cause) {
|
||||
// npm installs put only a claude.cmd shim on PATH, which Node does not spawn without a shell.
|
||||
const windowsHint =
|
||||
process.platform === "win32"
|
||||
? " On Windows, select claude.exe; npm installs provide it under node_modules/@anthropic-ai/claude-code/bin."
|
||||
: "";
|
||||
throw new Error(
|
||||
`Claude Code preflight failed for ${command}; install Claude Code or set REA_VERIFY_CLAUDE_COMMAND. ${String(cause)}`,
|
||||
`Claude Code preflight failed for ${command}; install Claude Code or set REA_VERIFY_CLAUDE_COMMAND.${windowsHint} ${String(cause)}`,
|
||||
);
|
||||
}
|
||||
const target = join(lab, "fixture α测试");
|
||||
@@ -57,6 +63,8 @@ await writeFile(
|
||||
'export function result() { return { clientCompatibility: "REA_CLIENT_FIXTURE", count: 7 }; }\n',
|
||||
);
|
||||
let skillRequested = false;
|
||||
let sessionRequested = false;
|
||||
let sessionStatusSeen = false;
|
||||
let skillLoaded = false;
|
||||
let expectedSkillBody;
|
||||
let evidenceSeen = false;
|
||||
@@ -72,7 +80,7 @@ const containsText = (value, text) => {
|
||||
return Object.values(value).some((nested) => containsText(nested, text));
|
||||
return false;
|
||||
};
|
||||
const extractEvidence = (value) => {
|
||||
const extractInlineResult = (value, matches) => {
|
||||
if (typeof value === "string") {
|
||||
try {
|
||||
// Native Claude appends this resource hint to tool results. Original bodies remain intact in request artifacts.
|
||||
@@ -80,20 +88,16 @@ const extractEvidence = (value) => {
|
||||
/\n\n<system-reminder>\n<total_tokens>\d+ tokens left<\/total_tokens>\n<\/system-reminder>$/u,
|
||||
"",
|
||||
);
|
||||
return extractEvidence(JSON.parse(json));
|
||||
return extractInlineResult(JSON.parse(json), matches);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
if (value && typeof value === "object") {
|
||||
if (
|
||||
typeof value.evidence_id === "string" &&
|
||||
value.normalized_result?.graph?.nodes?.length > 0
|
||||
)
|
||||
return value;
|
||||
if (matches(value)) return value;
|
||||
for (const nested of Object.values(value)) {
|
||||
const evidence = extractEvidence(nested);
|
||||
if (evidence) return evidence;
|
||||
const result = extractInlineResult(nested, matches);
|
||||
if (result) return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
@@ -124,10 +128,12 @@ const fixture = await createAnthropicModelFixture({
|
||||
0,
|
||||
-"analyze_javascript_application".length,
|
||||
);
|
||||
const expected = TOOL_CONTRACTS.map((t) => prefix + t.name).sort();
|
||||
const expected = sortVerifierNames(
|
||||
TOOL_CONTRACTS.map((t) => prefix + t.name),
|
||||
);
|
||||
const reaTools = tools.filter((t) => t.name.startsWith(prefix));
|
||||
assert.deepEqual(
|
||||
reaTools.map((t) => t.name).sort(),
|
||||
sortVerifierNames(reaTools.map((t) => t.name)),
|
||||
expected,
|
||||
"Claude must forward the complete REA catalog after connection",
|
||||
);
|
||||
@@ -145,11 +151,61 @@ const fixture = await createAnthropicModelFixture({
|
||||
schemasChecked++;
|
||||
}
|
||||
}
|
||||
if (mode === "call" && !sessionStatusSeen) {
|
||||
const observedStatus = extractInlineResult(
|
||||
body.messages,
|
||||
(value) =>
|
||||
Boolean(value.result?.server_identity) &&
|
||||
Array.isArray(value.result?.tool_availability?.groups),
|
||||
);
|
||||
if (observedStatus) {
|
||||
const parsedStatus =
|
||||
toolContract("binary_session").outputSchema.parse(observedStatus);
|
||||
const status = {
|
||||
...parsedStatus,
|
||||
result: expandMcpSessionStatus(parsedStatus.result),
|
||||
};
|
||||
assert.deepEqual(
|
||||
status.result.server_identity.catalog,
|
||||
CATALOG_IDENTITY,
|
||||
"binary_session must return the exact compact catalog counts and digests",
|
||||
);
|
||||
assert.equal(
|
||||
Object.hasOwn(status.result.server_identity.catalog, "tools"),
|
||||
false,
|
||||
"binary_session must not inline the duplicated canonical tool catalog",
|
||||
);
|
||||
assert.deepEqual(
|
||||
sortVerifierNames(
|
||||
status.result.tool_availability.map((entry) => entry.name),
|
||||
),
|
||||
sortVerifierNames(TOOL_CONTRACTS.map((contract) => contract.name)),
|
||||
"binary_session must retain complete canonical tool availability",
|
||||
);
|
||||
sessionStatusSeen = true;
|
||||
} else {
|
||||
assert(
|
||||
!sessionRequested,
|
||||
"Claude must return the inline binary_session result before continuing",
|
||||
);
|
||||
const session = tools.find(
|
||||
(tool) => tool.name === `${prefix}binary_session`,
|
||||
);
|
||||
assert(session, "Claude must expose the REA binary_session tool");
|
||||
sessionRequested = true;
|
||||
return call(session.name, {});
|
||||
}
|
||||
}
|
||||
skillLoaded ||=
|
||||
expectedSkillBody !== undefined &&
|
||||
containsText(body.messages, expectedSkillBody) &&
|
||||
containsText(body.messages, skillDirectory);
|
||||
const observed = extractEvidence(body.messages);
|
||||
const observed = extractInlineResult(
|
||||
body.messages,
|
||||
(value) =>
|
||||
typeof value.evidence_id === "string" &&
|
||||
value.normalized_result?.graph?.nodes?.length > 0,
|
||||
);
|
||||
if (observed) {
|
||||
const evidence = toolContract(
|
||||
"analyze_javascript_application",
|
||||
@@ -183,7 +239,7 @@ const fixture = await createAnthropicModelFixture({
|
||||
await writeFile(join(lab, "claude-evidence.json"), bytes);
|
||||
evidenceSeen = true;
|
||||
}
|
||||
if (mode === "chat" || evidenceSeen) {
|
||||
if (mode === "chat" || (sessionStatusSeen && evidenceSeen)) {
|
||||
finalSeen = true;
|
||||
return [{ type: "text", text: "REA_CLIENT_COMPATIBILITY_OK" }];
|
||||
}
|
||||
@@ -293,6 +349,7 @@ try {
|
||||
"Skill",
|
||||
"WaitForMcpServers",
|
||||
"mcp__rea__analyze_javascript_application",
|
||||
...(mode === "call" ? ["mcp__rea__binary_session"] : []),
|
||||
],
|
||||
},
|
||||
}),
|
||||
@@ -323,7 +380,7 @@ try {
|
||||
assert.equal(parsed.result, "REA_CLIENT_COMPATIBILITY_OK");
|
||||
assert.deepEqual(parsed.permission_denials, []);
|
||||
assert(finalSeen);
|
||||
if (mode === "call") assert(evidenceSeen && skillLoaded);
|
||||
if (mode === "call") assert(evidenceSeen && skillLoaded && sessionStatusSeen);
|
||||
} catch (cause) {
|
||||
failure = cause;
|
||||
if (typeof cause?.stdout === "string")
|
||||
@@ -354,6 +411,7 @@ try {
|
||||
schemasChecked,
|
||||
pendingServerWaits,
|
||||
evidenceSeen,
|
||||
sessionStatusSeen,
|
||||
skillLoaded,
|
||||
artifactDigest,
|
||||
finalSeen,
|
||||
|
||||
@@ -5,11 +5,12 @@ import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { Ajv2020 } from "ajv/dist/2020.js";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import {
|
||||
TOOL_CONTRACTS,
|
||||
toolContract,
|
||||
} from "../dist/contracts/toolContracts.js";
|
||||
import { exec } from "./lib/verify-package-core.mjs";
|
||||
import { exec, pathExists } from "./lib/verify-package-core.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { createOpenAiModelFixture } from "./verify/clients/model-fixture.mjs";
|
||||
|
||||
@@ -32,6 +33,7 @@ const skillPath = join(
|
||||
"SKILL.md",
|
||||
);
|
||||
const cli = join(runtimeRoot, "scripts", "rea.mjs");
|
||||
const configPath = join(profile, "cordis.patch.yml");
|
||||
const verifier = createVerifierRun();
|
||||
await Promise.all([
|
||||
mkdir(account),
|
||||
@@ -66,32 +68,32 @@ const clientVersion = (
|
||||
timeout: 30_000,
|
||||
})
|
||||
).stdout.trim();
|
||||
for (const args of [
|
||||
["setup", "--skill", "--dry-run"],
|
||||
["setup", "--skill", "--yes"],
|
||||
]) {
|
||||
const lifecycle = [];
|
||||
const runRea = async (args, artifact) => {
|
||||
const result = await exec(process.execPath, [cli, ...args, "--json"], {
|
||||
env: environment,
|
||||
cwd: workspace,
|
||||
timeout: 60_000,
|
||||
});
|
||||
const parsed = JSON.parse(result.stdout);
|
||||
assert.equal(parsed.status, args.includes("--dry-run") ? "planned" : "ready");
|
||||
if (args.includes("--dry-run")) {
|
||||
assert.equal(parsed.plannedActions.length, 1);
|
||||
assert.equal(parsed.plannedActions[0].kind, "install_skill");
|
||||
assert.equal(parsed.plannedActions[0].target, dirname(skillPath));
|
||||
}
|
||||
await writeFile(
|
||||
join(lab, args.includes("--dry-run") ? "setup-plan.json" : "setup.json"),
|
||||
result.stdout,
|
||||
);
|
||||
}
|
||||
assert(
|
||||
(await readFile(skillPath, "utf8")).includes(
|
||||
"name: reverse-engineer-anything",
|
||||
),
|
||||
);
|
||||
await writeFile(join(lab, `${artifact}.json`), result.stdout);
|
||||
return JSON.parse(result.stdout);
|
||||
};
|
||||
const patches = (text) =>
|
||||
parseYaml(text, {
|
||||
customTags: [
|
||||
{
|
||||
tag: "tag:yaml.org,2002:js",
|
||||
resolve: (source) => ({ __jsExpr: source }),
|
||||
},
|
||||
],
|
||||
});
|
||||
const withoutRea = (text) =>
|
||||
patches(text).flatMap((op) => {
|
||||
if (op.id === "rea-mcp") return [];
|
||||
if (!Array.isArray(op.insert)) return [op];
|
||||
const insert = op.insert.filter((entry) => entry.id !== "rea-mcp");
|
||||
return insert.length === 0 ? [] : [{ ...op, insert }];
|
||||
});
|
||||
|
||||
let catalog;
|
||||
let patternCount = 0;
|
||||
@@ -247,10 +249,9 @@ const fixture = await createOpenAiModelFixture({
|
||||
});
|
||||
let failure;
|
||||
try {
|
||||
// Use the client's native Cordis patch DSL; generic `rea setup --client` does not edit it.
|
||||
await writeFile(
|
||||
join(profile, "cordis.patch.yml"),
|
||||
`
|
||||
// Seed model settings and a stale registration; the public setup workflow
|
||||
// must produce the registration this native client actually boots.
|
||||
const original = `
|
||||
- id: llm-pi-ai
|
||||
config:
|
||||
providers:
|
||||
@@ -275,13 +276,59 @@ try {
|
||||
config:
|
||||
serverName: rea
|
||||
transport: stdio
|
||||
command: ${JSON.stringify(process.execPath)}
|
||||
args: ${JSON.stringify([cli, "mcp"])}
|
||||
command: stale-launcher
|
||||
args: [mcp]
|
||||
cwd: !!js process.cwd()
|
||||
env:
|
||||
REA_PROCESS_RUN_ID: ${JSON.stringify(verifier.run_id)}
|
||||
`,
|
||||
`;
|
||||
await writeFile(configPath, original);
|
||||
const planned = await runRea(
|
||||
["setup", "--client", "dsh", "--dry-run"],
|
||||
"setup-plan",
|
||||
);
|
||||
assert.equal(planned.status, "planned");
|
||||
assert(planned.plannedActions.some((action) => action.target === configPath));
|
||||
assert(
|
||||
planned.plannedActions.some(
|
||||
(action) => action.target === dirname(skillPath),
|
||||
),
|
||||
);
|
||||
assert(
|
||||
planned.plannedActions.every(
|
||||
(action) =>
|
||||
action.target === configPath || action.target === dirname(skillPath),
|
||||
),
|
||||
);
|
||||
assert.equal(await readFile(configPath, "utf8"), original);
|
||||
assert.equal(await pathExists(`${configPath}.rea.backup`), false);
|
||||
lifecycle.push("dry-run");
|
||||
const setup = await runRea(["setup", "--client", "dsh", "--yes"], "setup");
|
||||
assert.equal(setup.status, "ready");
|
||||
assert.equal(await readFile(`${configPath}.rea.backup`, "utf8"), original);
|
||||
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",
|
||||
),
|
||||
);
|
||||
lifecycle.push("setup");
|
||||
const repeated = await runRea(
|
||||
["setup", "--client", "dsh", "--yes"],
|
||||
"setup-repeat",
|
||||
);
|
||||
assert.deepEqual(repeated.appliedActions, []);
|
||||
assert.equal(await readFile(configPath, "utf8"), configured);
|
||||
assert.equal(await readFile(`${configPath}.rea.backup`, "utf8"), original);
|
||||
lifecycle.push("idempotent-setup");
|
||||
const doctor = await runRea(
|
||||
["doctor", "--client", "dsh", "--skill"],
|
||||
"doctor",
|
||||
);
|
||||
assert.equal(doctor.healthy, true);
|
||||
lifecycle.push("doctor");
|
||||
const execution = exec(
|
||||
command,
|
||||
[
|
||||
@@ -304,6 +351,20 @@ try {
|
||||
assert.equal(fixture.failure, undefined);
|
||||
assert(finalSeen && result.stdout.includes("REA_CLIENT_COMPATIBILITY_OK"));
|
||||
if (mode === "call") assert(evidenceSeen && skillLoaded);
|
||||
lifecycle.push(mode === "call" ? "native-tool-invocation" : "native-chat");
|
||||
const uninstall = await runRea(["uninstall"], "uninstall");
|
||||
assert.equal(uninstall.status, "complete");
|
||||
assert(
|
||||
uninstall.items.some(
|
||||
(item) => item.name === "dsh" && item.status === "removed",
|
||||
),
|
||||
);
|
||||
assert.deepEqual(
|
||||
patches(await readFile(configPath, "utf8")),
|
||||
withoutRea(original),
|
||||
);
|
||||
assert.equal(await pathExists(skillPath), false);
|
||||
lifecycle.push("uninstall");
|
||||
} catch (cause) {
|
||||
failure = cause;
|
||||
if (typeof cause?.stdout === "string")
|
||||
@@ -316,6 +377,7 @@ try {
|
||||
client: "deepseek-harness",
|
||||
clientVersion,
|
||||
mode,
|
||||
lifecycle,
|
||||
passed: failure === undefined,
|
||||
artifacts: lab,
|
||||
runtimeRoot,
|
||||
|
||||
@@ -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 { join, resolve, sep } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import Ajv2020 from "ajv/dist/2020.js";
|
||||
import {
|
||||
@@ -13,14 +13,18 @@ import { exec } from "./lib/verify-package-core.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { createGeminiModelFixture } from "./verify/clients/gemini-model-fixture.mjs";
|
||||
|
||||
assert(
|
||||
process.platform !== "win32",
|
||||
"Native Gemini verifier requires POSIX; Windows is unverified",
|
||||
);
|
||||
const repo = fileURLToPath(new URL("..", import.meta.url));
|
||||
const runtimeRoot = process.env.REA_VERIFY_RUNTIME_ROOT ?? repo;
|
||||
const command = process.env.REA_VERIFY_GEMINI_COMMAND ?? "gemini";
|
||||
const mode = process.argv[2] ?? "call";
|
||||
// npm installs of Gemini CLI provide a JavaScript entry point; on Windows only a gemini.cmd shim reaches
|
||||
// PATH, which Node does not spawn without a shell, so a selected .js entry runs under this Node. It is
|
||||
// resolved once, because the preflight runs from the caller's directory and the client from the workspace.
|
||||
const entryPoint = /\.(c|m)?js$/iu.test(command) ? resolve(command) : undefined;
|
||||
const launch = (args) =>
|
||||
entryPoint === undefined
|
||||
? [command, args]
|
||||
: [process.execPath, [entryPoint, ...args]];
|
||||
assert(
|
||||
["chat", "call"].includes(mode),
|
||||
"Usage: verify-gemini-client.mjs [chat|call]",
|
||||
@@ -60,11 +64,15 @@ const environment = {
|
||||
let clientVersion;
|
||||
try {
|
||||
clientVersion = (
|
||||
await exec(command, ["--version"], { env: environment, timeout: 30_000 })
|
||||
await exec(...launch(["--version"]), { env: environment, timeout: 30_000 })
|
||||
).stdout.trim();
|
||||
} catch (cause) {
|
||||
const windowsHint =
|
||||
process.platform === "win32"
|
||||
? " On Windows, select the JavaScript entry point; npm installs provide it as node_modules/@google/gemini-cli/bundle/gemini.js."
|
||||
: "";
|
||||
throw new Error(
|
||||
`Gemini preflight failed for ${command}; install Gemini CLI or set REA_VERIFY_GEMINI_COMMAND. ${String(cause)}`,
|
||||
`Gemini preflight failed for ${command}; install Gemini CLI or set REA_VERIFY_GEMINI_COMMAND.${windowsHint} ${String(cause)}`,
|
||||
);
|
||||
}
|
||||
const original = JSON.stringify(
|
||||
@@ -103,11 +111,11 @@ for (const args of [
|
||||
assert(parsed.plannedActions.some((a) => a.target === configPath));
|
||||
assert(
|
||||
parsed.plannedActions.some((a) =>
|
||||
a.target.startsWith(join(profile, ".agents", "skills") + "/"),
|
||||
a.target.startsWith(join(profile, ".agents", "skills") + sep),
|
||||
),
|
||||
);
|
||||
assert(
|
||||
parsed.plannedActions.every((a) => a.target.startsWith(profile + "/")),
|
||||
parsed.plannedActions.every((a) => a.target.startsWith(profile + sep)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -300,8 +308,7 @@ const fixture = await createGeminiModelFixture({
|
||||
let failure;
|
||||
try {
|
||||
const response = await exec(
|
||||
command,
|
||||
[
|
||||
...launch([
|
||||
"--model",
|
||||
"gemini-2.5-flash",
|
||||
"--approval-mode",
|
||||
@@ -314,7 +321,7 @@ try {
|
||||
mode === "chat"
|
||||
? "Say hello briefly."
|
||||
: `Use REA to analyze the JavaScript directory ${target}. Report the result.`,
|
||||
],
|
||||
]),
|
||||
{
|
||||
env: {
|
||||
...environment,
|
||||
|
||||
@@ -0,0 +1,518 @@
|
||||
#!/usr/bin/env node
|
||||
import assert from "node:assert/strict";
|
||||
import { isUtf8 } from "node:buffer";
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { isAbsolute, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { goBinarySchema } from "../dist/domain/go/goBinary.js";
|
||||
import { PrivateRuntimeRoot } from "../dist/process/PrivateRuntimeRoot.js";
|
||||
import { mcpTextValue } from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
const compiler = process.env.REA_VERIFY_GO_COMMAND ?? "go";
|
||||
if (process.env.REA_VERIFY_GO_COMMAND !== undefined && !isAbsolute(compiler))
|
||||
throw new Error(
|
||||
"REA_VERIFY_GO_COMMAND must be an absolute Go compiler path.",
|
||||
);
|
||||
let compilerVersion = null;
|
||||
const run = createVerifierRun();
|
||||
const root = await PrivateRuntimeRoot.create({ prefix: "rea-go-verifier-" });
|
||||
const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url));
|
||||
const environment = Object.fromEntries(
|
||||
Object.entries(process.env).filter(([, value]) => typeof value === "string"),
|
||||
);
|
||||
delete environment.HOPPER_TARGET_PATH;
|
||||
const goEnvironment = {
|
||||
...environment,
|
||||
CGO_ENABLED: "0",
|
||||
GOCACHEPROG: "",
|
||||
GO111MODULE: "on",
|
||||
GOENV: "off",
|
||||
GOFLAGS: "",
|
||||
GOMAXPROCS: "2",
|
||||
GOTOOLCHAIN: "local",
|
||||
GOWORK: "off",
|
||||
GOPROXY: "off",
|
||||
GOSUMDB: "off",
|
||||
GOPATH: join(root.path, "gopath"),
|
||||
GOCACHE: join(root.path, "cache"),
|
||||
GOMODCACHE: join(root.path, "modules"),
|
||||
GOTMPDIR: join(root.path, "tmp"),
|
||||
// Go's own verifier seam isolates telemetry; no global configuration is changed.
|
||||
TEST_TELEMETRY_DIR: join(root.path, "telemetry"),
|
||||
};
|
||||
const client = new Client({ name: "go-binary-verifier", version: "1" });
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [entrypoint, "mcp"],
|
||||
env: environment,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const targets = [
|
||||
["linux", "amd64", "elf", 64, "little"],
|
||||
["linux", "386", "elf", 32, "little"],
|
||||
["linux", "arm64", "elf", 64, "little"],
|
||||
["linux", "ppc64", "elf", 64, "big"],
|
||||
["linux", "mips", "elf", 32, "big"],
|
||||
["windows", "amd64", "pe", 64, "little"],
|
||||
["windows", "386", "pe", 32, "little"],
|
||||
["darwin", "amd64", "macho", 64, "little"],
|
||||
["darwin", "arm64", "macho", 64, "little"],
|
||||
];
|
||||
const verified = [];
|
||||
const omittedCoverage = [];
|
||||
const failures = [];
|
||||
try {
|
||||
await prepareFixture();
|
||||
try {
|
||||
compilerVersion = (
|
||||
await execute(compiler, ["version"], {
|
||||
cwd: root.path,
|
||||
env: goEnvironment,
|
||||
timeout: 10_000,
|
||||
})
|
||||
).stdout.trim();
|
||||
} catch (cause) {
|
||||
throw new Error(
|
||||
"verify:go:binary requires Go on PATH or absolute REA_VERIFY_GO_COMMAND; Go is only a fixture producer, not a REA runtime dependency.",
|
||||
{ cause },
|
||||
);
|
||||
}
|
||||
const host = (await go(["env", "GOHOSTOS", "GOHOSTARCH"])).stdout
|
||||
.trim()
|
||||
.split("\n");
|
||||
const oracle = join(
|
||||
root.path,
|
||||
host[0] === "windows" ? "buildinfo-oracle.exe" : "buildinfo-oracle",
|
||||
);
|
||||
await go(
|
||||
["build", "-trimpath", "-buildvcs=false", "-o", oracle, "./oracle"],
|
||||
{ GOOS: host[0], GOARCH: host[1] },
|
||||
);
|
||||
await client.connect(transport);
|
||||
for (const target of targets) {
|
||||
const [os, arch] = target;
|
||||
const path = join(
|
||||
root.path,
|
||||
`${os}-${arch}${os === "windows" ? ".exe" : ""}`,
|
||||
);
|
||||
await go(
|
||||
[
|
||||
"build",
|
||||
"-trimpath",
|
||||
"-buildvcs=false",
|
||||
"-ldflags=-s -w",
|
||||
"-o",
|
||||
path,
|
||||
".",
|
||||
],
|
||||
{ GOOS: os, GOARCH: arch },
|
||||
);
|
||||
await verify(path, target, oracle);
|
||||
if (os === "linux" && arch === "amd64") {
|
||||
const sectionless = join(root.path, "linux-amd64-sectionless");
|
||||
const bytes = Buffer.from(await readFile(path));
|
||||
bytes.fill(0, 40, 48); // ELF64 e_shoff.
|
||||
bytes.fill(0, 58, 64); // e_shentsize, e_shnum and e_shstrndx.
|
||||
await writeFile(sectionless, bytes);
|
||||
await verify(sectionless, target, oracle);
|
||||
}
|
||||
}
|
||||
if (process.platform === "win32")
|
||||
omittedCoverage.push(
|
||||
"Compiler-produced non-UTF-8 argv strings were not verified on Windows; Windows argv cannot preserve arbitrary byte arguments.",
|
||||
);
|
||||
else {
|
||||
const path = join(root.path, "linux-amd64-byte-strings");
|
||||
const expected = JSON.parse(
|
||||
(
|
||||
await execute(oracle, ["--build-byte-fixture", compiler, path], {
|
||||
cwd: root.path,
|
||||
env: { ...goEnvironment, GOOS: "linux", GOARCH: "amd64" },
|
||||
timeout: 120_000,
|
||||
maxBuffer: 1024 * 1024,
|
||||
})
|
||||
).stdout,
|
||||
);
|
||||
await verify(path, targets[0], oracle, expected);
|
||||
}
|
||||
} catch (cause) {
|
||||
failures.push(cause);
|
||||
} finally {
|
||||
for (const close of [
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
() => root.close(),
|
||||
]) {
|
||||
try {
|
||||
await close();
|
||||
} catch (cause) {
|
||||
failures.push(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
const verifier = await completeVerifierRun(run);
|
||||
try {
|
||||
await assert.rejects(readFile(join(root.path, "go.mod")), { code: "ENOENT" });
|
||||
if (verifier.process_lineage.status === "verified")
|
||||
assert.deepEqual(verifier.process_lineage.descendants, []);
|
||||
} catch (cause) {
|
||||
failures.push(cause);
|
||||
}
|
||||
if (failures.length > 0) {
|
||||
console.error(
|
||||
JSON.stringify(
|
||||
{
|
||||
status: "failed",
|
||||
compiler: compilerVersion,
|
||||
verified,
|
||||
omitted_coverage: omittedCoverage,
|
||||
verifier,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
throw new AggregateError(
|
||||
failures,
|
||||
"Real Go metadata verification failed; cleanup failures are retained.",
|
||||
);
|
||||
}
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
status: "passed",
|
||||
compiler: compilerVersion,
|
||||
binaries: verified.length,
|
||||
public_cases: verified.length * 2,
|
||||
verified,
|
||||
omitted_coverage: omittedCoverage,
|
||||
fixture_execution:
|
||||
"not-requested; only the Go fixture producer and test-owned buildinfo oracle/helper were executed",
|
||||
verifier,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
async function go(args, overrides = {}) {
|
||||
return execute(compiler, args, {
|
||||
cwd: root.path,
|
||||
env: { ...goEnvironment, ...overrides },
|
||||
timeout: 120_000,
|
||||
maxBuffer: 1024 * 1024,
|
||||
});
|
||||
}
|
||||
|
||||
async function prepareFixture() {
|
||||
await mkdir(join(root.path, "tmp"));
|
||||
await mkdir(join(root.path, "telemetry"));
|
||||
await writeFile(join(root.path, "telemetry", "mode"), "off\n");
|
||||
await mkdir(join(root.path, "dependency"));
|
||||
await mkdir(join(root.path, "oracle"));
|
||||
await writeFile(
|
||||
join(root.path, "go.mod"),
|
||||
"module example.test/rea-go-fixture\n\ngo 1.20\n\nrequire example.test/dependency v1.2.3\nreplace example.test/dependency => ./dependency\n",
|
||||
);
|
||||
await writeFile(
|
||||
join(root.path, "dependency", "go.mod"),
|
||||
"module example.test/dependency\n\ngo 1.20\n",
|
||||
);
|
||||
await writeFile(
|
||||
join(root.path, "dependency", "dependency.go"),
|
||||
'package dependency\nfunc Value() string { return "REA fixture" }\n',
|
||||
);
|
||||
await writeFile(
|
||||
join(root.path, "main.go"),
|
||||
'package main\nimport ("fmt"; "example.test/dependency")\nfunc main() { fmt.Println(dependency.Value()) }\n',
|
||||
);
|
||||
await writeFile(
|
||||
join(root.path, "oracle", "main.go"),
|
||||
String.raw`package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"debug/buildinfo"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func main() {
|
||||
byteFixture := len(os.Args) == 4 && os.Args[1] == "--build-byte-fixture"
|
||||
path := os.Args[1]
|
||||
if byteFixture {
|
||||
path = os.Args[3]
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, os.Args[2], "build", "-buildvcs=false",
|
||||
"-tags=" + string([]byte{0xff}),
|
||||
"-ldflags=-X runtime.buildVersion=custom" + string([]byte{0xff}),
|
||||
"-o", path, ".")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil { panic(err) }
|
||||
}
|
||||
info, err := buildinfo.ReadFile(path)
|
||||
if err != nil { panic(err) }
|
||||
var result interface{} = info
|
||||
if byteFixture {
|
||||
settings := make([]map[string]string, 0)
|
||||
byteSettings := make([]map[string]string, 0)
|
||||
for _, setting := range info.Settings {
|
||||
if utf8.ValidString(setting.Key) && utf8.ValidString(setting.Value) {
|
||||
settings = append(settings, map[string]string{"Key": setting.Key, "Value": setting.Value})
|
||||
} else {
|
||||
byteSettings = append(byteSettings, map[string]string{
|
||||
"KeyBytesBase64": base64.StdEncoding.EncodeToString([]byte(setting.Key)),
|
||||
"ValueBytesBase64": base64.StdEncoding.EncodeToString([]byte(setting.Value)),
|
||||
})
|
||||
}
|
||||
}
|
||||
result = map[string]interface{}{
|
||||
"GoVersion": nil,
|
||||
"GoVersionBytesBase64": base64.StdEncoding.EncodeToString([]byte(info.GoVersion)),
|
||||
"Path": info.Path, "Main": info.Main, "Deps": info.Deps,
|
||||
"Settings": settings, "ByteSettings": byteSettings,
|
||||
}
|
||||
}
|
||||
if err := json.NewEncoder(os.Stdout).Encode(result); err != nil { panic(err) }
|
||||
}
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
async function inspect(mode, path) {
|
||||
let envelope;
|
||||
if (mode === "cli") {
|
||||
const { stdout } = await execute(
|
||||
process.execPath,
|
||||
[entrypoint, "inspect-go-binary", path, "--json"],
|
||||
{ env: environment, timeout: 30_000, maxBuffer: 8 * 1024 * 1024 },
|
||||
);
|
||||
envelope = JSON.parse(stdout);
|
||||
} else {
|
||||
const response = await client.callTool(
|
||||
{ name: "inspect_go_binary", arguments: { path } },
|
||||
{ timeout: 30_000 },
|
||||
);
|
||||
assert.notEqual(response.isError, true, mcpTextValue(response));
|
||||
envelope = JSON.parse(mcpTextValue(response));
|
||||
}
|
||||
const evidence = parseEvidence(envelope);
|
||||
assert.equal(evidence.subject.local_path, path);
|
||||
const report = goBinarySchema.parse(evidence.normalized_result);
|
||||
assert.equal(evidence.subject.digest.sha256, report.artifact.sha256);
|
||||
return { evidence, report };
|
||||
}
|
||||
|
||||
async function verify(path, target, oracle, byteExpected = null) {
|
||||
const bytes = await readFile(path);
|
||||
const expected =
|
||||
byteExpected ??
|
||||
JSON.parse(
|
||||
(
|
||||
await execute(oracle, [path], {
|
||||
timeout: 10_000,
|
||||
maxBuffer: 1024 * 1024,
|
||||
})
|
||||
).stdout,
|
||||
);
|
||||
const reports = [];
|
||||
for (const mode of ["cli", "mcp"]) {
|
||||
const { evidence, report } = await inspect(mode, path);
|
||||
assert.equal(report.format, target[2]);
|
||||
assert.equal(report.architecture, target[1]);
|
||||
assert.equal(
|
||||
evidence.subject.architecture,
|
||||
{ amd64: "x86_64", 386: "x86", arm64: "arm64", mips: "mips" }[
|
||||
target[1]
|
||||
] ?? null,
|
||||
);
|
||||
assert.equal(report.bits, target[3]);
|
||||
assert.equal(report.byte_order, target[4]);
|
||||
assert.equal(report.artifact.path, path);
|
||||
assert.equal(report.artifact.bytes, bytes.length);
|
||||
assert.equal(
|
||||
report.artifact.sha256,
|
||||
createHash("sha256").update(bytes).digest("hex"),
|
||||
);
|
||||
const info = report.build_info;
|
||||
assert.notEqual(info, null);
|
||||
assert.equal(info.go_version, expected.GoVersion);
|
||||
assert.equal(info.module.path, expected.Path);
|
||||
assert.deepEqual(
|
||||
normalizeObservedModule(info.module.main),
|
||||
normalizeModule(expected.Main),
|
||||
);
|
||||
assert.deepEqual(
|
||||
info.module.dependencies.map(normalizeObservedModule),
|
||||
(expected.Deps ?? []).map(normalizeModule),
|
||||
);
|
||||
assert.deepEqual(
|
||||
info.module.settings,
|
||||
(expected.Settings ?? []).map(({ Key, Value }) => ({
|
||||
key: Key,
|
||||
value: Value,
|
||||
})),
|
||||
);
|
||||
assert.equal(info.module.complete, byteExpected === null);
|
||||
if (byteExpected === null) {
|
||||
assert.deepEqual(info.module.unparsed_lines, []);
|
||||
assert.deepEqual(info.module.unparsed_line_bytes_base64, []);
|
||||
} else verifyByteStrings(info, expected);
|
||||
verifySource(info, bytes);
|
||||
reports.push(report);
|
||||
assert.deepEqual(
|
||||
await readFile(path),
|
||||
bytes,
|
||||
`${mode} changed source bytes`,
|
||||
);
|
||||
}
|
||||
assert.deepEqual(
|
||||
reports[0],
|
||||
reports[1],
|
||||
"Public CLI and MCP projections disagree",
|
||||
);
|
||||
verified.push({
|
||||
target: `${target[0]}/${target[1]}`,
|
||||
file:
|
||||
byteExpected === null
|
||||
? path.endsWith("sectionless")
|
||||
? "sectionless"
|
||||
: "stripped"
|
||||
: "byte-valued-strings",
|
||||
go_version: expected.GoVersion,
|
||||
...(byteExpected === null
|
||||
? {}
|
||||
: { go_version_bytes_base64: expected.GoVersionBytesBase64 }),
|
||||
});
|
||||
}
|
||||
|
||||
function verifyByteStrings(info, expected) {
|
||||
const rawByte = Buffer.from([0xff]);
|
||||
const compilerBytes = Buffer.concat([Buffer.from("custom"), rawByte]);
|
||||
const ldflagsBytes = Buffer.concat([
|
||||
Buffer.from("-X runtime.buildVersion=custom"),
|
||||
rawByte,
|
||||
]);
|
||||
assert.equal(info.go_version, null);
|
||||
assert.equal(info.module_text, null);
|
||||
assert.equal(info.go_version_bytes_base64, compilerBytes.toString("base64"));
|
||||
assert.equal(info.go_version_bytes_base64, expected.GoVersionBytesBase64);
|
||||
assert.deepEqual(expected.ByteSettings, [
|
||||
{
|
||||
KeyBytesBase64: Buffer.from("-ldflags").toString("base64"),
|
||||
ValueBytesBase64: ldflagsBytes.toString("base64"),
|
||||
},
|
||||
{
|
||||
KeyBytesBase64: Buffer.from("-tags").toString("base64"),
|
||||
ValueBytesBase64: rawByte.toString("base64"),
|
||||
},
|
||||
]);
|
||||
assert.deepEqual(info.module.unparsed_lines, [
|
||||
'build\t-ldflags="-X runtime.buildVersion=custom\\xff"',
|
||||
]);
|
||||
assert.deepEqual(info.module.unparsed_line_bytes_base64, [
|
||||
Buffer.concat([Buffer.from("build\t-tags="), rawByte]).toString("base64"),
|
||||
]);
|
||||
}
|
||||
|
||||
function normalizeModule(module) {
|
||||
const identity = ({ Path, Version, Sum }) => ({
|
||||
path: Path,
|
||||
version: Version,
|
||||
sum: Sum === "" || Sum === undefined ? null : Sum,
|
||||
});
|
||||
return {
|
||||
...identity(module),
|
||||
replacement: module.Replace == null ? null : identity(module.Replace),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeObservedModule(module) {
|
||||
// debug/buildinfo JSON represents both absent and explicitly empty sums as "".
|
||||
const identity = (value) => ({
|
||||
...value,
|
||||
sum: value.sum === "" ? null : value.sum,
|
||||
});
|
||||
return {
|
||||
...identity(module),
|
||||
replacement:
|
||||
module.replacement === null ? null : identity(module.replacement),
|
||||
};
|
||||
}
|
||||
|
||||
function verifySource(info, bytes) {
|
||||
assert.deepEqual(
|
||||
bytes.subarray(info.header_offset, info.header_offset + 14),
|
||||
Buffer.from("ff20476f206275696c64696e663a", "hex"),
|
||||
);
|
||||
assert.equal(
|
||||
info.encoding,
|
||||
"inline",
|
||||
"Current compiler fixture must use inline string encoding",
|
||||
);
|
||||
const version = inlineString(bytes, info.header_offset + 32);
|
||||
const module = inlineString(bytes, version.offset + version.bytes.length);
|
||||
assert.deepEqual(info.version_location, {
|
||||
offset: version.offset,
|
||||
bytes: version.bytes.length,
|
||||
});
|
||||
assert.deepEqual(info.module_location, {
|
||||
offset: module.offset,
|
||||
bytes: module.bytes.length,
|
||||
});
|
||||
assert.equal(
|
||||
isUtf8(version.bytes) ? version.bytes.toString("utf8") : null,
|
||||
info.go_version,
|
||||
);
|
||||
assert.equal(version.bytes.toString("base64"), info.go_version_bytes_base64);
|
||||
assert.equal(module.bytes.toString("base64"), info.module_bytes_base64);
|
||||
assert.deepEqual(
|
||||
module.bytes.subarray(0, 16),
|
||||
Buffer.from("3077af0c9274080241e1c107e6d618e6", "hex"),
|
||||
);
|
||||
assert.deepEqual(
|
||||
module.bytes.subarray(-16),
|
||||
Buffer.from("f932433186182072008242104116d8f2", "hex"),
|
||||
);
|
||||
const moduleBody = module.bytes.subarray(16, -16);
|
||||
assert.equal(
|
||||
isUtf8(moduleBody) ? moduleBody.toString("utf8") : null,
|
||||
info.module_text,
|
||||
);
|
||||
assert.equal(moduleBody.at(-1), 10);
|
||||
}
|
||||
|
||||
function inlineString(bytes, start) {
|
||||
let length = 0;
|
||||
let offset = start;
|
||||
let shift = 0;
|
||||
for (;;) {
|
||||
assert.ok(
|
||||
offset < bytes.length && shift < 49,
|
||||
"Fixture string length is invalid",
|
||||
);
|
||||
const byte = bytes[offset++];
|
||||
length += (byte & 127) * 2 ** shift;
|
||||
if ((byte & 128) === 0) break;
|
||||
shift += 7;
|
||||
}
|
||||
assert.ok(
|
||||
length <= bytes.length - offset,
|
||||
"Fixture string contents are truncated",
|
||||
);
|
||||
return { offset, bytes: bytes.subarray(offset, offset + length) };
|
||||
}
|
||||
@@ -1,13 +1,19 @@
|
||||
import { json, run } from "./lib/verify-package-core.mjs";
|
||||
import {
|
||||
expandCapabilitiesOutput,
|
||||
expandProviderOutput,
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
|
||||
/** Verify packaged capabilities, providers, and non-Linux search results. */
|
||||
export async function verifyPackageCapabilitiesAndSearch({ cli, environment }) {
|
||||
const capabilities = json(
|
||||
await run(cli, ["capabilities", "--json"], environment),
|
||||
const capabilities = expandCapabilitiesOutput(
|
||||
json(await run(cli, ["capabilities", "--json"], environment)),
|
||||
);
|
||||
if (!Array.isArray(capabilities.capabilities))
|
||||
throw new Error("packaged capabilities CLI failed");
|
||||
const providers = json(await run(cli, ["providers", "--json"], environment));
|
||||
const providers = expandProviderOutput(
|
||||
json(await run(cli, ["providers", "--json"], environment)),
|
||||
);
|
||||
const candidates = providers.analysis_provider_candidates;
|
||||
if (
|
||||
!Array.isArray(candidates) ||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { requireMcpToolError } from "./lib/mcp-verifier-results.mjs";
|
||||
import {
|
||||
requireMcpLifecycleResult,
|
||||
requireMcpToolError,
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import { join } from "node:path";
|
||||
import { execFile } from "node:child_process";
|
||||
@@ -21,14 +24,14 @@ const verifyMcpToolsAndPrompts = async (client, mcpOptions) => {
|
||||
};
|
||||
|
||||
const verifyMcpTargetFree = async (client, mcpOptions) => {
|
||||
const status = await client.callTool(
|
||||
const result = await client.callTool(
|
||||
{ name: "binary_session", arguments: {} },
|
||||
mcpOptions,
|
||||
);
|
||||
const currentDocument =
|
||||
status.structuredContent?.result?.tool_availability?.find(
|
||||
({ name }) => name === "current_document",
|
||||
);
|
||||
const status = requireMcpLifecycleResult(result, "binary_session");
|
||||
const currentDocument = status.tool_availability.find(
|
||||
({ name }) => name === "current_document",
|
||||
);
|
||||
if (
|
||||
currentDocument?.available !== false ||
|
||||
currentDocument.reason !== "target_required"
|
||||
@@ -67,15 +70,13 @@ const verifyMcpOpenAndBind = async (client, mcpOptions) => {
|
||||
);
|
||||
if (opened.isError === true)
|
||||
throw new Error("packaged MCP could not open a binary");
|
||||
const providerStatusEnvelope = json(
|
||||
prompts.mcpText(
|
||||
await client.callTool(
|
||||
{ name: "binary_session", arguments: {} },
|
||||
mcpOptions,
|
||||
),
|
||||
const providerStatus = requireMcpLifecycleResult(
|
||||
await client.callTool(
|
||||
{ name: "binary_session", arguments: {} },
|
||||
mcpOptions,
|
||||
),
|
||||
"binary_session",
|
||||
);
|
||||
const providerStatus = providerStatusEnvelope.result;
|
||||
if (
|
||||
providerStatus.analysis_provider_binding?.provider?.id !== "hopper" ||
|
||||
providerStatus.analysis_provider_binding?.selection_source !== "request"
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
|
||||
import {
|
||||
expandProviderOutput,
|
||||
requireMcpOperationResult,
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
import { verifyGhidraCompactReferences } from "./lib/real-ghidra-compact-references.mjs";
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
@@ -14,7 +17,7 @@ import {
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { basename, dirname, join, resolve } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
@@ -172,11 +175,14 @@ const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
||||
const runtimeParent = join(workspace, "runtime with spaces");
|
||||
await mkdir(runtimeParent);
|
||||
const callerDirectory = join(workspace, "caller cwd with spaces");
|
||||
// Keep sorted for comparison with the final caller-directory listing.
|
||||
const scriptCollisions = [
|
||||
"ReaGhidraBridge.java",
|
||||
"ReaGhidraPrepareCom.java",
|
||||
"ReaGhidraAnalysisOptions.java",
|
||||
"ReaGhidraApplySeeds.java",
|
||||
"ReaGhidraBridge.java",
|
||||
"ReaGhidraNoReturnFix.java",
|
||||
"ReaGhidraPrepareCom.java",
|
||||
"ReaGhidraPrepareRaw.java",
|
||||
];
|
||||
await mkdir(callerDirectory);
|
||||
for (const name of scriptCollisions) await mkdir(join(callerDirectory, name));
|
||||
@@ -227,14 +233,16 @@ const assertCleanup = async () => {
|
||||
assert.equal(entries.length, 0, "Provider runtime cleanup left files.");
|
||||
};
|
||||
try {
|
||||
const providers = JSON.parse(
|
||||
(
|
||||
await exec(process.execPath, [entry, "providers", "--json"], {
|
||||
env: environment,
|
||||
cwd: callerDirectory,
|
||||
timeout: 30_000,
|
||||
})
|
||||
).stdout,
|
||||
const providers = expandProviderOutput(
|
||||
JSON.parse(
|
||||
(
|
||||
await exec(process.execPath, [entry, "providers", "--json"], {
|
||||
env: environment,
|
||||
cwd: callerDirectory,
|
||||
timeout: 30_000,
|
||||
})
|
||||
).stdout,
|
||||
),
|
||||
);
|
||||
assert.equal(providers.analysis_provider_binding, null);
|
||||
assert.ok(Array.isArray(providers.analysis_provider_candidates));
|
||||
@@ -290,6 +298,23 @@ try {
|
||||
await client.connect(transport);
|
||||
await call("open_binary", { path: target });
|
||||
await call("binary_overview", {});
|
||||
const activators = (
|
||||
await readdir(runtimeParent, { recursive: true })
|
||||
).filter((path) => basename(path) === "ReaGhidraBundleActivator.class");
|
||||
assert.equal(activators.length, 1);
|
||||
const [activator] = activators;
|
||||
assert.ok(activator !== undefined);
|
||||
const bundleDirectory = dirname(join(runtimeParent, activator));
|
||||
const manifest = await readFile(
|
||||
join(bundleDirectory, "META-INF", "MANIFEST.MF"),
|
||||
"utf8",
|
||||
);
|
||||
assert.match(manifest, /^Bundle-Activator: ReaGhidraBundleActivator\r?$/mu);
|
||||
await assert.rejects(
|
||||
readFile(join(bundleDirectory, "GeneratedActivator.class")),
|
||||
{ code: "ENOENT" },
|
||||
);
|
||||
report.bundleActivator = "ReaGhidraBundleActivator";
|
||||
assert.equal((await call("list_documents", {})).length, 1);
|
||||
await call("list_names", {});
|
||||
const procedures = await call("list_procedures", {});
|
||||
@@ -394,15 +419,15 @@ try {
|
||||
return evidence.normalized_result;
|
||||
},
|
||||
);
|
||||
const expected = (
|
||||
const { GHIDRA_MUTATING_OPERATIONS, GHIDRA_SESSION_CAPABILITIES } =
|
||||
await import(
|
||||
pathToFileURL(join(packageRoot, "dist/ghidra/GhidraSessionValues.js"))
|
||||
)
|
||||
).GHIDRA_SESSION_CAPABILITIES.filter(
|
||||
);
|
||||
const expected = GHIDRA_SESSION_CAPABILITIES.filter(
|
||||
(name) =>
|
||||
name !== "ping" &&
|
||||
name !== "shutdown" &&
|
||||
name !== "annotate_native_function",
|
||||
!GHIDRA_MUTATING_OPERATIONS.has(name),
|
||||
);
|
||||
assert.ok(
|
||||
expected.every((name) => report.mcpOperations.includes(name)),
|
||||
@@ -440,7 +465,10 @@ try {
|
||||
token,
|
||||
"Analysis changed caller token authority.",
|
||||
);
|
||||
assert.deepEqual((await readdir(callerDirectory)).sort(), scriptCollisions);
|
||||
assert.deepEqual(
|
||||
(await readdir(callerDirectory)).sort(),
|
||||
[...scriptCollisions].sort(),
|
||||
);
|
||||
for (const name of scriptCollisions)
|
||||
assert.deepEqual(await readdir(join(callerDirectory, name)), []);
|
||||
report.callerScriptCollisionsPreserved = true;
|
||||
|
||||
@@ -5,10 +5,16 @@ import { promisify } from "node:util";
|
||||
const exec = promisify(execFile);
|
||||
const packedHopperBridge = "package/bridge/hopper_bridge.py";
|
||||
const packedGhidraBridge = "package/bridge/ghidra/ReaGhidraBridge.java";
|
||||
const packedGhidraActivator =
|
||||
"package/bridge/ghidra/ReaGhidraBundleActivator.java";
|
||||
|
||||
/** Check shipped bridge files; real-provider lanes verify runtime behavior. */
|
||||
export async function verifyPackedBridge({ workspace, tarball, packedFiles }) {
|
||||
for (const path of [packedHopperBridge, packedGhidraBridge]) {
|
||||
for (const path of [
|
||||
packedHopperBridge,
|
||||
packedGhidraBridge,
|
||||
packedGhidraActivator,
|
||||
]) {
|
||||
if (!packedFiles.includes(path)) throw new Error(`package omitted ${path}`);
|
||||
}
|
||||
await exec("tar", ["-xf", tarball, "-C", workspace]);
|
||||
|
||||
@@ -10,6 +10,11 @@ import { pathToFileURL } from "node:url";
|
||||
import { lt, rcompare } from "semver";
|
||||
|
||||
import { CATALOG_IDENTITY } from "../dist/catalogIdentity.js";
|
||||
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
|
||||
import {
|
||||
requireMcpLifecycleResult,
|
||||
sortVerifierNames,
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
|
||||
|
||||
const verifierRun = createVerifierRun();
|
||||
@@ -54,27 +59,39 @@ try {
|
||||
await client.connect(transport);
|
||||
const tools = await client.listTools();
|
||||
const toolNames = tools.tools.map(({ name }) => name);
|
||||
const canonicalToolNames = new Set(
|
||||
CATALOG_IDENTITY.tools.map(({ name }) => name),
|
||||
);
|
||||
const canonicalToolNames = new Set(TOOL_CONTRACTS.map(({ name }) => name));
|
||||
const unknownToolNames = toolNames.filter(
|
||||
(name) => !canonicalToolNames.has(name),
|
||||
);
|
||||
const publishedToolNames = new Set(toolNames);
|
||||
const missingToolNames = [...canonicalToolNames].filter(
|
||||
(name) => !publishedToolNames.has(name),
|
||||
);
|
||||
publishedToolCount = toolNames.length;
|
||||
if (
|
||||
publishedToolCount === 0 ||
|
||||
new Set(toolNames).size !== publishedToolCount ||
|
||||
unknownToolNames.length > 0
|
||||
publishedToolNames.size !== publishedToolCount ||
|
||||
unknownToolNames.length > 0 ||
|
||||
missingToolNames.length > 0
|
||||
)
|
||||
throw new Error(
|
||||
`Published MCP exposed an invalid capability-scoped tool projection (${String(publishedToolCount)} tools, unknown: ${unknownToolNames.join(", ") || "none"})`,
|
||||
`Published MCP exposed an invalid canonical tool catalog (${String(publishedToolCount)} tools, unknown: ${unknownToolNames.join(", ") || "none"}, missing: ${missingToolNames.join(", ") || "none"})`,
|
||||
);
|
||||
const status = await client.callTool({
|
||||
const statusReply = await client.callTool({
|
||||
name: "binary_session",
|
||||
arguments: {},
|
||||
});
|
||||
if (status.isError === true)
|
||||
throw new Error("Published target-free MCP session check failed");
|
||||
const status = requireMcpLifecycleResult(statusReply, "binary_session");
|
||||
const availabilityNames = sortVerifierNames(
|
||||
status.tool_availability.map(({ name }) => name),
|
||||
);
|
||||
if (
|
||||
JSON.stringify(availabilityNames) !==
|
||||
JSON.stringify(sortVerifierNames(canonicalToolNames))
|
||||
)
|
||||
throw new Error(
|
||||
"Published binary_session omitted canonical tool availability",
|
||||
);
|
||||
update = await verifyPublishedUpdate({ version, canaryRoot });
|
||||
} catch (cause) {
|
||||
throw new Error(`Published package verification failed: ${stderr}`, {
|
||||
|
||||
@@ -158,7 +158,7 @@ try {
|
||||
observations: {
|
||||
...x.observations,
|
||||
source_files: x.observations.source_files.map(
|
||||
({ name, ...file }) => file,
|
||||
({ name: _name, ...file }) => file,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env node
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdtemp, mkdir, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
||||
import { buildMipsRelocatableFixture } from "../tests/conformance/ghidra/mips-fixture.mjs";
|
||||
import {
|
||||
assertMipsRelocations,
|
||||
inspectMipsRelocatableReadelf,
|
||||
} from "../tests/conformance/ghidra/mips-relocatable-checks.mjs";
|
||||
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
|
||||
// Optional cross-target lane for ET_REL objects. No tool acquisition, no
|
||||
// linking and no target execution.
|
||||
if (process.argv.length > 2)
|
||||
throw new Error(
|
||||
"Usage: node scripts/verify-real-ghidra-mips-relocatable.mjs",
|
||||
);
|
||||
if (!["linux", "darwin"].includes(process.platform))
|
||||
throw new Error(
|
||||
"MIPS verification requires a supported Linux/macOS Ghidra host",
|
||||
);
|
||||
if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
||||
throw new Error("Missing prerequisite: caller-supplied GHIDRA_INSTALL_DIR");
|
||||
const installation = inspectGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
? {}
|
||||
: { javaHome: process.env.JAVA_HOME }),
|
||||
});
|
||||
if (installation.status !== "available")
|
||||
throw new Error(
|
||||
`Ghidra prerequisite unavailable: ${JSON.stringify(installation)}`,
|
||||
);
|
||||
const execute = promisify(execFile);
|
||||
const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url));
|
||||
const run = createVerifierRun();
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-mips-rel-proof-"));
|
||||
const runtime = join(workspace, "runtime");
|
||||
await mkdir(runtime);
|
||||
const env = {
|
||||
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
||||
TMPDIR: runtime,
|
||||
REA_LOG_LEVEL: "silent",
|
||||
REA_ANALYSIS_PROVIDER: "ghidra",
|
||||
REA_PROCESS_RUN_ID: run.run_id,
|
||||
GHIDRA_INSTALL_DIR: installation.installDir,
|
||||
JAVA_HOME: installation.javaHome,
|
||||
HOPPER_LAUNCHER_PATH: "/rea-unconfigured-deep-provider/hopper",
|
||||
};
|
||||
for (const setting of ["GHIDRA_HEADLESS_MAXMEM", "GHIDRA_MAXMEM"]) {
|
||||
if (process.env[setting] !== undefined) env[setting] = process.env[setting];
|
||||
}
|
||||
|
||||
/** Load every relocated word through the public read_bytes operation. */
|
||||
async function loadedWords(call, independent, segments, byteOrder) {
|
||||
const words = new Map();
|
||||
for (const relocation of independent.relocations) {
|
||||
const section = [...independent.sections.values()].find(
|
||||
(item) => item.name === relocation.section,
|
||||
);
|
||||
if (!section?.allocated) continue;
|
||||
const segment = segments.find((item) => item.name === section.name);
|
||||
assert.ok(segment, `Ghidra segment ${section.name}`);
|
||||
const address = BigInt(segment.start) + BigInt(relocation.offset);
|
||||
const read = await call("read_bytes", {
|
||||
address: `0x${address.toString(16)}`,
|
||||
length: 4,
|
||||
});
|
||||
assert.equal(read.returned_bytes, 4);
|
||||
const bytes = Buffer.from(read.bytes_hex, "hex");
|
||||
words.set(
|
||||
address,
|
||||
byteOrder === "little" ? bytes.readUInt32LE(0) : bytes.readUInt32BE(0),
|
||||
);
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
async function verifyFixture(byteOrder, kernelModule) {
|
||||
const fixture = await buildMipsRelocatableFixture(
|
||||
workspace,
|
||||
byteOrder,
|
||||
kernelModule,
|
||||
);
|
||||
const independent = await inspectMipsRelocatableReadelf(
|
||||
fixture.path,
|
||||
byteOrder,
|
||||
fixture.float_abi,
|
||||
);
|
||||
const resolved = await parseBinaryTarget(fixture.path);
|
||||
if (!resolved.ok) throw resolved.error;
|
||||
assert.deepEqual(
|
||||
resolved.value.mips,
|
||||
independent.mips,
|
||||
"REA ELF/ABI interpretation disagrees with GNU readelf",
|
||||
);
|
||||
assert.equal(resolved.value.sha256, fixture.sha256);
|
||||
const label = `${byteOrder}${kernelModule ? "-module" : ""}`;
|
||||
const snapshotPath = join(workspace, `${label}.snapshot.json`);
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [entrypoint, "mcp"],
|
||||
env,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const client = new Client({ name: "rea-real-mips-rel-proof", version: "1" });
|
||||
let stderr = "";
|
||||
transport.stderr?.on("data", (chunk) => {
|
||||
stderr = (stderr + chunk.toString()).slice(-65536);
|
||||
});
|
||||
const call = async (name, args = {}) =>
|
||||
requireMcpOperationResult(
|
||||
await client.callTool({ name, arguments: args }, undefined, {
|
||||
timeout: 180000,
|
||||
}),
|
||||
name,
|
||||
);
|
||||
try {
|
||||
await client.connect(transport);
|
||||
const target = await call("open_binary", {
|
||||
path: fixture.path,
|
||||
provider_id: "ghidra",
|
||||
});
|
||||
assert.equal(target.architecture, "mips");
|
||||
assert.equal(target.sha256, fixture.sha256);
|
||||
const image = (await call("inspect_native_load_image")).observations;
|
||||
assert.equal(
|
||||
image.language_id,
|
||||
`MIPS:${byteOrder === "little" ? "LE" : "BE"}:32:default`,
|
||||
);
|
||||
assert.equal(image.source_files.length, 1);
|
||||
assert.equal(image.source_files[0].original_sha256, fixture.sha256);
|
||||
assert.notEqual(
|
||||
image.source_files[0].modified_sha256,
|
||||
fixture.sha256,
|
||||
"Relocations must change the loaded bytes",
|
||||
);
|
||||
const names = await call("list_names");
|
||||
const segments = await call("list_segments");
|
||||
const coverage = assertMipsRelocations({
|
||||
facts: independent,
|
||||
fileBytes: await readFile(fixture.path),
|
||||
segments,
|
||||
mappings: image.mappings,
|
||||
ghidraRelocations: image.relocations,
|
||||
words: await loadedWords(call, independent, segments, byteOrder),
|
||||
names,
|
||||
byteOrder,
|
||||
});
|
||||
assert.equal(coverage.unplaced, 0, "Fixture references an empty section");
|
||||
assert.ok(coverage.shared_hi16, "No HI16 shared by several LO16s");
|
||||
assert.ok(coverage.multiple_hi16, "No LO16 resolved several pending HI16s");
|
||||
assert.ok(coverage.lo16_carry, "No LO16 >= 0x8000 forced a HI16 carry");
|
||||
assert.ok(coverage.external_code, "No external code relocation");
|
||||
assert.ok(coverage.external_data, "No external data relocation");
|
||||
assert.ok(coverage.types.R_MIPS_32 > 0, "No R_MIPS_32 relocation");
|
||||
assert.equal(
|
||||
coverage.types.R_MIPS_26 === undefined,
|
||||
kernelModule,
|
||||
"Long calls must replace every R_MIPS_26 with HI16/LO16",
|
||||
);
|
||||
const procedures = await call("list_procedures");
|
||||
const named = (name) => {
|
||||
const found = procedures.filter((item) => item.value === name);
|
||||
assert.equal(found.length, 1, `Procedure ${name}`);
|
||||
return found[0].address;
|
||||
};
|
||||
const entry = named("rea_mips_rel_entry");
|
||||
const callees = await call("procedure_callees", { procedure: entry });
|
||||
for (const callee of [
|
||||
named("rea_mips_rel_leaf"),
|
||||
named("rea_mips_rel_far"),
|
||||
])
|
||||
assert.ok(callees.includes(callee), `Missing direct call to ${callee}`);
|
||||
// Ghidra lists the external function and its EXTERNAL-block thunk.
|
||||
assert.ok(
|
||||
procedures.some(
|
||||
(item) =>
|
||||
item.value === "<EXTERNAL>::rea_mips_rel_external" &&
|
||||
callees.includes(item.address),
|
||||
),
|
||||
"Missing call to the external function",
|
||||
);
|
||||
const dossier = functionDossierSchema.parse(
|
||||
await call("analyze_function", { procedure: entry }),
|
||||
);
|
||||
for (const name of [
|
||||
"rea_mips_rel_leaf",
|
||||
"rea_mips_rel_far",
|
||||
"rea_mips_rel_external",
|
||||
])
|
||||
assert.ok(dossier.pseudocode.includes(name), `Pseudocode lost ${name}`);
|
||||
// powf's float prototype comes from Ghidra's C library archive; soft-float
|
||||
// builds pass 1.5f/2.5f in $a0/$a1, hard-float builds in $f12/$f14.
|
||||
const scaled = functionDossierSchema.parse(
|
||||
await call("analyze_function", {
|
||||
procedure: named("rea_mips_rel_scaled"),
|
||||
}),
|
||||
);
|
||||
assert.match(scaled.pseudocode, /\bpowf\(1\.5,2\.5\)/u);
|
||||
assert.doesNotMatch(scaled.pseudocode, /\bin_f\d+\b/u);
|
||||
await call("close_binary", { snapshot_path: snapshotPath });
|
||||
const snapshot = parseAnalysisSnapshot(
|
||||
JSON.parse(await readFile(snapshotPath, "utf8")),
|
||||
);
|
||||
const parameters = snapshot.binding.analysis_profile.parameters;
|
||||
assert.equal(parameters.mips_elf.type, 1);
|
||||
assert.equal(
|
||||
parameters.mips_support_lane,
|
||||
"elf32-rel-o32-arch32r2-standard-v1",
|
||||
);
|
||||
assert.equal(
|
||||
parameters.mips_soft_float_storage,
|
||||
kernelModule ? "o32-soft-float-prototype-v1" : undefined,
|
||||
);
|
||||
assert.ok(
|
||||
snapshot.evidence_bundle.records.some((record) =>
|
||||
parseEvidence(record).limitations.some((item) =>
|
||||
item.includes("Relocatable MIPS ELF"),
|
||||
),
|
||||
),
|
||||
"Relocatable limitation missing from retained Evidence",
|
||||
);
|
||||
assert.equal(
|
||||
snapshot.evidence_bundle.records.some((record) =>
|
||||
parseEvidence(record).limitations.some((item) =>
|
||||
/Soft-float prototype outcome: [1-9]\d* functions/u.test(item),
|
||||
),
|
||||
),
|
||||
kernelModule,
|
||||
"Soft-float prototype must cover the archive-typed external in module builds",
|
||||
);
|
||||
await call("open_binary", {
|
||||
path: fixture.path,
|
||||
provider_id: "ghidra",
|
||||
snapshot_path: snapshotPath,
|
||||
});
|
||||
assert.deepEqual(await call("list_procedures"), procedures);
|
||||
await call("close_binary");
|
||||
// Same public operation through CLI, not a private injected provider.
|
||||
const cli = await execute(
|
||||
process.execPath,
|
||||
[
|
||||
entrypoint,
|
||||
"function",
|
||||
fixture.path,
|
||||
"rea_mips_rel_entry",
|
||||
"--provider",
|
||||
"ghidra",
|
||||
"--json",
|
||||
],
|
||||
{ env, timeout: 240000, maxBuffer: 16 * 1024 * 1024 },
|
||||
);
|
||||
const evidence = parseEvidence(JSON.parse(cli.stdout));
|
||||
assert.equal(evidence.subject?.architecture, "mips");
|
||||
assert.equal(evidence.subject?.digest.sha256, fixture.sha256);
|
||||
assert.equal(
|
||||
functionDossierSchema.parse(evidence.normalized_result).procedure.address,
|
||||
dossier.procedure.address,
|
||||
);
|
||||
assert.equal(
|
||||
createHash("sha256")
|
||||
.update(await readFile(fixture.path))
|
||||
.digest("hex"),
|
||||
fixture.sha256,
|
||||
"Analysis modified the source object",
|
||||
);
|
||||
return {
|
||||
...fixture,
|
||||
independent_reader: {
|
||||
command: independent.command,
|
||||
version: independent.version,
|
||||
},
|
||||
effective_language: image.language_id,
|
||||
relocation_coverage: coverage,
|
||||
procedures: procedures.length,
|
||||
status: "passed",
|
||||
};
|
||||
} catch (cause) {
|
||||
throw new Error(
|
||||
`MIPS ${label} relocatable verification failed; MCP stderr: ${stderr}`,
|
||||
{ cause },
|
||||
);
|
||||
} finally {
|
||||
try {
|
||||
await client.close();
|
||||
} finally {
|
||||
await transport.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const reports = [];
|
||||
let primaryFailure;
|
||||
try {
|
||||
for (const [byteOrder, kernelModule] of [
|
||||
["little", false],
|
||||
["big", false],
|
||||
["little", true],
|
||||
])
|
||||
reports.push(await verifyFixture(byteOrder, kernelModule));
|
||||
} catch (cause) {
|
||||
primaryFailure = cause;
|
||||
}
|
||||
const completed = await completeVerifierRun(run);
|
||||
if (
|
||||
completed.process_lineage.status !== "verified" ||
|
||||
completed.process_lineage.descendants.length !== 0
|
||||
)
|
||||
throw new Error(
|
||||
`Cleanup unverified; retained diagnostic workspace: ${workspace}`,
|
||||
{ cause: primaryFailure },
|
||||
);
|
||||
if (primaryFailure !== undefined)
|
||||
throw new Error(
|
||||
`MIPS relocatable verification failed; retained workspace: ${workspace}`,
|
||||
{ cause: primaryFailure },
|
||||
);
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
console.log(JSON.stringify({ ...completed, fixtures: reports }, null, 2));
|
||||
@@ -0,0 +1,338 @@
|
||||
#!/usr/bin/env node
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||
import { buildMips16SharedFixture } from "../tests/conformance/ghidra/mips16-shared-fixture.mjs";
|
||||
import {
|
||||
requireMcpOperationResult,
|
||||
requireMcpToolError,
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
|
||||
if (
|
||||
process.env.GHIDRA_INSTALL_DIR === undefined ||
|
||||
!["linux", "darwin"].includes(process.platform)
|
||||
)
|
||||
throw new Error(
|
||||
"This optional lane requires BYO Ghidra on Linux/macOS and GNU MIPS cross-binutils.",
|
||||
);
|
||||
const execute = promisify(execFile);
|
||||
const run = createVerifierRun();
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-mips-shared-proof-"));
|
||||
const runtime = join(workspace, "runtime");
|
||||
await mkdir(runtime);
|
||||
const env = {
|
||||
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
||||
TMPDIR: runtime,
|
||||
REA_LOG_LEVEL: "silent",
|
||||
REA_ANALYSIS_PROVIDER: "ghidra",
|
||||
REA_PROCESS_RUN_ID: run.run_id,
|
||||
GHIDRA_INSTALL_DIR: process.env.GHIDRA_INSTALL_DIR,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
? {}
|
||||
: { JAVA_HOME: process.env.JAVA_HOME }),
|
||||
...(process.env.GHIDRA_HEADLESS_MAXMEM === undefined
|
||||
? {}
|
||||
: { GHIDRA_HEADLESS_MAXMEM: process.env.GHIDRA_HEADLESS_MAXMEM }),
|
||||
};
|
||||
const reports = [];
|
||||
let failure;
|
||||
try {
|
||||
for (const byteOrder of ["big", "little"]) {
|
||||
for (const variant of [
|
||||
"declared",
|
||||
"recordless",
|
||||
"symbol-relative",
|
||||
"section-relocations",
|
||||
]) {
|
||||
const fixture = await buildMips16SharedFixture(
|
||||
workspace,
|
||||
byteOrder,
|
||||
variant,
|
||||
);
|
||||
const resolved = await parseBinaryTarget(fixture.path);
|
||||
if (!resolved.ok) throw resolved.error;
|
||||
assert.equal(resolved.value.sha256, fixture.sha256);
|
||||
assert.equal(resolved.value.architecture, "mips");
|
||||
if (variant === "section-relocations") {
|
||||
assert.equal(resolved.value.mips.sharedLoad.status, "unavailable");
|
||||
assert.match(
|
||||
resolved.value.mips.sharedLoad.reason,
|
||||
/section relocation declarations/u,
|
||||
);
|
||||
} else {
|
||||
assert.equal(resolved.value.mips.sharedLoad.status, "complete");
|
||||
assert.deepEqual(
|
||||
resolved.value.mips.sharedLoad.relocationTypes,
|
||||
[0, 3],
|
||||
);
|
||||
assert.equal(
|
||||
resolved.value.mips.sharedLoad.symbolRelativeCount,
|
||||
variant === "symbol-relative" ? 1 : 0,
|
||||
);
|
||||
}
|
||||
if (variant === "recordless")
|
||||
assert.equal(resolved.value.mips.abiFlags, null);
|
||||
else {
|
||||
assert.equal(resolved.value.mips.abiFlags.fpAbi, 5);
|
||||
assert.equal(resolved.value.mips.abiFlags.ases, 0x400);
|
||||
}
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: ["scripts/rea.mjs", "mcp"],
|
||||
cwd: process.cwd(),
|
||||
env,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const client = new Client({
|
||||
name: "rea-mips16-shared-conformance",
|
||||
version: "1",
|
||||
});
|
||||
let stderr = "";
|
||||
transport.stderr?.on("data", (chunk) => {
|
||||
stderr = (stderr + chunk.toString()).slice(-262144);
|
||||
});
|
||||
let calls = 0;
|
||||
const reply = async (name, args = {}) => {
|
||||
const response = await client.callTool(
|
||||
{ name, arguments: args },
|
||||
{ timeout: 180000 },
|
||||
);
|
||||
await writeFile(
|
||||
join(workspace, `${byteOrder}-${variant}-${++calls}-${name}.json`),
|
||||
JSON.stringify(response, null, 2),
|
||||
);
|
||||
return response;
|
||||
};
|
||||
const call = async (name, args) =>
|
||||
requireMcpOperationResult(await reply(name, args), name);
|
||||
const original = await readFile(fixture.path);
|
||||
try {
|
||||
await client.connect(transport);
|
||||
await client.listTools();
|
||||
if (
|
||||
variant === "symbol-relative" ||
|
||||
variant === "section-relocations"
|
||||
) {
|
||||
const refusal = requireMcpToolError(
|
||||
await reply("open_binary", {
|
||||
path: fixture.path,
|
||||
provider_id: "ghidra",
|
||||
}),
|
||||
);
|
||||
assert.equal(refusal.category, "unsupported_provider");
|
||||
assert.match(
|
||||
JSON.stringify(refusal),
|
||||
variant === "symbol-relative"
|
||||
? /Symbol-bearing R_MIPS_REL32/u
|
||||
: /section relocation declarations/u,
|
||||
);
|
||||
assert.deepEqual(await readFile(fixture.path), original);
|
||||
await client.ping();
|
||||
reports.push({ ...fixture, status: "refused", ping: true });
|
||||
continue;
|
||||
}
|
||||
await call("open_binary", {
|
||||
path: fixture.path,
|
||||
provider_id: "ghidra",
|
||||
});
|
||||
const loadedReply = await reply("inspect_native_load_image");
|
||||
const loaded = requireMcpOperationResult(
|
||||
loadedReply,
|
||||
"inspect_native_load_image",
|
||||
);
|
||||
const evidence = parseEvidence(
|
||||
JSON.parse(
|
||||
loadedReply.content.find((item) => item.type === "text").text,
|
||||
),
|
||||
);
|
||||
const language = `MIPS:${byteOrder === "little" ? "LE" : "BE"}:32:default`;
|
||||
assert.equal(loaded.observations.language_id, language);
|
||||
assert.equal(loaded.observations.compiler_spec_id, "default");
|
||||
assert(
|
||||
loaded.observations.source_files.some(
|
||||
(file) => file.original_sha256 === fixture.sha256,
|
||||
),
|
||||
);
|
||||
assert(
|
||||
evidence.limitations.some((item) =>
|
||||
item.includes("External GOT bindings"),
|
||||
),
|
||||
);
|
||||
if (variant === "recordless")
|
||||
assert(
|
||||
evidence.limitations.some((item) =>
|
||||
item.includes("does not establish the floating-point ABI"),
|
||||
),
|
||||
);
|
||||
const base = BigInt(loaded.observations.image_base);
|
||||
const address = (source, clearTag = false) =>
|
||||
`0x${(base + (clearTag ? BigInt(source) & ~1n : BigInt(source))).toString(16)}`;
|
||||
const compressed = address(fixture.symbols.rea_mips16_probe, true);
|
||||
const standard = address(fixture.symbols.rea_mips32_probe);
|
||||
const procedures = await call("list_procedures");
|
||||
assert(
|
||||
procedures.some(
|
||||
(item) =>
|
||||
item.address === compressed && item.value === "rea_mips16_probe",
|
||||
),
|
||||
);
|
||||
assert(
|
||||
procedures.some(
|
||||
(item) =>
|
||||
item.address === standard && item.value === "rea_mips32_probe",
|
||||
),
|
||||
);
|
||||
let firstInstruction;
|
||||
const expected =
|
||||
byteOrder === "little"
|
||||
? ["5a6a", "8eea", "074a", "a0e8"]
|
||||
: ["6a5a", "ea8e", "4a07", "e8a0"];
|
||||
for (let i = 0; i < 4; i += 1) {
|
||||
const instruction = await call("inspect_native_instruction", {
|
||||
address: `0x${(BigInt(compressed) + BigInt(2 * i)).toString(16)}`,
|
||||
});
|
||||
assert.equal(instruction.status, "decoded");
|
||||
assert.equal(instruction.mode, "default");
|
||||
assert.equal(instruction.length, 2);
|
||||
assert.equal(instruction.bytes, expected[i]);
|
||||
assert.deepEqual(instruction.decoder_context, [
|
||||
{
|
||||
register: "ISA_MODE",
|
||||
value: "1",
|
||||
source: "ghidra-program-context",
|
||||
},
|
||||
]);
|
||||
if (i === 0) firstInstruction = instruction;
|
||||
}
|
||||
const standardInstruction = await call("inspect_native_instruction", {
|
||||
address: standard,
|
||||
});
|
||||
assert.equal(standardInstruction.length, 4);
|
||||
assert.equal(
|
||||
standardInstruction.bytes,
|
||||
byteOrder === "little" ? "03008224" : "24820003",
|
||||
);
|
||||
assert.equal(
|
||||
standardInstruction.decoder_context[0].register,
|
||||
"ISA_MODE",
|
||||
);
|
||||
assert(
|
||||
[null, "0"].includes(standardInstruction.decoder_context[0].value),
|
||||
);
|
||||
const pointer = await call("read_bytes", {
|
||||
address: address(fixture.symbols.rea_mips16_pointer),
|
||||
length: 4,
|
||||
});
|
||||
const pointerBytes = Buffer.from(pointer.bytes_hex, "hex");
|
||||
const pointerValue =
|
||||
byteOrder === "little"
|
||||
? pointerBytes.readUInt32LE(0)
|
||||
: pointerBytes.readUInt32BE(0);
|
||||
assert.equal(
|
||||
BigInt(pointerValue),
|
||||
base + BigInt(fixture.symbols.rea_mips16_probe),
|
||||
);
|
||||
const dossier = await call("analyze_function", {
|
||||
procedure: compressed,
|
||||
});
|
||||
assert.match(dossier.pseudocode, /\^\s*0x5a/u);
|
||||
assert.match(dossier.pseudocode, /\+\s*(?:7|0x7)\b/u);
|
||||
const standardDossier = await call("analyze_function", {
|
||||
procedure: standard,
|
||||
});
|
||||
assert.match(standardDossier.pseudocode, /\+\s*(?:3|0x3)\b/u);
|
||||
const snapshotPath = join(
|
||||
workspace,
|
||||
`${byteOrder}-${variant}.snapshot.json`,
|
||||
);
|
||||
await call("close_binary", { snapshot_path: snapshotPath });
|
||||
const snapshot = parseAnalysisSnapshot(
|
||||
JSON.parse(await readFile(snapshotPath, "utf8")),
|
||||
);
|
||||
assert.equal(
|
||||
snapshot.binding.analysis_profile.parameters.language_selection,
|
||||
"provider-pinned-v1",
|
||||
);
|
||||
assert.equal(
|
||||
snapshot.binding.analysis_profile.parameters.language_id,
|
||||
language,
|
||||
);
|
||||
await call("open_binary", {
|
||||
path: fixture.path,
|
||||
provider_id: "ghidra",
|
||||
snapshot_path: snapshotPath,
|
||||
});
|
||||
assert.deepEqual(
|
||||
await call("inspect_native_instruction", { address: compressed }),
|
||||
firstInstruction,
|
||||
);
|
||||
await call("close_binary");
|
||||
await client.ping();
|
||||
const cli = await execute(
|
||||
process.execPath,
|
||||
[
|
||||
"scripts/rea.mjs",
|
||||
"inspect-native-instruction",
|
||||
fixture.path,
|
||||
compressed,
|
||||
"--provider",
|
||||
"ghidra",
|
||||
"--format",
|
||||
"json",
|
||||
],
|
||||
{ env, timeout: 180000, maxBuffer: 8 * 1024 * 1024 },
|
||||
);
|
||||
assert.deepEqual(
|
||||
parseEvidence(JSON.parse(cli.stdout)).normalized_result,
|
||||
firstInstruction,
|
||||
);
|
||||
assert.deepEqual(await readFile(fixture.path), original);
|
||||
reports.push({
|
||||
...fixture,
|
||||
status: "passed",
|
||||
language_id: language,
|
||||
mixed_mode: true,
|
||||
standard_decoder_context: standardInstruction.decoder_context,
|
||||
pointer_tag_preserved: true,
|
||||
snapshot_context_preserved: true,
|
||||
cli_mcp_parity: true,
|
||||
source_unchanged: true,
|
||||
ping: true,
|
||||
});
|
||||
} finally {
|
||||
await client.close();
|
||||
await transport.close();
|
||||
await writeFile(
|
||||
join(workspace, `${byteOrder}-${variant}.stderr`),
|
||||
stderr,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (cause) {
|
||||
failure = cause;
|
||||
}
|
||||
const completed = await completeVerifierRun(run);
|
||||
if (
|
||||
completed.process_lineage.status !== "verified" ||
|
||||
completed.process_lineage.descendants.length !== 0
|
||||
)
|
||||
throw new Error(`Cleanup unverified; retained workspace: ${workspace}`, {
|
||||
cause: failure,
|
||||
});
|
||||
if (failure !== undefined)
|
||||
throw new Error(
|
||||
`MIPS16 shared verification failed; retained workspace: ${workspace}`,
|
||||
{ cause: failure },
|
||||
);
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
console.log(JSON.stringify({ ...completed, fixtures: reports }, null, 2));
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
assertMipsLoadedImage,
|
||||
assertMipsProbe,
|
||||
assertMipsGlobal,
|
||||
assertMipsSoftFloatStorage,
|
||||
} from "../tests/conformance/ghidra/mips-checks.mjs";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||
@@ -77,16 +78,64 @@ const env = {
|
||||
for (const setting of ["GHIDRA_HEADLESS_MAXMEM", "GHIDRA_MAXMEM"]) {
|
||||
if (process.env[setting] !== undefined) env[setting] = process.env[setting];
|
||||
}
|
||||
|
||||
/**
|
||||
* Typed float signatures must use soft-float o32 storage, and callers must see
|
||||
* the source constants through it rather than FPU inputs.
|
||||
*/
|
||||
async function verifySoftFloat(call, symbols, byteOrder) {
|
||||
const boundaries = {};
|
||||
for (const name of [
|
||||
"rea_mips_soft_pick",
|
||||
"rea_mips_soft_wide",
|
||||
"rea_mips_soft_mixed",
|
||||
"rea_mips_soft_struct",
|
||||
]) {
|
||||
const api = await call("inspect_native_api", { procedure: symbols[name] });
|
||||
boundaries[name] = api.boundary;
|
||||
}
|
||||
assertMipsSoftFloatStorage(boundaries, byteOrder);
|
||||
const caller = functionDossierSchema.parse(
|
||||
await call("analyze_function", {
|
||||
procedure: symbols.rea_mips_soft_caller,
|
||||
}),
|
||||
);
|
||||
// Includes the struct call and the indirect call through a typed pointer.
|
||||
assert.doesNotMatch(caller.pseudocode, /\bin_f\d+\b/u);
|
||||
for (const literal of [
|
||||
"1.5",
|
||||
"2.5",
|
||||
"3.25",
|
||||
"4.75",
|
||||
"5.5",
|
||||
"6.125",
|
||||
"7.5",
|
||||
"8.5",
|
||||
"9.5",
|
||||
])
|
||||
assert.ok(
|
||||
caller.pseudocode.includes(literal),
|
||||
`Soft-float caller lost argument ${literal}`,
|
||||
);
|
||||
return { storage: boundaries };
|
||||
}
|
||||
const reports = [];
|
||||
let primaryFailure;
|
||||
try {
|
||||
for (const byteOrder of psp ? ["little"] : ["little", "big"]) {
|
||||
for (const [byteOrder, floatAbi] of psp
|
||||
? [["little", "hard"]]
|
||||
: [
|
||||
["little", "hard"],
|
||||
["big", "hard"],
|
||||
["little", "soft"],
|
||||
["big", "soft"],
|
||||
]) {
|
||||
const fixture = psp
|
||||
? await buildPspFixture(workspace)
|
||||
: await buildMipsFixture(workspace, byteOrder);
|
||||
: await buildMipsFixture(workspace, byteOrder, floatAbi);
|
||||
const independent = psp
|
||||
? fixture
|
||||
: await inspectMipsReadelf(fixture.path, byteOrder);
|
||||
: await inspectMipsReadelf(fixture.path, byteOrder, floatAbi);
|
||||
const resolved = await parseBinaryTarget(fixture.path);
|
||||
if (!resolved.ok) throw resolved.error;
|
||||
if (psp) {
|
||||
@@ -104,7 +153,10 @@ try {
|
||||
);
|
||||
}
|
||||
assert.equal(resolved.value.sha256, fixture.sha256);
|
||||
const snapshotPath = join(workspace, `${byteOrder}.snapshot.json`);
|
||||
const snapshotPath = join(
|
||||
workspace,
|
||||
`${byteOrder}-${floatAbi}.snapshot.json`,
|
||||
);
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [entrypoint, "mcp"],
|
||||
@@ -238,6 +290,10 @@ try {
|
||||
callees.includes(leaf.address),
|
||||
"Expected direct call not recovered",
|
||||
);
|
||||
const softFloat =
|
||||
floatAbi === "soft"
|
||||
? await verifySoftFloat(call, independent.symbols, byteOrder)
|
||||
: undefined;
|
||||
const assembly = await call("procedure_assembly", {
|
||||
procedure: entry.address,
|
||||
});
|
||||
@@ -293,6 +349,19 @@ try {
|
||||
},
|
||||
"Snapshot profile did not retain the independently inspected ELF/ABI declaration",
|
||||
);
|
||||
assert.equal(
|
||||
snapshot.binding.analysis_profile.parameters.mips_soft_float_storage,
|
||||
floatAbi === "soft" ? "o32-soft-float-prototype-v1" : undefined,
|
||||
);
|
||||
assert.equal(
|
||||
snapshot.evidence_bundle.records.some((record) =>
|
||||
/Soft-float prototype outcome: 4 functions and [1-9]\d* function-pointer definitions use __rea_soft_float\./u.test(
|
||||
JSON.stringify(record),
|
||||
),
|
||||
),
|
||||
floatAbi === "soft",
|
||||
"Soft-float prototype outcome missing from, or unexpected in, retained Evidence",
|
||||
);
|
||||
assert.ok(snapshot.evidence_bundle.records.length > 0);
|
||||
if (psp) {
|
||||
assert.equal(
|
||||
@@ -419,7 +488,10 @@ try {
|
||||
assert.ok(read32(offset + 4) + read32(offset + 16) <= cut);
|
||||
}
|
||||
const carvedBytes = sourceBytes.subarray(0, cut);
|
||||
const carvedPath = join(workspace, `${byteOrder}-carved.elf`);
|
||||
const carvedPath = join(
|
||||
workspace,
|
||||
`${byteOrder}-${floatAbi}-carved.elf`,
|
||||
);
|
||||
const carvedSha256 = createHash("sha256")
|
||||
.update(carvedBytes)
|
||||
.digest("hex");
|
||||
@@ -460,7 +532,7 @@ try {
|
||||
assert.ok(carvedDossier.pseudocode.trim().length > 0);
|
||||
const carvedSnapshotPath = join(
|
||||
workspace,
|
||||
`${byteOrder}-carved.snapshot.json`,
|
||||
`${byteOrder}-${floatAbi}-carved.snapshot.json`,
|
||||
);
|
||||
await call("close_binary", { snapshot_path: carvedSnapshotPath });
|
||||
const carvedSnapshot = parseAnalysisSnapshot(
|
||||
@@ -523,7 +595,11 @@ try {
|
||||
: "conditional branch destination",
|
||||
"global word and marker bytes",
|
||||
"CLI/MCP and snapshot identity",
|
||||
...(floatAbi === "soft"
|
||||
? ["soft-float o32 parameter/return storage and caller constants"]
|
||||
: []),
|
||||
],
|
||||
...(softFloat === undefined ? {} : { soft_float: softFloat }),
|
||||
procedures: procedures.length,
|
||||
carved: carved ?? {
|
||||
status: "not_run",
|
||||
@@ -534,7 +610,7 @@ try {
|
||||
});
|
||||
} catch (cause) {
|
||||
throw new Error(
|
||||
`MIPS ${byteOrder} verification failed; MCP stderr: ${stderr}`,
|
||||
`MIPS ${byteOrder} ${floatAbi}-float verification failed; MCP stderr: ${stderr}`,
|
||||
{ cause },
|
||||
);
|
||||
} finally {
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
||||
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
|
||||
if (process.argv.length !== 2)
|
||||
throw new Error("Usage: node scripts/verify-real-ghidra-raw.mjs");
|
||||
if (!["linux", "darwin"].includes(process.platform))
|
||||
throw new Error(
|
||||
"Raw-binary verification requires a Linux or macOS Ghidra host.",
|
||||
);
|
||||
if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
||||
throw new Error(
|
||||
"Set GHIDRA_INSTALL_DIR to a bring-your-own Ghidra installation.",
|
||||
);
|
||||
const installation = inspectGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
? {}
|
||||
: { javaHome: process.env.JAVA_HOME }),
|
||||
});
|
||||
assert.equal(installation.status, "available", JSON.stringify(installation));
|
||||
|
||||
const run = createVerifierRun();
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-raw-proof-"));
|
||||
const runtime = join(workspace, "runtime");
|
||||
await mkdir(runtime);
|
||||
const bytes = Buffer.from("1eff2fe1", "hex"); // ARM little-endian bx lr.
|
||||
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
||||
const path = join(workspace, "image.bin");
|
||||
await writeFile(path, bytes);
|
||||
const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url));
|
||||
const env = {
|
||||
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
||||
TMPDIR: runtime,
|
||||
REA_LOG_LEVEL: "silent",
|
||||
REA_ANALYSIS_PROVIDER: "ghidra",
|
||||
REA_PROCESS_RUN_ID: run.run_id,
|
||||
GHIDRA_INSTALL_DIR: process.env.GHIDRA_INSTALL_DIR,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
? {}
|
||||
: { JAVA_HOME: process.env.JAVA_HOME }),
|
||||
...(process.env.GHIDRA_HEADLESS_MAXMEM === undefined
|
||||
? {}
|
||||
: { GHIDRA_HEADLESS_MAXMEM: process.env.GHIDRA_HEADLESS_MAXMEM }),
|
||||
...(process.env.REA_GHIDRA_DISABLED_ANALYZERS === undefined
|
||||
? {}
|
||||
: {
|
||||
REA_GHIDRA_DISABLED_ANALYZERS:
|
||||
process.env.REA_GHIDRA_DISABLED_ANALYZERS,
|
||||
}),
|
||||
};
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [entrypoint, "mcp"],
|
||||
env,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const client = new Client({ name: "rea-real-raw-proof", version: "1" });
|
||||
let stderr = "";
|
||||
transport.stderr?.on("data", (chunk) => {
|
||||
stderr = (stderr + chunk.toString()).slice(-65536);
|
||||
});
|
||||
let report;
|
||||
try {
|
||||
await client.connect(transport);
|
||||
const profiles = [];
|
||||
const sessions = [];
|
||||
for (const entryPoint of ["0x08000000", "0x8000000", "0x09000000"]) {
|
||||
// Omit provider_id to exercise reuse of the current provider route.
|
||||
const opened = await call("open_binary", {
|
||||
path,
|
||||
format: "raw-binary",
|
||||
architecture: "arm",
|
||||
entry_point: entryPoint,
|
||||
});
|
||||
const entry = `0x${BigInt(entryPoint).toString(16)}`;
|
||||
assert.equal(opened.entry_point, entry);
|
||||
const image = await call("inspect_native_load_image");
|
||||
assert.equal(
|
||||
image.normalized_result.status,
|
||||
"verified",
|
||||
JSON.stringify(image),
|
||||
);
|
||||
assert.equal(image.normalized_result.base_address, entry);
|
||||
assert.equal(image.normalized_result.entry.address, entry);
|
||||
assert.equal(
|
||||
image.normalized_result.observations.language_id,
|
||||
"ARM:LE:32:v7",
|
||||
);
|
||||
assert.ok(
|
||||
image.normalized_result.observations.entry_points.includes(entry),
|
||||
);
|
||||
assert.equal(image.analysis_profile.parameters.entry_point, entry);
|
||||
profiles.push(image.analysis_profile.digest);
|
||||
const status = await call("binary_session");
|
||||
assert.equal(status.entry_point, entry);
|
||||
sessions.push(status.analysis_run.run_id);
|
||||
const memory = await call("read_bytes", {
|
||||
address: entry,
|
||||
length: bytes.length,
|
||||
});
|
||||
assert.equal(memory.normalized_result.bytes_hex, bytes.toString("hex"));
|
||||
const procedures = await call("list_procedures");
|
||||
assert.ok(
|
||||
procedures.normalized_result.some(
|
||||
(procedure) => procedure.address === entry,
|
||||
),
|
||||
);
|
||||
const instruction = await call("inspect_native_instruction", {
|
||||
address: entry,
|
||||
});
|
||||
assert.equal(instruction.normalized_result.address, entry);
|
||||
}
|
||||
assert.equal(profiles[0], profiles[1]);
|
||||
assert.notEqual(profiles[1], profiles[2]);
|
||||
assert.equal(sessions[0], sessions[1]);
|
||||
assert.notEqual(sessions[1], sessions[2]);
|
||||
const rejected = await client.callTool({
|
||||
name: "open_binary",
|
||||
arguments: {
|
||||
path,
|
||||
format: "raw-binary",
|
||||
architecture: "arm64",
|
||||
entry_point: "0x8000001",
|
||||
},
|
||||
});
|
||||
assert.equal(rejected.isError, true);
|
||||
assert.match(JSON.stringify(rejected.content), /must be 4-byte aligned/);
|
||||
const unchanged = await call("binary_session");
|
||||
assert.equal(unchanged.analysis_run.run_id, sessions[2]);
|
||||
assert.equal(unchanged.entry_point, "0x9000000");
|
||||
await call("close_binary");
|
||||
|
||||
const { stdout } = await promisify(execFile)(
|
||||
process.execPath,
|
||||
[
|
||||
entrypoint,
|
||||
"inspect-native-load-image",
|
||||
path,
|
||||
"--target-format",
|
||||
"raw-binary",
|
||||
"--architecture",
|
||||
"arm",
|
||||
"--entry-point",
|
||||
"0x08000000",
|
||||
"--provider",
|
||||
"ghidra",
|
||||
"--json",
|
||||
],
|
||||
{ env, timeout: 240000, maxBuffer: 16 * 1024 * 1024 },
|
||||
);
|
||||
const cli = parseEvidence(JSON.parse(stdout));
|
||||
assert.equal(cli.subject.digest.sha256, sha256);
|
||||
assert.equal(cli.normalized_result.status, "verified");
|
||||
assert.equal(cli.normalized_result.base_address, "0x8000000");
|
||||
assert.equal(cli.analysis_profile.digest, profiles[0]);
|
||||
assert.deepEqual(await readFile(path), bytes);
|
||||
const x86Targets = [];
|
||||
for (const architecture of ["x86", "x86_64"]) {
|
||||
const x86Bytes = Buffer.from("b82a000000c3", "hex"); // mov eax, 42; ret.
|
||||
const x86Sha256 = createHash("sha256").update(x86Bytes).digest("hex");
|
||||
const x86Path = join(workspace, `${architecture}.bin`);
|
||||
await writeFile(x86Path, x86Bytes);
|
||||
await call("open_binary", {
|
||||
path: x86Path,
|
||||
format: "raw-binary",
|
||||
architecture,
|
||||
entry_point: "0x1000",
|
||||
});
|
||||
const image = await call("inspect_native_load_image", {}, x86Sha256);
|
||||
assert.equal(
|
||||
image.normalized_result.status,
|
||||
"verified",
|
||||
JSON.stringify(image),
|
||||
);
|
||||
assert.equal(image.normalized_result.base_address, "0x1000");
|
||||
assert.equal(image.normalized_result.observations.compiler_spec_id, "gcc");
|
||||
assert.equal(image.analysis_profile.parameters.compiler_spec_id, "gcc");
|
||||
const memory = await call(
|
||||
"read_bytes",
|
||||
{ address: "0x1000", length: x86Bytes.length },
|
||||
x86Sha256,
|
||||
);
|
||||
assert.equal(memory.normalized_result.bytes_hex, x86Bytes.toString("hex"));
|
||||
const pseudo = await call(
|
||||
"procedure_pseudo_code",
|
||||
{ procedure: "0x1000" },
|
||||
x86Sha256,
|
||||
);
|
||||
assert.match(pseudo.normalized_result, /\b(?:42|0x2a)\b/iu);
|
||||
await call("close_binary");
|
||||
assert.deepEqual(await readFile(x86Path), x86Bytes);
|
||||
x86Targets.push({
|
||||
architecture,
|
||||
sha256: x86Sha256,
|
||||
compiler_spec_id: "gcc",
|
||||
return_42: true,
|
||||
});
|
||||
}
|
||||
report = {
|
||||
ok: true,
|
||||
mocked: false,
|
||||
lane: "ghidra-raw",
|
||||
host: process.platform,
|
||||
provider: { id: "ghidra", version: installation.providerVersion },
|
||||
target: {
|
||||
format: "raw-binary",
|
||||
architecture: "arm",
|
||||
sha256,
|
||||
bytes: bytes.length,
|
||||
},
|
||||
transport: { cli: true, stdio_mcp: true },
|
||||
canonical_entry_reuses_session: true,
|
||||
changed_entry_replaces_session: true,
|
||||
lifecycle_entry_preserved: true,
|
||||
invalid_entry_preserves_active_session: true,
|
||||
load_images_verified: true,
|
||||
original_unchanged: true,
|
||||
x86_targets: x86Targets,
|
||||
};
|
||||
} catch (error) {
|
||||
if (stderr.trim()) process.stderr.write(stderr);
|
||||
throw error;
|
||||
} finally {
|
||||
await client.close();
|
||||
await transport.close();
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
}
|
||||
const verifierRun = await completeVerifierRun(run);
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
verifier_run: verifierRun,
|
||||
...report,
|
||||
cleanup: "complete",
|
||||
}) + "\n",
|
||||
);
|
||||
|
||||
async function call(name, args = {}, targetSha256 = sha256) {
|
||||
const result = await client.callTool(
|
||||
{ name, arguments: args },
|
||||
{ timeout: 240000 },
|
||||
);
|
||||
assert.notEqual(result.isError, true, JSON.stringify(result));
|
||||
const projected = requireMcpOperationResult(result, name);
|
||||
if (
|
||||
name === "open_binary" ||
|
||||
name === "close_binary" ||
|
||||
name === "binary_session"
|
||||
)
|
||||
return projected;
|
||||
const evidence = parseEvidence(result.structuredContent);
|
||||
assert.equal(evidence.subject.digest.sha256, targetSha256);
|
||||
assert.equal(evidence.subject.format, "raw-binary");
|
||||
return evidence;
|
||||
}
|
||||
@@ -21,14 +21,17 @@ import { SUPPORTED_GHIDRA_VERSION } from "../dist/ghidra/GhidraInstallationPolic
|
||||
import { GhidraHeadlessLauncher } from "../dist/ghidra/GhidraLauncher.js";
|
||||
import { GHIDRA_PROVIDER_IDENTITY } from "../dist/ghidra/GhidraProviderCapabilities.js";
|
||||
import { windowsP0Capabilities } from "../dist/ghidra/GhidraProviderCapabilities.js";
|
||||
import { GHIDRA_SESSION_CAPABILITIES } from "../dist/ghidra/GhidraSessionValues.js";
|
||||
import {
|
||||
GHIDRA_MUTATING_OPERATIONS,
|
||||
GHIDRA_SESSION_CAPABILITIES,
|
||||
} from "../dist/ghidra/GhidraSessionValues.js";
|
||||
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { verifyGhidraCompactReferences } from "./lib/real-ghidra-compact-references.mjs";
|
||||
|
||||
const verifierRun = createVerifierRun();
|
||||
const SESSION_CAPABILITIES = GHIDRA_SESSION_CAPABILITIES.filter(
|
||||
(name) => name !== "annotate_native_function",
|
||||
(name) => !GHIDRA_MUTATING_OPERATIONS.has(name),
|
||||
);
|
||||
|
||||
if (process.platform !== "win32" || process.arch !== "x64")
|
||||
@@ -236,10 +239,8 @@ try {
|
||||
|
||||
const expected = [
|
||||
...GHIDRA_INVENTORY_OPERATIONS,
|
||||
...GHIDRA_FUNCTION_OPERATIONS.filter(
|
||||
(name) => name !== "annotate_native_function",
|
||||
),
|
||||
];
|
||||
...GHIDRA_FUNCTION_OPERATIONS,
|
||||
].filter((name) => !GHIDRA_MUTATING_OPERATIONS.has(name));
|
||||
if (
|
||||
observed.size !== expected.length ||
|
||||
expected.some((operation) => !observed.has(operation))
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
requireMcpOperationResult,
|
||||
requireMcpLifecycleResult,
|
||||
requireMcpEvidenceResult,
|
||||
sortVerifierNames,
|
||||
requireWorkflowEvidenceProvider,
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
import {
|
||||
@@ -283,8 +284,10 @@ try {
|
||||
const availableTools = initialStatus.tool_availability;
|
||||
if (!Array.isArray(availableTools))
|
||||
throw new Error("The real session omitted tool availability");
|
||||
const expectedNames = availableTools.map(({ name }) => name).sort();
|
||||
const actualNames = listed.tools.map(({ name }) => name).sort();
|
||||
const expectedNames = sortVerifierNames(
|
||||
availableTools.map(({ name }) => name),
|
||||
);
|
||||
const actualNames = sortVerifierNames(listed.tools.map(({ name }) => name));
|
||||
if (JSON.stringify(actualNames) !== JSON.stringify(expectedNames)) {
|
||||
const expected = new Set(expectedNames);
|
||||
const actual = new Set(actualNames);
|
||||
|
||||
@@ -122,15 +122,44 @@ try {
|
||||
new URL("../tests/fixtures/wasm/custom-names.wat", import.meta.url),
|
||||
"utf8",
|
||||
),
|
||||
];
|
||||
for (const [index, source] of sources.entries()) {
|
||||
`(module
|
||||
(memory 1)
|
||||
(data "x")
|
||||
(func (export "init")
|
||||
i32.const 0
|
||||
i32.const 0
|
||||
i32.const 1
|
||||
memory.init 0
|
||||
data.drop 0))\n`,
|
||||
].map((source) => ({ source, debugNames: false }));
|
||||
sources.push(
|
||||
{
|
||||
source:
|
||||
'(module $named (func (export "answer") (result i32) i32.const 42))\n',
|
||||
debugNames: true,
|
||||
},
|
||||
{
|
||||
source: await readFile(
|
||||
new URL("../tests/fixtures/wasm/module-names.wat", import.meta.url),
|
||||
"utf8",
|
||||
),
|
||||
debugNames: false,
|
||||
},
|
||||
);
|
||||
for (const [index, { source, debugNames }] of sources.entries()) {
|
||||
const fixtureRoot = join(root.path, `fixture-${index}`);
|
||||
await mkdir(fixtureRoot);
|
||||
const path = join(fixtureRoot, "module.wasm");
|
||||
await writeFile(join(fixtureRoot, "source.wat"), source);
|
||||
await tool(
|
||||
"wat2wasm",
|
||||
["--enable-all", "source.wat", "-o", "module.wasm"],
|
||||
[
|
||||
"--enable-all",
|
||||
...(debugNames ? ["--debug-names"] : []),
|
||||
"source.wat",
|
||||
"-o",
|
||||
"module.wasm",
|
||||
],
|
||||
fixtureRoot,
|
||||
);
|
||||
await tool("wasm-validate", ["--enable-all", "module.wasm"], fixtureRoot);
|
||||
@@ -218,8 +247,29 @@ try {
|
||||
'"test\nline"',
|
||||
'"fake"\nSection Details:\n Type start=0x0000000a end=0x0000000b (size=0x00000001) count: 0\n"end"',
|
||||
'"unicode-é\\\t"',
|
||||
'"visible"',
|
||||
],
|
||||
);
|
||||
if (debugNames) {
|
||||
assert.ok(report.headers.includes("\nmodule name: <named>\n"));
|
||||
assert.deepEqual(report.exports, ['(export "answer" (func 0))']);
|
||||
}
|
||||
if (index === 4) {
|
||||
assert.match(
|
||||
headers,
|
||||
/^DataCount start=0x[\da-f]+ end=0x[\da-f]+ \(size=0x[\da-f]+\) count: 1$/m,
|
||||
);
|
||||
assert.deepEqual(
|
||||
report.sections.find(({ kind }) => kind === "DataCount"),
|
||||
{
|
||||
kind: "DataCount",
|
||||
start: 0x23,
|
||||
end: 0x24,
|
||||
bytes: 1,
|
||||
description: "count: 1",
|
||||
},
|
||||
);
|
||||
}
|
||||
const bundleReply = await client.callTool({
|
||||
name: "get_evidence_bundle",
|
||||
arguments: {},
|
||||
|
||||
@@ -913,6 +913,30 @@ async function inspect(mode, input, errorCategory) {
|
||||
}
|
||||
assert.notEqual(result.isError, true, mcpTextValue(result));
|
||||
assertSensitiveLimitations(value, input.sensitive_values ?? []);
|
||||
if (input.format === "har") {
|
||||
const surface = await client.callTool({
|
||||
name: "inspect_analysis_view",
|
||||
arguments: {
|
||||
source: { kind: "retained-evidence", evidence_id: value.evidence_id },
|
||||
view: { kind: "api-surface" },
|
||||
},
|
||||
});
|
||||
if ((input.sensitive_values ?? []).length > 0) {
|
||||
// Derived view text cannot be checked against undisclosed declarations.
|
||||
assert.equal(surface.isError, true);
|
||||
const refused = JSON.parse(mcpTextValue(surface));
|
||||
for (const literal of input.sensitive_values)
|
||||
assert.ok(!JSON.stringify(refused).includes(literal));
|
||||
} else {
|
||||
assert.notEqual(surface.isError, true, mcpTextValue(surface));
|
||||
const projected = JSON.parse(mcpTextValue(surface));
|
||||
assert.equal(projected.normalized_result.kind, "api-surface");
|
||||
assert.equal(
|
||||
projected.normalized_result.records.examined,
|
||||
value.normalized_result.records.length,
|
||||
);
|
||||
}
|
||||
}
|
||||
return value.normalized_result;
|
||||
}
|
||||
const args = [
|
||||
|
||||
@@ -7,13 +7,16 @@ import { promisify } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseEvidence } from "../../../dist/domain/evidence.js";
|
||||
import { hashAndroidFile } from "../../../dist/android/AndroidTargetSnapshot.js";
|
||||
import { hashAndroidFile } from "./digest.mjs";
|
||||
import { JADX_RELEASE } from "../../../dist/android/JadxRelease.js";
|
||||
import fixture from "../../fixtures/android/apidemos.json" with { type: "json" };
|
||||
import { verifyAndroidCliCancellation } from "./cli-cancellation.mjs";
|
||||
import { verifyAndroidMetadata } from "./metadata.mjs";
|
||||
import { verifyAndroidFailureRecovery } from "./failure-recovery.mjs";
|
||||
import { resolveJadxConfiguration } from "../../../dist/android/JadxConfiguration.js";
|
||||
import {
|
||||
resolveJadxConfigurationInputs,
|
||||
validateJadxConfiguration,
|
||||
} from "../../../dist/android/JadxConfiguration.js";
|
||||
import { verifyWindowsAndroidLifecycle } from "./windows-lifecycle.mjs";
|
||||
|
||||
const repository = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
|
||||
@@ -35,10 +38,16 @@ assert.equal(
|
||||
JADX_RELEASE.sha256,
|
||||
"Real Android lane requires the audited headless JADX release JAR",
|
||||
);
|
||||
const { java, jvmArguments } = await resolveJadxConfiguration(
|
||||
const configuration = await resolveJadxConfigurationInputs(
|
||||
process.env,
|
||||
"inspect_android_package",
|
||||
);
|
||||
await validateJadxConfiguration(
|
||||
configuration,
|
||||
process.env,
|
||||
"inspect_android_package",
|
||||
);
|
||||
const { java, jvmArguments } = configuration;
|
||||
const execute = promisify(execFile);
|
||||
await execute(java, ["-version"], { timeout: 10_000 });
|
||||
const entrypoint =
|
||||
@@ -52,6 +61,8 @@ await verifyAndroidMetadata({
|
||||
repository,
|
||||
execute,
|
||||
jvmArguments,
|
||||
entrypoint,
|
||||
environment,
|
||||
bridge: resolve(dirname(entrypoint), "../bridge/android/ReaJadxBridge.java"),
|
||||
});
|
||||
const tasks = [
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { createReadStream } from "node:fs";
|
||||
|
||||
/** Verify artifact identity without depending on private runtime snapshot helpers. */
|
||||
export const hashAndroidFile = async (path) => {
|
||||
const hash = createHash("sha256");
|
||||
for await (const bytes of createReadStream(path)) hash.update(bytes);
|
||||
return hash.digest("hex");
|
||||
};
|
||||
@@ -1,4 +1,8 @@
|
||||
import { requireMcpToolError } from "../../lib/mcp-verifier-results.mjs";
|
||||
import {
|
||||
requireMcpLifecycleResult,
|
||||
requireMcpToolError,
|
||||
sortVerifierNames,
|
||||
} from "../../lib/mcp-verifier-results.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
access,
|
||||
@@ -273,22 +277,23 @@ const verifyAndroidReadiness = async ({
|
||||
try {
|
||||
await client.connect(transport);
|
||||
const catalog = await client.listTools();
|
||||
const catalogNames = catalog.tools.map(({ name }) => name).sort();
|
||||
const status = await client.callTool({
|
||||
const catalogNames = sortVerifierNames(
|
||||
catalog.tools.map(({ name }) => name),
|
||||
);
|
||||
const statusReply = await client.callTool({
|
||||
name: "binary_session",
|
||||
arguments: {},
|
||||
});
|
||||
assert.notEqual(status.isError, true, JSON.stringify(status));
|
||||
const availability = status.structuredContent?.result?.tool_availability;
|
||||
assert.ok(Array.isArray(availability));
|
||||
const status = requireMcpLifecycleResult(statusReply, "binary_session");
|
||||
const availability = status.tool_availability;
|
||||
assert.deepEqual(
|
||||
availability.map(({ name }) => name).sort(),
|
||||
TOOL_CONTRACTS.map(({ name }) => name).sort(),
|
||||
sortVerifierNames(availability.map(({ name }) => name)),
|
||||
sortVerifierNames(TOOL_CONTRACTS.map(({ name }) => name)),
|
||||
"Readiness must retain one availability record for the complete canonical catalog",
|
||||
);
|
||||
assert.deepEqual(
|
||||
catalogNames,
|
||||
TOOL_CONTRACTS.map(({ name }) => name).sort(),
|
||||
sortVerifierNames(TOOL_CONTRACTS.map(({ name }) => name)),
|
||||
"Readiness must not truncate the SDK tool catalog",
|
||||
);
|
||||
const byName = new Map(availability.map((item) => [item.name, item]));
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseEvidence } from "../../../dist/domain/evidence.js";
|
||||
import { hashAndroidFile } from "./digest.mjs";
|
||||
|
||||
/** Verify folded field assignments and remaining static-block calls through both public adapters. */
|
||||
export const verifyAndroidInitializers = async ({
|
||||
apk,
|
||||
repository,
|
||||
entrypoint,
|
||||
environment,
|
||||
execute,
|
||||
}) => {
|
||||
const digest = await hashAndroidFile(apk);
|
||||
const cases = [
|
||||
{
|
||||
class_name: "fixture.InitializationPolicy",
|
||||
witnesses: [
|
||||
"declared-policy",
|
||||
"->singletonList",
|
||||
"->POLICY:Ljava/util/List;",
|
||||
],
|
||||
},
|
||||
{
|
||||
class_name: "fixture.MixedInitialization",
|
||||
witnesses: ["field-value", "->LABEL:Ljava/lang/String;", "->touch()V"],
|
||||
},
|
||||
];
|
||||
const check = (value, scenario) => {
|
||||
const evidence = parseEvidence(value);
|
||||
assert.equal(evidence.subject.digest.sha256, digest);
|
||||
const result = evidence.normalized_result;
|
||||
assert.equal(result.class_name, scenario.class_name);
|
||||
assert.equal(result.method.name, "<clinit>");
|
||||
assert.equal(result.overload_count, 1);
|
||||
assert.equal(result.body_status, "available");
|
||||
assert.equal(result.representation, "smali");
|
||||
assert.equal(result.fell_back, true);
|
||||
assert.ok(result.markers.includes("static_initializer_disassembly"));
|
||||
assert.equal(result.source.status, "complete");
|
||||
for (const witness of scenario.witnesses)
|
||||
assert.ok(
|
||||
result.source.text.includes(witness),
|
||||
`Missing initializer evidence: ${witness}`,
|
||||
);
|
||||
return evidence;
|
||||
};
|
||||
const cli = new Map();
|
||||
for (const scenario of cases) {
|
||||
const response = await execute(
|
||||
process.execPath,
|
||||
[
|
||||
entrypoint,
|
||||
"inspect-android-method",
|
||||
apk,
|
||||
scenario.class_name,
|
||||
"<clinit>",
|
||||
"--format",
|
||||
"json",
|
||||
],
|
||||
{
|
||||
cwd: repository,
|
||||
env: environment,
|
||||
timeout: 150_000,
|
||||
maxBuffer: 8 * 1024 * 1024,
|
||||
},
|
||||
);
|
||||
cli.set(scenario.class_name, check(JSON.parse(response.stdout), scenario));
|
||||
}
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [entrypoint, "mcp"],
|
||||
cwd: repository,
|
||||
env: environment,
|
||||
stderr: "pipe",
|
||||
});
|
||||
let stderr = "";
|
||||
transport.stderr?.on("data", (chunk) => {
|
||||
stderr += chunk.toString();
|
||||
});
|
||||
const client = new Client({
|
||||
name: "rea-real-android-initializers",
|
||||
version: "1",
|
||||
});
|
||||
try {
|
||||
await client.connect(transport, { timeout: 30_000 });
|
||||
for (const scenario of cases) {
|
||||
const response = await client.callTool(
|
||||
{
|
||||
name: "inspect_android_method",
|
||||
arguments: {
|
||||
path: apk,
|
||||
class_name: scenario.class_name,
|
||||
method_name: "<clinit>",
|
||||
},
|
||||
},
|
||||
{ timeout: 150_000 },
|
||||
);
|
||||
assert.notEqual(response.isError, true, JSON.stringify(response));
|
||||
const evidence = check(response.structuredContent, scenario);
|
||||
assert.deepEqual(
|
||||
evidence.normalized_result,
|
||||
cli.get(scenario.class_name).normalized_result,
|
||||
);
|
||||
assert.deepEqual(
|
||||
evidence.parameters,
|
||||
cli.get(scenario.class_name).parameters,
|
||||
);
|
||||
assert.deepEqual(
|
||||
evidence.provider,
|
||||
cli.get(scenario.class_name).provider,
|
||||
);
|
||||
}
|
||||
await client.ping();
|
||||
} catch (cause) {
|
||||
process.stderr.write(stderr);
|
||||
throw cause;
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
assert.equal(
|
||||
await hashAndroidFile(apk),
|
||||
digest,
|
||||
"Static inspection changed fixture bytes",
|
||||
);
|
||||
console.log(
|
||||
"PASS real CLI/MCP static initializer evidence, identity, parity and unchanged APK",
|
||||
);
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { delimiter, join, resolve } from "node:path";
|
||||
import { verifyAndroidInitializers } from "./initializers.mjs";
|
||||
|
||||
/** Prove cold metadata stays unmaterialized and overload identities survive code generation. */
|
||||
export const verifyAndroidMetadata = async ({
|
||||
@@ -11,6 +12,8 @@ export const verifyAndroidMetadata = async ({
|
||||
bridge,
|
||||
repository,
|
||||
execute,
|
||||
entrypoint,
|
||||
environment,
|
||||
jvmArguments = [],
|
||||
}) => {
|
||||
const javac =
|
||||
@@ -55,6 +58,8 @@ export const verifyAndroidMetadata = async ({
|
||||
`${classes}${delimiter}${jar}`,
|
||||
"ReaJadxMetadataRegression",
|
||||
apk,
|
||||
resolve(repository, "tests/fixtures/android/static-initializers"),
|
||||
classes,
|
||||
],
|
||||
{ timeout: 120_000, maxBuffer: 8 * 1024 * 1024 },
|
||||
);
|
||||
@@ -62,9 +67,23 @@ export const verifyAndroidMetadata = async ({
|
||||
result.stdout,
|
||||
/METADATA_REGRESSION_PASS classes=\d+ overloads=3/u,
|
||||
);
|
||||
assert.match(
|
||||
result.stdout,
|
||||
/INITIALIZER_REGRESSION_PASS folded=2 bounded=true/u,
|
||||
);
|
||||
console.log(
|
||||
"PASS real cold metadata, full generic types and stable overloaded method selection",
|
||||
);
|
||||
console.log(
|
||||
"PASS real folded and mixed static initializers, absent bodies and bounded smali",
|
||||
);
|
||||
await verifyAndroidInitializers({
|
||||
apk: resolve(classes, "initializers.apk"),
|
||||
repository,
|
||||
entrypoint,
|
||||
environment,
|
||||
execute,
|
||||
});
|
||||
} finally {
|
||||
await rm(classes, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user