mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 13:56:47 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8adc2c6b1 | ||
|
|
7b90bc5a67 | ||
|
|
1f1bb95d53 | ||
|
|
3370bab50b | ||
|
|
69582df9c3 | ||
|
|
303008f35d | ||
|
|
55246a6425 | ||
|
|
4eb3b5335a | ||
|
|
fb3fa1dc5f | ||
|
|
ecd92970a4 | ||
|
|
ff34e7aeff | ||
|
|
53ba7e7b31 | ||
|
|
0cd9f47ff1 | ||
|
|
502e665212 | ||
|
|
b478a8b011 |
No files matched your search
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "rea",
|
||||
"interface": {
|
||||
"displayName": "REA"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "rea",
|
||||
"source": {
|
||||
"source": "npm",
|
||||
"package": "rea-agents",
|
||||
"version": "6.8.0"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "Developer Tools"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
---
|
||||
name: rea-analysis-optimization
|
||||
description: Optimize REA providers and shared analysis workflows for agent context, latency, CPU, and memory while preserving correctness and Evidence. Use for analysis bottlenecks and cache or query design; CI/test-suite optimization belongs to rea-ci-tests-audit.
|
||||
---
|
||||
|
||||
# REA analysis optimization
|
||||
|
||||
Find a concrete source of unnecessary work, change it at its owner, and verify
|
||||
equivalent completed investigations. A useful outcome can also be a measured
|
||||
decision to leave an implementation alone.
|
||||
|
||||
Follow the checkout's [performance priorities](../../../AGENTS.md#performance),
|
||||
[tool design](../../../docs/tool-design.md), and
|
||||
[testing guide](../../../docs/testing.md). This skill supplies an optimization
|
||||
workflow, not additional permission to execute targets, publish, or merge.
|
||||
Use the current contracts, provider prerequisites, and user authorization.
|
||||
|
||||
Reserve performance measurements for changes that target performance or have a
|
||||
concrete regression concern. Routine cleanup uses correctness checks and does
|
||||
not require this optimization workflow or synthetic benchmarks merely because
|
||||
helper calls, imports, or representations change.
|
||||
|
||||
Read [the Kuna case study](references/kuna-case-study.md) when selecting a
|
||||
reuse strategy or interpreting a benchmark. Its pinned observations illustrate
|
||||
tradeoffs; they are not current-provider guarantees or universal speedups.
|
||||
|
||||
## Pin the question and the correctness oracle
|
||||
|
||||
Describe the analyst's completed task, including the facts and relationships
|
||||
needed to answer it, requested detail, source access, and meaningful unknowns.
|
||||
Choose a representative real artifact and a small or empty case. Record the
|
||||
baseline revision, target digest, engine identity, options, host, and entrypoint.
|
||||
Use a separate checkout for changes without disturbing other work.
|
||||
|
||||
Establish an oracle independent of the changed implementation: source-built
|
||||
fixture expectations, independently mapped file bytes, or a native producer
|
||||
paired with checks of its interpretation. Comparing two wrappers around the
|
||||
same faulty decoder does not establish correctness. Preserve address widths,
|
||||
same-address alternatives, unresolved endpoints, coverage, and source linkage.
|
||||
An empty result proves absence only within the actually examined scope.
|
||||
|
||||
## Locate the earliest unnecessary work
|
||||
|
||||
Trace one public CLI or MCP task through shared application code to its producer.
|
||||
Measure relevant phases: admission and identity checks, native analysis,
|
||||
decoding, lookup, projection, serialization, Evidence retention, and cleanup.
|
||||
Include catalog discovery and extra calls when they affect the completed task.
|
||||
|
||||
Use the evidence to choose the smallest structural correction:
|
||||
|
||||
| Observed cost | Candidate correction | Condition to establish |
|
||||
| --------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
| Repeated scans of validated facts | Reuse existing order or lookup structure | The owner establishes ordering and identity; every matching alternative survives |
|
||||
| Repeated equivalent engine work | Share a validated result or in-flight producer | Inputs, freshness, lifetime, and per-caller outcomes remain explicit |
|
||||
| Full reports for selected facts | Push selection or projection to the earliest capable owner | Required facts and evidence remain reachable; examined coverage is reported |
|
||||
| Large temporary transport buffers | Decode incrementally or use bounded source reads | Validate the whole admitted document; retained facts and copies still consume memory |
|
||||
| Many repeated homogeneous reads | Share admission, decoding, or batch work | Preserve item identity, order where required, and individual failures |
|
||||
|
||||
Do not introduce an index, generic cache, batch endpoint, or orchestration layer
|
||||
without identifying the work it removes and its maintenance/resource cost.
|
||||
Projection after materializing a full report may reduce output while leaving
|
||||
analysis time and peak memory unchanged. Fix missing or incorrect facts before
|
||||
benchmarking an alternative composition against a better-covered workflow.
|
||||
|
||||
## Keep one interpretation and a defined reuse lifetime
|
||||
|
||||
Let the producer boundary validate and normalize facts once. Consumers query
|
||||
that representation through the shared CLI/MCP implementation; adapters should
|
||||
not rebuild relationships or independently reinterpret raw reports. Keep lookup
|
||||
identity separate from display formatting. Reuse contracts when their intent
|
||||
matches, honoring requested detail and complete Evidence access.
|
||||
|
||||
For every cache or shared producer, state its key, owner, invalidation, retained
|
||||
size, and lifetime. Account for result-affecting target bytes, engine contents,
|
||||
analysis options, architecture/load mapping, and parser/schema versions where
|
||||
they affect reuse. Validate live state before calling a cached observation
|
||||
current. Cache a refusal only when it is deterministic for that validated key;
|
||||
malformed, partial, transient, or cancelled attempts need explicit retry policy.
|
||||
|
||||
One cancelled waiter must not invalidate a successful peer. Preserve producer
|
||||
failure context and each caller's cancellation reason. Retain ownership and
|
||||
capacity charges until shared work and cleanup settle; preserve completed
|
||||
observations when cleanup fails. Exercise invalidation, concurrent consumers,
|
||||
last-waiter cancellation, close, and cleanup retry when those paths change.
|
||||
|
||||
Bound the process family and retained state, not just a parent heap or worker
|
||||
count. Account for decoding and serialization expansion before allocation.
|
||||
Choose CPU, memory, concurrency, and disk limits from the host and workload;
|
||||
stop before OOM and verify owned cleanup. A guard-stopped task is incomplete.
|
||||
Do not replace a transport buffer cap with an unbounded retained inventory or
|
||||
hide partial results behind a success status.
|
||||
|
||||
## Compare completed tasks and limit the claim
|
||||
|
||||
Run the affected real public workflow before and after with equivalent inputs,
|
||||
results, coverage, and Evidence semantics. Include small inputs so setup costs
|
||||
and regressions are visible. Distinguish fresh sessions, warm runtime caches,
|
||||
and OS file caches. Alternate or repeat runs when variance affects the decision.
|
||||
Choose meaningful boundary regressions for cases the actual engine cannot
|
||||
reliably reproduce; a fake engine does not qualify a real provider.
|
||||
|
||||
Report task outcome, relevant phase timings, total elapsed and CPU time, peak
|
||||
process-family memory and its accounting method, resource stops, and cleanup.
|
||||
Check the payload exit status as well as the profiler's. Keep failed attempts
|
||||
and identify unmeasured costs; a passing retry does not explain a failure.
|
||||
|
||||
For token claims, count schemas/discovery, arguments, replies, retries, and
|
||||
continuations needed to finish the task. Label tokenizer estimates separately
|
||||
from actual model usage and record the client/catalog behavior. Smaller JSON
|
||||
alone establishes neither fewer model tokens nor better investigation quality.
|
||||
Do not claim a complex-software advantage without completed representative
|
||||
investigations and comparable correctness/coverage.
|
||||
|
||||
Finish with the bottleneck, owning change, preserved semantics, executed
|
||||
checks, before/after measurements, and remaining limits. Keep any integration
|
||||
record current with its tested revision and distinguish merged production work
|
||||
from experiment-only code. Stop when the demonstrated bottleneck is resolved
|
||||
or further benefit remains speculative.
|
||||
@@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "REA analysis optimization"
|
||||
short_description: "Optimize analysis with preserved Evidence and measured costs"
|
||||
default_prompt: "Use $rea-analysis-optimization to find and verify a concrete analysis bottleneck while preserving correctness and Evidence."
|
||||
@@ -1,112 +0,0 @@
|
||||
# Kuna: reuse before adding machinery
|
||||
|
||||
Read this reference for concrete examples of the skill's decisions. The records
|
||||
below concern static Linux analysis with caller-supplied Kuna. They do not
|
||||
establish parity with another provider or a general advantage for Code Mode,
|
||||
atomic tools, or any particular orchestration design.
|
||||
|
||||
## Exact-address lookup: a measured, narrow improvement
|
||||
|
||||
[PR #4695](https://github.com/morluto/rea/pull/4695) reused the validated string
|
||||
inventory's numeric address order. Previously, each `list_strings(address)`
|
||||
filtered all cached facts. The owner now performs a binary lower-bound lookup
|
||||
and returns every encoding at that address: O(log n + matches), without a
|
||||
second retained lookup index.
|
||||
|
||||
The owning implementation is
|
||||
[`KunaStringInventory.ts`](../../../../src/kuna/KunaStringInventory.ts), consumed
|
||||
by [`KunaStringQueries.ts`](../../../../src/kuna/KunaStringQueries.ts). Inspect
|
||||
their current versions before adapting the example. Numeric u64 addresses,
|
||||
same-address encodings, duplicate rejection, source digests, caller selection,
|
||||
and the existing session lifetime were part of the contract. Full listings and
|
||||
literal/regex searches kept their semantics. An attempted removal of the
|
||||
duplicate-detection set was dropped: no stable memory benefit was established,
|
||||
and it delayed malformed-input rejection.
|
||||
|
||||
### Recorded workload and results
|
||||
|
||||
The benchmark used unmodified Kuna 1.833 and LLVM 16 on Linux x86-64, with
|
||||
602,544 string facts. Baseline was `be0b2d138`; the final feature was
|
||||
`5f564962da0e1b17f00a68a74a312539f0209a01`, subsequently integrated in
|
||||
`c6b62754b095a134d6d67877508fa34311a2eda2`. These are historical observations,
|
||||
not a benchmark of every subsequent main revision.
|
||||
|
||||
The same production stdio MCP task discovered the catalog, opened the binary,
|
||||
made one cold selected read and 28 warm exact reads, checked retained Evidence,
|
||||
and closed the session. Five selected addresses and two absent selectors were
|
||||
read four times. A native filtered scan selected the strings; an independent
|
||||
ELF load-header mapping checked the source bytes and terminators. Returned
|
||||
facts, source linkage, target digests, and cleanup were checked.
|
||||
|
||||
Three alternating fresh sessions per implementation used one CPU, a 4 GiB
|
||||
process-family hard limit, a 3 GiB early stop, and no swap. OS file caches were
|
||||
warm. Whole-harness costs include the independent native oracle, hashing,
|
||||
catalog/client work, and correctness checks.
|
||||
|
||||
| Median | Before | After |
|
||||
| ---------------------------------------- | ---------: | ---------: |
|
||||
| 28 warm exact MCP reads | 495.2 ms | 90.8 ms |
|
||||
| Cold selected MCP read | 11.30 s | 11.01 s |
|
||||
| Whole harness elapsed | 28.24 s | 27.43 s |
|
||||
| Whole process-family CPU | 27.71 s | 26.89 s |
|
||||
| Kernel family peak, including file cache | 2237.6 MiB | 2240.6 MiB |
|
||||
|
||||
Warm totals ranged from 491.7–544.3 ms before to 84.8–99.5 ms after: about
|
||||
5.5 times faster at this phase. Cold/whole-task timing and memory overlapped;
|
||||
no stable improvement in those costs was established. One small SQLite pair
|
||||
(7,985 facts) measured 20 warm reads at 88.6 → 80.5 ms and the whole harness at
|
||||
13.68 → 13.94 s. The large-inventory lookup benefit does not generalize to a
|
||||
whole-task speedup on small inputs.
|
||||
|
||||
Catalog hashes, call counts, and normalized facts were unchanged. A
|
||||
`cl100k_base` estimate over one pass of raw catalog JSON, arguments, and replies
|
||||
was about 846,000 tokens in both lanes, with about 802,562 from the catalog.
|
||||
This was transport serialization, not rendered model context or actual usage;
|
||||
client discovery, prompts, replay, and protocol framing were unmeasured.
|
||||
**No token reduction or improved agent investigation outcome was established.**
|
||||
|
||||
Pinned target SHA-256:
|
||||
`f62d254b7f2bf42df8c8b07d46ee3bb4c2cafeca436b2e6bc6ccbe4581f58f40`.
|
||||
Pinned engine SHA-256:
|
||||
`d858918b0766435cd96b999a77f2d5a31a0cc4f07256421c883b5b9dc4dd58e4`.
|
||||
All six normalized-result sequence digests were
|
||||
`4d2f90269da8568984090b20274163c4a7c2a0538aab63530bd2fe44840a0bba`.
|
||||
Selected byte agreement does not prove native discovery completeness; missing
|
||||
strings and relationships can remain unknown.
|
||||
|
||||
## Other transferable changes and their limits
|
||||
|
||||
- [Cached string inventory and search, #4095](https://github.com/morluto/rea/pull/4095):
|
||||
share validated facts across queries in a defined session. Audit key inputs
|
||||
and lifetime before extending reuse across targets or engine revisions.
|
||||
- [Shared single-function work, #4045](https://github.com/morluto/rea/pull/4045)
|
||||
and [repeat-request reuse, #4169](https://github.com/morluto/rea/pull/4169):
|
||||
avoid duplicate native work while retaining per-caller cancellation and
|
||||
cleanup ownership. A query count alone cannot reveal duplicate subprocesses.
|
||||
- [Streamed strings, #4318](https://github.com/morluto/rea/pull/4318)
|
||||
and [streamed call graph, #4329](https://github.com/morluto/rea/pull/4329):
|
||||
remove fixed aggregate transport-buffer failures through incremental parsing.
|
||||
Streaming transport does not bound every retained object or final reply.
|
||||
These links establish changes; no numerical speedup is asserted here.
|
||||
|
||||
Typed reference facts, native string-query projection, and shared native byte
|
||||
reads remained experimental at the 2026-10-17 checkpoint
|
||||
`65f2f9a22bc892f61d2b00f9a4611124f57d4bff` on
|
||||
`experiment/atomic-codemode-20261016`. Some experiments used a patched native
|
||||
engine. Check current integration and bring-your-own engine capabilities before
|
||||
proposing them; a working experiment is not a stock-provider guarantee.
|
||||
|
||||
## Applying the reasoning elsewhere
|
||||
|
||||
For another provider or format, inspect its actual representation and native
|
||||
capabilities before borrowing the implementation. Existing numeric ordering
|
||||
may support range reads; an on-disk native query may avoid materializing an
|
||||
inventory; an immutable parse may support several views. Measure the relevant
|
||||
alternative rather than requiring every provider to use Kuna's cached array.
|
||||
|
||||
For live observations, immutable artifact caching rules are insufficient:
|
||||
validate the process/session identity and freshness required by the caller.
|
||||
For compact replies, preserve access to source Evidence and report examined
|
||||
coverage; projection must not silently turn an unresolved edge into no edge.
|
||||
For batching, test mixed successes and failures and measure the entire task,
|
||||
including discovery, extra calls, native work, and serialization.
|
||||
@@ -68,10 +68,9 @@ closing-issue reporters. Grouping preserves all those credits in one line.
|
||||
When Highlights, detailed entries or migrations reference the same work,
|
||||
put its thanks in one of those entries, not all of them. Include issue links.
|
||||
Direct-commit bullets carry the commit link and known human credit.
|
||||
Exclude GitHub bots and known agent accounts. REA's observed Anthropic, Cursor,
|
||||
Codex, and Copilot co-author trailers resolve to `@claude`, `@cursoragent`,
|
||||
`@codex`, and `@Copilot` with GitHub type `User`; the credit policy excludes
|
||||
them while retaining their metadata.
|
||||
Exclude GitHub bots and known agent accounts. REA's observed Anthropic and
|
||||
Cursor co-author trailers resolve to `@claude` and `@cursoragent` with GitHub
|
||||
type `User`; the credit policy excludes them while retaining their metadata.
|
||||
At the REA maintainer's request, omit `@morluto` self-thanks while preserving
|
||||
authorship in the inventory. Other human contributors remain eligible.
|
||||
Use explicit GitHub links for cross-repository references to avoid ambiguous
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: rea-tool-design
|
||||
description: Audit, investigate, design, or change REA investigation tools, CLI/MCP contracts, provider capabilities, and Evidence semantics.
|
||||
description: Design or change REA investigation tools, CLI/MCP contracts, provider capabilities, and Evidence semantics.
|
||||
---
|
||||
|
||||
# REA tool design
|
||||
@@ -10,20 +10,6 @@ contract semantics, provider ownership, and discoverability. Inspect the current
|
||||
contract and nearest existing tool relevant to the analyst question; source,
|
||||
documentation, and live catalogs may describe different versions.
|
||||
|
||||
For provider-backed work, exercise the analyst's task through REA and compare
|
||||
relevant native provider and independent artifact observations where available.
|
||||
Check applicable partial results, recovery and session boundaries; report what
|
||||
was not exercised. For failure guidance, follow [design recovery around the
|
||||
failure](../../../docs/tool-design.md#design-recovery-around-the-failure).
|
||||
Use [workflow verification](../../../docs/testing.md#provider-backed-workflow-verification)
|
||||
to select evidence for the proposed contract instead of seeking provider API
|
||||
parity.
|
||||
|
||||
For an audit, follow [audit existing tools](../../../docs/tool-design.md#audit-existing-tools):
|
||||
scope representative tasks, exercise the live workflow, and distinguish
|
||||
reproduced bugs, supported limitations, design opportunities and unverified
|
||||
claims. Report working alternatives and evidence limits as well as findings.
|
||||
|
||||
For a design request, return the proposed boundary, input/result shape, Evidence
|
||||
semantics, nearest alternative, and verification approach. Implement when
|
||||
requested, preserving the user's scope and existing authorization.
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
name: reverse-engineer-anything
|
||||
description: Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA, and build agent environments (AgentEnv bundles) of web applications from a live instance or a capture. 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.
|
||||
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: "59"
|
||||
version: "41"
|
||||
---
|
||||
|
||||
# REA
|
||||
|
||||
Use REA when a claim depends on a shipped binary or package, decompilation,
|
||||
requested runtime evidence, or comparison with
|
||||
passive application runtime evidence, or comparison with
|
||||
behavior not established by available source. For ordinary analysis of a
|
||||
complete source repository, use normal repository tools and do not run REA
|
||||
readiness or provider commands.
|
||||
@@ -17,9 +17,6 @@ readiness or provider commands.
|
||||
|
||||
Installing this skill supplies instructions; it does not register the REA MCP
|
||||
server, install analysis engines, or add tools to an already-running agent.
|
||||
The exception is a Claude Code or Codex plugin install: the plugin bundles
|
||||
this skill and registers its own `rea` server, outside the state that
|
||||
`rea setup` and `rea doctor` manage.
|
||||
|
||||
If REA tools are available and their registration is not known to be stale,
|
||||
proceed directly to the target. Do not run diagnostics or setup before every
|
||||
@@ -33,49 +30,13 @@ unavailable, read [connection and recovery](references/connection-and-recovery.m
|
||||
Diagnose the observed blocker, continue useful supported work, and resume the
|
||||
original task after repair. Client approval, shell PATH, registration and engine
|
||||
availability have different remedies; avoid repeating setup for all of them.
|
||||
If the host times out without a REA result, do not repeat the unchanged call or
|
||||
assume work stopped. Read the recovery guide's deadline distinction before retrying.
|
||||
If the MCP connection drops, retry that call once only when it is safe to
|
||||
repeat or known not to have started. If it may already have changed the
|
||||
target, or it fails again, tell the user and ask them to restart the MCP
|
||||
client. If REA could not start its connection, retry once, then tell the user
|
||||
and ask them to restart the MCP client. Do not retry a startup failure that
|
||||
returns the same unchanged result. For plugin installs, follow
|
||||
[plugin recovery](references/connection-and-recovery.md#rea-installed-as-a-client-plugin).
|
||||
For setup-owned registrations, run `rea doctor` when REA could not start
|
||||
or tools stay missing after that restart. For `cleanup_incomplete`, a retryable
|
||||
capacity limit, or `configuration_invalid`, follow the recovery guide's ladder
|
||||
before asking for a restart. Read that guide before a second repair attempt.
|
||||
|
||||
Choose the installation owner before repairing registration. Plugin installs use
|
||||
[plugin recovery](references/connection-and-recovery.md#rea-installed-as-a-client-plugin);
|
||||
only setup-owned registrations use the guide's
|
||||
[registration repair plan](references/connection-and-recovery.md#repair-registration-when-needed).
|
||||
A plugin-only doctor result of `missing` does not call for `rea setup`.
|
||||
|
||||
REA setup never installs or upgrades Node.js, npm, Homebrew, Java, Ghidra, IDA,
|
||||
JADX, Kuna, Binwalk, or Unblob. Use existing prerequisites; do not install unrelated
|
||||
software to repair MCP registration. For an unsupported client, use manual stdio
|
||||
registration with an existing installation’s absolute runtime and entry script, or continue through the CLI.
|
||||
|
||||
A concrete CLI fallback for an operator-supplied JavaScript tree or ASAR is:
|
||||
|
||||
```bash
|
||||
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
|
||||
```
|
||||
|
||||
No MCP registration or native engine is required. Read the returned Evidence,
|
||||
graph, limitations, and unknowns with the same care as an MCP result; the CLI
|
||||
returns the Evidence record directly. This fallback does not establish that
|
||||
MCP is configured. Native CLI tasks still require their selected engine.
|
||||
Prefer an existing `rea` CLI. When none is installed and REA package acquisition
|
||||
is within the authorized scope, use `npx -y rea-agents@latest` in its place from a
|
||||
trusted directory outside the target project, with absolute target paths. Managed
|
||||
setup binds the current installation directly; retain its runtime and package
|
||||
cache entry, or restore that installation before repairing its registration.
|
||||
For setup-owned registration, configuration repair starts with a read-only setup plan; show paths, backups and
|
||||
is within the authorized scope, use `npx -y rea-agents@latest` in its place.
|
||||
Configuration repair starts with a read-only setup plan; show paths, backups and
|
||||
skill replacement before applying authorized changes. Preserve existing approval
|
||||
and target-execution boundaries.
|
||||
and target-execution boundaries. REA setup never installs or upgrades unrelated
|
||||
software, including Node.js, npm, Homebrew, Java, Ghidra, IDA, JADX, Binwalk or Unblob.
|
||||
|
||||
## Route the target first
|
||||
|
||||
@@ -85,22 +46,9 @@ explicit path or endpoint and do not need it.
|
||||
|
||||
- ASAR or extracted JavaScript/Electron tree:
|
||||
`analyze_javascript_application`.
|
||||
- Archive/package member inventory (ZIP/APK/IPA/MSIX/AppX, DMG, or a PyInstaller
|
||||
executable): `open_binary` with the supplied local path; use `inspect_artifact`
|
||||
when its graph and findings help answer the question. A PyInstaller CArchive
|
||||
is the application layer; the bootloader is only the native prefix. Start with
|
||||
`detail: "summary"` for large inventories, then use retained inventory views
|
||||
for selected member fields/pages. A complete small inventory can cost less
|
||||
than extra calls and schemas. Runtime options are metadata, not files.
|
||||
Compact delivery still inventories and hashes members. See the
|
||||
[Python packaged applications guide](https://github.com/morluto/rea/blob/main/docs/python-packaged-applications.md).
|
||||
- Bun `--compile` executable: `open_binary`, then `inspect_artifact`; the Bun
|
||||
module graph is the application layer. Extract stored modules and analyze the
|
||||
JavaScript. See [Bun standalone executables](https://github.com/morluto/rea/blob/main/docs/bun-standalone.md).
|
||||
- Apple app plist, Interface Builder, asset catalog, or keyed archive resources:
|
||||
`open_bundle_resources`, then the selected resource tool. This does not
|
||||
require `CFBundleExecutable` or a deep provider. Resource sessions have no
|
||||
executable digest or binary snapshot; use `open_binary` for native code.
|
||||
- Archive/package member inventory (ZIP/APK/IPA/MSIX/AppX or DMG):
|
||||
`open_binary` with the supplied local path; use `inspect_artifact` when its
|
||||
graph and findings help answer the question.
|
||||
- Android APK code, classes, methods, or incoming references:
|
||||
`inspect_android_package`, then focused Android tools when advertised.
|
||||
Archive member inventory still uses the archive route above.
|
||||
@@ -109,35 +57,17 @@ explicit path or endpoint and do not need it.
|
||||
`extract_firmware` when extraction is requested, with a caller-selected new
|
||||
absolute output directory. These tools use caller-supplied Binwalk/Unblob on
|
||||
Linux; see the [firmware guide](https://github.com/morluto/rea/blob/main/docs/firmware-analysis.md).
|
||||
- ESP32-family app or bootloader image (`0xE9` magic): `inspect_artifact`
|
||||
without a provider; see the ESP section of `references/native-and-artifacts.md`.
|
||||
- .NET NativeAOT PE/ELF: native Ghidra analysis. Native code can yield pseudocode;
|
||||
consult the NativeAOT workflow in `references/native-and-artifacts.md` for
|
||||
optional metadata recovery and its supported host/layout boundary.
|
||||
- Native program behavior, live calls, instrumentation, or command-run effects:
|
||||
choose the matching [native runtime workflow](references/runtime-observation.md#native-runtime-questions).
|
||||
`open_binary` and `trace_native_values` establish static facts; they do not
|
||||
observe execution. Runtime tools have distinct launch, attach, mutation and
|
||||
cleanup effects, and do not require a preliminary static session unless their
|
||||
own contract says so.
|
||||
- User-owned browser page already open: `list_browser_targets`.
|
||||
- Browser action the agent must perform (click, type, navigate):
|
||||
`capture_browser_scenario` with declared actions. See
|
||||
[runtime observation](references/runtime-observation.md) for connect/launch
|
||||
and the focused follow-up browser tools.
|
||||
- Agent environment (AgentEnv bundle) of a web application: read
|
||||
[environments](references/environments.md) before recording; it records with
|
||||
`capture_browser_scenario` and writes the bundle with `export_environment_bundle`.
|
||||
- Fiddler Everywhere traffic or a native `.saz` capture: read the
|
||||
[Fiddler workflow](references/runtime-observation.md#fiddler-everywhere-traffic-debugging).
|
||||
Inspect saved SAZ with `inspect_web_network_capture` and `format: "saz"`;
|
||||
connect Everywhere's separate MCP server for live traffic.
|
||||
- Retained HAR/SAZ/native mitmproxy/aioquic qlog capture: `inspect_web_network_capture`. Select
|
||||
original record ordinals when useful; inspect producer fields and byte/number
|
||||
sidecars without fetching recorded URLs or claiming live attribution. HAR/SAZ/qlog
|
||||
old-generation heap is `REA_CAPTURE_DECODER_OLD_GENERATION_MIB` (default 192 MiB,
|
||||
floor 8, maximum 16384), set where REA starts. Host memory and `NODE_OPTIONS` do not
|
||||
raise it. See
|
||||
sidecars without fetching recorded URLs or claiming live attribution. See
|
||||
[historical captures](https://github.com/morluto/rea/blob/main/docs/web-network-captures.md) for the exact upstream
|
||||
profile and credential exclusions. Qlog support starts with aioquic 1.5.0
|
||||
JSON 0.3; stream coverage and endpoint loss declarations do not recover bodies
|
||||
@@ -145,14 +75,12 @@ explicit path or endpoint and do not need it.
|
||||
pages; select one trace, stream ID and direction for contribution/range pages.
|
||||
Facts retain all-recorded-events-before-selection scope. Other qlog layouts
|
||||
and PCAP correlation are unsupported.
|
||||
- User-owned Electron runtime already open: `list_electron_targets`. Before an electron-main Inspector attempt, check packaged fuses with `inspect_electron_fuses`; disabled `EnableNodeCliInspectArguments` means the app ignores `--inspect`.
|
||||
- User-owned Electron runtime already open: `list_electron_targets`.
|
||||
- Packet-field discovery: `inspect_packet_fields` searches installed protocols
|
||||
and field names/types before choosing fields for `inspect_packet_capture`.
|
||||
Query/page the relevant records; registrations do not establish observed traffic.
|
||||
- Recorded packet captures: `inspect_packet_capture` with selected TShark fields,
|
||||
display filters and decode-as rules. Requires Linux, util-linux prlimit and
|
||||
TShark 4.x on an absolute `PATH` entry or via the optional absolute
|
||||
`REA_TSHARK_COMMAND` override; see
|
||||
display filters and decode-as rules. Requires Linux, util-linux prlimit and `REA_TSHARK_COMMAND`; see
|
||||
[capture guide](https://github.com/morluto/rea/blob/main/docs/packet-captures.md).
|
||||
Packet limits count input packets before filtering. Output defaults to 50 matching
|
||||
frames; use `match_offset` / `match_limit` for later pages, or explicit null
|
||||
@@ -179,18 +107,8 @@ explicit path or endpoint and do not need it.
|
||||
Select JavaScript glue and candidate paths explicitly and preserve URL/path
|
||||
ambiguity. No implicit fetch or target execution; WAT is not original source.
|
||||
See the [WASM guide](https://github.com/morluto/rea/blob/main/docs/wasm-artifacts.md).
|
||||
- Explicit local Android APK (or ZIP/AAB) signing: `inspect_apk_signature`.
|
||||
Prefer its summary or selected signer/digest/lineage page; retain complete
|
||||
Evidence and use `inspect_analysis_view` kind `apk-signature` for follow-ups.
|
||||
Reports which schemes are present (v1 JAR, v2, v3, v3.1), each signer's
|
||||
certificate SHA-256 fingerprint (v1 included, from its PKCS#7 block), v3 key-rotation lineage, source stamp, and
|
||||
whether each signed digest still matches the bytes (self-consistency). This
|
||||
is identity and integrity, not a trust or authenticity verdict.
|
||||
See the [APK signature guide](https://github.com/morluto/rea/blob/main/docs/apk-signature.md).
|
||||
- Explicit local EVM bytecode carrier: `inspect_evm_interface` with caller-selected
|
||||
`raw` or `hex` encoding. The owned worker deadline is `timeout_ms` (default
|
||||
30000 ms, range 1..2147483647, next call). It does not raise the fixed byte,
|
||||
address-space, or CPU allowances. Preserve carrier/decoded digests and treat selectors,
|
||||
`raw` or `hex` encoding. Preserve carrier/decoded digests and treat selectors,
|
||||
argument strings and mutability as inferred candidates; this performs no chain
|
||||
lookup or target execution. For the complete inferred interface without hex or
|
||||
duplicate stdout, discover support and select `detail: "summary"`; complete
|
||||
@@ -204,105 +122,6 @@ explicit path or endpoint and do not need it.
|
||||
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).
|
||||
- Embedded CUDA/HIP code objects: `inspect_gpu_code_objects`; inventory targets
|
||||
first, then decode selected payloads and query retained GPU Evidence.
|
||||
- Bundled GGUF or safetensors model declarations: `inspect_model_artifact`
|
||||
with the explicit local file. Default header hashing leaves full-file identity
|
||||
unknown; `hash: "full"` additionally reads all weights for SHA-256. MCP returns
|
||||
retained summaries; optional `view` selects one page in the initial call. Use
|
||||
`inspect_analysis_view` kind `model-artifact` for
|
||||
batched metadata keys, tensor names or type totals, selected fields and pages.
|
||||
Preserve header-scoped indices and array preview omissions; do not infer model
|
||||
behavior from declared architecture or tensor types. See the
|
||||
[model file guide](https://github.com/morluto/rea/blob/main/docs/model-artifacts.md).
|
||||
- Unity IL2CPP names, member owners and string literals from `global-metadata.dat`:
|
||||
`inspect_il2cpp_metadata` with the explicit local file; MCP defaults to compact
|
||||
counts. Query sections, images, types, methods, fields, properties, events,
|
||||
parameters or string_literals through `inspect_analysis_view`
|
||||
with the returned `parent_evidence_id`, using exact filters, batch indices and
|
||||
selected fields. Parameters accept `method_indices`, avoiding a scan of all
|
||||
parameter pages. Missing collections in historical Evidence remain unknown.
|
||||
Versions 24, 27, 29, 31 and 39; no Unity, IL2CPP
|
||||
binary or disassembler is run. Unrecognized magic (possibly encrypted) and
|
||||
other versions are unsupported. Optional matching `binary_path` enables static native addresses and type references; query `method_addresses` with indices or addresses/states, preserving every shared-body method and both artifact identities. Without a linked binary native counts remain unknown. Byte-pattern fallback is inferred; no runtime behavior or same-build pairing is proved. See the
|
||||
[IL2CPP metadata guide](https://github.com/morluto/rea/blob/main/docs/il2cpp-metadata.md).
|
||||
- Embedded cargo-auditable Rust dependency metadata: `inspect_rust_binary`
|
||||
with the explicit local ELF, PE, or thin Mach-O file. This built-in
|
||||
target-free inspection requires no Rust installation or disassembler. Preserve
|
||||
package order, dependency indices, and the section file range. A missing
|
||||
`.dep-v0` section is null metadata, not proof the binary has no dependencies.
|
||||
A rustc commit hash is an inference from a read-only `/rustc/<40-hex>/` path.
|
||||
For selected crates, use `view: {collection: "packages", names: [...],
|
||||
fields: ["name", "version", "source"]}` on the first call; explicitly include
|
||||
`dependencies` for direct index references. Use `detail: "summary"` for counts
|
||||
and ranges, or retained `inspect_analysis_view` Rust package/edge pages for
|
||||
subsequent filters. Keep the complete parent Evidence accessible; CLI compact
|
||||
delivery requires `--evidence_output`.
|
||||
See the
|
||||
[Rust binary guide](https://github.com/morluto/rea/blob/main/docs/rust-binaries.md).
|
||||
- PE Authenticode certificate table: `inspect_pe_certificates` with the explicit
|
||||
local PE32/PE32+ file. This built-in target-free inspection requires no
|
||||
Windows SDK. Preserve WIN_CERTIFICATE ranges, types, payload SHA-256, and
|
||||
PKCS#7 signer/X.509 identity fields, including `subject_key_identifier_hex`
|
||||
when the signer uses that form. A missing security directory is observed
|
||||
absence of that table, not proof the image is unsigned in every catalog sense.
|
||||
Signatures are not verified. See the
|
||||
[PE certificate guide](https://github.com/morluto/rea/blob/main/docs/pe-certificates.md).
|
||||
- PE import and delay-load tables: `inspect_pe_imports` with the explicit
|
||||
local PE32/PE32+ file. This built-in target-free inspection requires no
|
||||
Windows SDK. Preserve DLL names, named and ordinal functions, ILT/IAT RVAs,
|
||||
delay-load descriptors, and the pefile import hash of the standard import
|
||||
table. A missing import directory is observed absence of that table, not
|
||||
proof the image never imported APIs through another table. Bound snapshots
|
||||
and loaded IAT contents remain unobserved. See the
|
||||
[PE import guide](https://github.com/morluto/rea/blob/main/docs/pe-imports.md).
|
||||
- PE TLS callbacks and load configuration: `inspect_pe_load_config` with the
|
||||
explicit local PE32/PE32+ file. This built-in target-free inspection requires
|
||||
no Windows SDK. Preserve callback VAs as strings with their RVA, file offset
|
||||
and section, and the size-covered security cookie, SafeSEH, CFG,
|
||||
EH-continuation and XFG fields. Guard-flag mitigation claims are inferred
|
||||
linker declarations, not proof of enforcement. A missing directory is observed
|
||||
absence of that declaration. See the
|
||||
[PE load configuration guide](https://github.com/morluto/rea/blob/main/docs/pe-load-config.md).
|
||||
- ELF note tables: `inspect_elf_notes` with the explicit local ELF32/ELF64
|
||||
file. This built-in target-free inspection requires no disassembler. Preserve
|
||||
PT_NOTE and SHT_NOTE ranges, owners, types, descriptor SHA-256, and GNU
|
||||
build-id or ABI-tag fields. A missing table is observed absence of those
|
||||
declarations, not proof the image never carried notes. See the
|
||||
[ELF notes guide](https://github.com/morluto/rea/blob/main/docs/elf-notes.md).
|
||||
- Apple `.ips` crash reports: `inspect_apple_ips` with the explicit local
|
||||
file. This built-in target-free inspection requires no Xcode, atos, or
|
||||
pwntools. Preserve first-line metadata, `bug_type` as a string, process and
|
||||
exception facts, lossless integer registers, usedImages UUIDs, and
|
||||
documented classification matches. Only `bug_type` `"309"` yields a crash
|
||||
body; other types, including `"288"` stackshots, return no crash result.
|
||||
Opened Mach-O and dSYM UUID joins remain unobserved. See the
|
||||
[Apple IPS guide](https://github.com/morluto/rea/blob/main/docs/apple-ips.md).
|
||||
- Linux kernel module metadata: `inspect_kernel_module` with the explicit local
|
||||
ELF32/ELF64 relocatable file. This built-in target-free inspection requires
|
||||
no Linux host or `modinfo`. Preserve `.modinfo` key order and duplicate keys,
|
||||
`__versions` CRC hex, section file ranges, and the appended signature
|
||||
trailer's PKCS#7 SHA-256. A missing section is observed absence when section
|
||||
names are known, not proof the module never carried that metadata.
|
||||
`struct module` is not decoded. The signature is not validated. gzip, xz, and
|
||||
zstd containers are unsupported; decompress into a separate file with an
|
||||
external codec utility, then inspect the decompressed `ET_REL` image. See the
|
||||
[kernel module guide](https://github.com/morluto/rea/blob/main/docs/kernel-modules.md).
|
||||
- CPython 3.7–3.13 `.pyc` header, nested marshal code objects and bytecode
|
||||
identity: `inspect_python_pyc` with the explicit local file. This built-in
|
||||
target-free inspection requires no Python installation. Preserve magic,
|
||||
PEP 552 hash or mtime/size, nested names, and bytecode SHA-256. Bytecode is
|
||||
not decompiled, and a decoded header does not prove the matching `.py` is
|
||||
present. See the
|
||||
[CPython pyc guide](https://github.com/morluto/rea/blob/main/docs/python-pyc.md).
|
||||
- Erlang BEAM IFF container: `inspect_erlang_beam` with the explicit local
|
||||
`.beam` file. This built-in target-free inspection requires no Erlang
|
||||
installation. Preserve chunk names, atom order, export/import/lambda tables,
|
||||
and the Code header. For selected facts, prefer `detail: "summary"` plus a
|
||||
`beam` view (collection, names/indices, fields and paging), retaining the full
|
||||
Evidence. CLI summary requires `--evidence_output`. Missing table counts stay
|
||||
null; decoded empty tables count zero. Bytecode is not executed. See the
|
||||
[Erlang BEAM guide](https://github.com/morluto/rea/blob/main/docs/erlang-beam.md).
|
||||
|
||||
- Explicit local SQLite database snapshot: `inspect_sqlite_database`. Inspect
|
||||
with Node.js 24.x >=24.15 or 26+ providing native SQLite limits. Read schema
|
||||
@@ -325,8 +144,6 @@ explicit path or endpoint and do not need it.
|
||||
- PE resource type/name/language trees and static icon group references:
|
||||
`inspect_pe_resources` with an explicit path. This portable operation requires
|
||||
no active target or engine; opaque payloads retain file ranges and SHA-256.
|
||||
- PE export directories and ELF dynamic-symbol exports: `inspect_native_exports`
|
||||
with an explicit path; portable, no active target or engine.
|
||||
- Native executable, library, or analysis database: `open_binary`, then
|
||||
use focused analysis tools directly; call `binary_overview` when metadata or
|
||||
inventory context is useful and available from the selected provider.
|
||||
@@ -338,30 +155,11 @@ leaves its database open. The modern headless profile creates and closes an
|
||||
owned private database without saving. Installation and registration reuse
|
||||
[mrexodia/ida-pro-mcp](https://github.com/mrexodia/ida-pro-mcp); see the
|
||||
[IDA provider guide](https://github.com/morluto/rea/blob/main/docs/ida-provider.md).
|
||||
For migration to the separate native IDA MCP plugin, follow the guide's
|
||||
[legacy attached migration](https://github.com/morluto/rea/blob/main/docs/ida-provider.md#migrate-a-legacy-attached-registration).
|
||||
Replace the upstream registration explicitly; modern mrexodia tools are not the
|
||||
native profile. Native attachment preserves the database but does not provide
|
||||
direct callers; check session availability and the guide's verification limits.
|
||||
Proceed directly to `analyze_function`, pseudocode, or function/string searches
|
||||
after opening; IDA does not supply `binary_overview`. Consult session availability
|
||||
when composing broader workflows. Modern direct callers and unsupported dossier
|
||||
facets remain unknown; live IDA results are not replayed from snapshots.
|
||||
|
||||
With `REA_KUNA_COMMAND` configured, `provider_id: "kuna"` answers procedure
|
||||
lists, lookups, pseudocode, assembly and `analyze_function` from Kuna's cached
|
||||
whole-program export. Callers/callees use a separate body-free graph shared by
|
||||
the session; `xrefs` returns exact-address incoming sites and filters forwarding
|
||||
aliases. These queries and inventory lookups do not trigger a C export. Indirect
|
||||
targets remain unknown and can make relationship coverage partial. Reference
|
||||
caches expire on close; a fresh CLI process repeats the native reference walk.
|
||||
Typed references, CFG, raw-memory tools and annotations remain unsupported. A cold first
|
||||
answer can come from a single-function run (its limitations say so); assembly
|
||||
waits for the export. For pseudocode and assembly, `raw_result.source` names the
|
||||
cached `.c`/`.asm` file and byte span, which you may search directly instead of
|
||||
calling per function. See the
|
||||
[Kuna provider guide](https://github.com/morluto/rea/blob/main/docs/kuna-provider.md).
|
||||
|
||||
If the user says only "my app" without a name, path, or target already selected
|
||||
for this task, ask which app before searching for artifacts. Paths in unrelated
|
||||
saved reports and nearby test fixtures do not select the user's target. Resolve
|
||||
@@ -373,24 +171,12 @@ 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. Expand each
|
||||
group's `names` into entries, inheriting the inventory-level client requirements
|
||||
unless the group overrides them. The grouped name union is the complete tool
|
||||
inventory; use `tools/list` for canonical tool schemas. The default summary keeps
|
||||
each provider candidate's availability and target-support status, code and
|
||||
reason; `omitted_fields` lists what it leaves out. Request `{"detail": "complete"}`
|
||||
only when you need an unavailable candidate's remediation, provider
|
||||
diagnostics, candidate capability sets, or tool surface, effect and annotation
|
||||
flags; in those closed flag vocabularies, listed
|
||||
flags are true and omitted known flags are false. Provider candidate facts
|
||||
describe possible providers, while top-level session capabilities describe the
|
||||
current route and `analysis_provider_binding` names the selected provider. Do
|
||||
not treat those scopes as interchangeable.
|
||||
|
||||
For a Ghidra startup timeout on a large native target, follow
|
||||
[deadline recovery](references/native-and-artifacts.md#ghidra-startup-deadline-recovery)
|
||||
before abandoning deep analysis or repeating the same cold import. The default
|
||||
330 seconds can be changed for new startup attempts through
|
||||
`update_analysis_settings` using `ghidra.startup_timeout_ms`. Changing the
|
||||
server's launch environment instead requires a new REA process.
|
||||
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
|
||||
|
||||
@@ -398,22 +184,7 @@ Start with the default result and use its inline Evidence and graph context.
|
||||
For a JavaScript application or ELF layout whose size is unknown or large,
|
||||
call `analyze_javascript_application` or `inspect_binary_layout` with
|
||||
`"detail": "summary"`, then inspect the retained `parent_evidence_id` with
|
||||
`inspect_analysis_view`. Over MCP, `analyze_function` is summary-first without
|
||||
a flag: its default reply is the summary, and the complete dossier stays
|
||||
retained under `parent_evidence_id` for facet paging. Pass that ID as a
|
||||
`retained-evidence` reference to `compare_functions`, or inside
|
||||
`match_native_functions` `left_observations`/`right_observations` or
|
||||
`build_call_path.functions`; request `"detail": "complete"` only when the
|
||||
dossier must travel inline.
|
||||
Use the same exact-reference form in other advertised logical Evidence inputs,
|
||||
including optional sources, arrays and nested sources across domains. Each
|
||||
source can independently be inline or retained; preserve its source identity,
|
||||
coverage, partial facts and unknowns. A summary or inspection page has its own
|
||||
Evidence ID: use `normalized_result.parent_evidence_id` when the consumer needs
|
||||
the complete source. For portable CLI follow-ups, commands advertising
|
||||
`--evidence-bundle` resolve references from that selected exported bundle.
|
||||
See [Evidence composition](references/evidence-workflows.md) for scope and
|
||||
recovery; file selectors are supported only where explicitly advertised.
|
||||
`inspect_analysis_view`.
|
||||
Page module identities and select the returned `node_id` before requesting an
|
||||
item; a single file path can represent several graph roles. Do not repeat an
|
||||
identical analysis or rejected call without a changed input or resolved blocker.
|
||||
@@ -432,10 +203,7 @@ output also streams without a single MCP frame.
|
||||
When the retained parent ID is unknown, use `get_evidence_bundle` with
|
||||
`detail: "summary"` and exact filters for operation, target SHA-256,
|
||||
analysis-profile digest or canonical procedure address. Select its returned
|
||||
Evidence ID for the next view instead of requesting the complete bundle. When
|
||||
that record has no supported view (for example an annotation), read it with
|
||||
`detail: "records"` and an `evidence_id` filter; `missing_evidence_links`
|
||||
names linked sources this session does not retain.
|
||||
Evidence ID for the next view instead of requesting the complete bundle.
|
||||
`retention: "complete-record"` means the whole record is stored; native
|
||||
value-flow truncation and unknown metadata remain separate. For an exported
|
||||
portable bundle, use `rea inspect-evidence-bundle` with JSON containing `path`,
|
||||
@@ -504,22 +272,14 @@ as complete while required questions remain open.
|
||||
[references/javascript-applications.md](references/javascript-applications.md)
|
||||
- Android APK declarations, classes, methods, and static references:
|
||||
[references/android-applications.md](references/android-applications.md)
|
||||
- Native instrumentation, debugger and process capture; browser/Electron
|
||||
observation; static/runtime comparison:
|
||||
- Passive browser/Electron observation and static/runtime reconciliation:
|
||||
[references/runtime-observation.md](references/runtime-observation.md)
|
||||
- Evidence paging, comparisons, residual unknowns, and verification:
|
||||
[references/evidence-workflows.md](references/evidence-workflows.md)
|
||||
- Agent environments (AgentEnv bundles) of a web application, recorded from
|
||||
a live instance or a HAR:
|
||||
[references/environments.md](references/environments.md)
|
||||
|
||||
## Finish the task
|
||||
|
||||
Explain findings in plain language and tie them to returned evidence. When the
|
||||
user asks to build something, use normal coding tools and separate observed
|
||||
behavior from design choices. Close an opened native session with
|
||||
`close_binary` when its engine is no longer needed. Evidence references and Unknown
|
||||
history remain in memory on the same MCP connection. End the investigation with
|
||||
`reset_investigation`; supply `save_path` to checkpoint the complete bundle before
|
||||
clearing. Import the saved bundle to restore original references and Unknown
|
||||
history in a new process. Retention is bounded and is not automatic persistence.
|
||||
`close_binary` when the investigation is complete.
|
||||
@@ -13,47 +13,18 @@ own APK path and explicit selectors; do not open a native session first.
|
||||
Choose an `overload_index` from the class inventory when methods are ambiguous
|
||||
instead of guessing.
|
||||
|
||||
For JNI implementations, discover `inspect_android_jni_bindings`. Select raw
|
||||
DEX class/method identities and optional descriptors, ABI or logical library
|
||||
paths. Prefer `detail: "summary"`; complete Evidence remains retained and
|
||||
`android-jni` views page declarations, libraries, candidates or binding rows
|
||||
with exact fields. Global native-pair ambiguity survives class selection.
|
||||
Preserve slot/relocation provenance and source coverage: matched means observed
|
||||
candidate presence; partial scans do not establish uniqueness or absence.
|
||||
This reads DEX/ELF bytes without loading the APK into the decompiler or executing
|
||||
target code. Runtime registration and loading remain unknown. See the
|
||||
[JNI guide](https://github.com/morluto/rea/blob/main/docs/android-analysis.md#link-native-declarations-to-jni-candidates).
|
||||
|
||||
For an R8/ProGuard release build with its `mapping.txt`, pass the absolute
|
||||
`mapping_path` to `inspect_android_class` or `inspect_android_method`. Prefer
|
||||
`detail: "summary"` to receive normalized symbols or the selected body without
|
||||
duplicate raw class metadata. Complete Evidence stays retained under the linked
|
||||
parent ID and can be exported; CLI summary delivery requires `--evidence_output`.
|
||||
Omitted or complete detail preserves the full inline response. Keep
|
||||
selecting by observed names; the separate `mapping` block reports each symbol
|
||||
as `mapped`, `ambiguous` or `unmapped` after checking DEX types and prototypes.
|
||||
REA does not verify that the mapping belongs to the APK build.
|
||||
|
||||
The provider requires a separately supplied headless JADX MCP JAR 0.7.1 and
|
||||
Java 17 or newer. `REA_JADX_MCP_JAR` selects the absolute JAR path; `JAVA_HOME`
|
||||
can select Java. Use `rea doctor --provider jadx --json` to check the JADX
|
||||
configuration and obtain remediation for missing prerequisites. Java and the
|
||||
headless JADX MCP JAR remain caller-supplied; REA setup does not install or
|
||||
configure them. Configuration readiness does not establish successful analysis
|
||||
of a particular APK.
|
||||
can select Java. REA setup does not install or configure that JAR, and doctor
|
||||
provider scopes cover Hopper/Ghidra rather than JADX. Use existing tools and
|
||||
report missing prerequisites for the Android task specifically.
|
||||
|
||||
Linux and macOS use the POSIX owned-process boundary. Windows x64 requires the
|
||||
matching bundled native controls and uses an owned Job Object with bidirectional
|
||||
stdio. A full JDK, including `jdk.compiler`, is required on every host; Windows
|
||||
`JAVA_HOME` selects `bin\java.exe`. Consult the Android guide for actual host
|
||||
verification coverage. Calls use one worker and provider deadlines; optional
|
||||
`REA_JADX_HEAP_MIB` selects the JVM heap and `REA_JADX_OPERATION_TIMEOUT_MS`
|
||||
the per-operation deadline (integer milliseconds from 1 to 2147483647, default
|
||||
120000). To change that deadline on a running MCP server, set
|
||||
`jadx.operation_timeout_ms` through `update_analysis_settings`, then retry after
|
||||
work and cleanup settle. Newly accepted operations use the override; accepted
|
||||
work keeps its budget. Changing the launch environment requires a new process.
|
||||
The MCP client's tool timeout must also allow the selected deadline. The APK is never launched,
|
||||
`REA_JADX_HEAP_MIB` selects the JVM heap. The APK is never launched,
|
||||
and no emulator is required.
|
||||
|
||||
Manifest entries and decompiled text are static observations, not execution or
|
||||
|
||||
@@ -7,23 +7,18 @@ operation and the next useful step; after repair, continue the investigation.
|
||||
Commands below use an installed `rea`. If none is available, use the package
|
||||
invocation in `SKILL.md` when acquiring REA is authorized. Follow the connected
|
||||
server's schemas and the installed CLI's help rather than assuming repository
|
||||
main and an older release expose the same arguments. Tool-name prefixes belong
|
||||
to the client: find the canonical REA operation in its actual tool listing and
|
||||
server association before concluding that it is absent. A shared prefix alone
|
||||
does not establish which server an approval wildcard covers. See
|
||||
[client tool names and discovery](https://github.com/morluto/rea/blob/main/docs/client-configuration.md#client-tool-names-and-discovery).
|
||||
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 | If REA was installed as a client plugin, follow the plugin section below. Otherwise 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 connection dropped or REA could not start | If the connection drops, retry that call once only when it is safe to repeat or known not to have started. If it may already have changed the target, or the same failure returns, tell the user REA's connection failed and ask them to restart the MCP client. If REA could not start its connection, retry once, then tell the user and ask them to restart the MCP client. Do not retry a startup failure that returns the same unchanged result. Run `rea doctor` when REA could not start or tools stay missing after restart. Doctor cannot restore a live connection by itself. |
|
||||
| `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. Request `{"detail": "complete"}` only for provider diagnostics. |
|
||||
| `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
|
||||
@@ -39,109 +34,6 @@ answers part of the question, and name the unavailable evidence separately.
|
||||
Do not substitute another target. Label observations from ordinary shell tools
|
||||
as such; only actual REA output supplies REA Evidence.
|
||||
|
||||
## Distinguish client and server deadlines
|
||||
|
||||
A host message such as `timed out awaiting tools/call` with no REA result means
|
||||
the client's deadline expired before a result arrived. It does not establish
|
||||
that REA finished, canceled successfully, or released all resources. Do not
|
||||
repeat the unchanged analysis. Inspect available session/provider state and
|
||||
retained Evidence only when useful; an idle `binary_session` is not proof that
|
||||
every target-free operation stopped. Never invent a missing Evidence reference.
|
||||
|
||||
Allow the host tool deadline to cover execution, cancellation/cleanup, and
|
||||
response delivery. In Codex, the setting is
|
||||
`mcp_servers.<server-id>.tool_timeout_sec` in **seconds**, not
|
||||
`startup_timeout_sec`. Select a value for the actual operation budget and
|
||||
headroom, not a universal timeout. See the
|
||||
[official MCP configuration reference](https://learn.chatgpt.com/docs/extend/mcp?surface=cli).
|
||||
Apply configuration changes only within the authorized scope, preserving
|
||||
unrelated entries and backups; reconnect/restart as required by the host.
|
||||
When configuration is not editable, report the setting needed or use an
|
||||
authorized CLI fallback. Do not silently broaden permissions or switch targets.
|
||||
|
||||
A returned REA `provider_timeout` instead means the **server/provider** reached
|
||||
its deadline. Follow its `remediation.action`, reuse accessible partial Evidence,
|
||||
and resolve reported incomplete cleanup before retrying. Change a request
|
||||
field or a `REA_*` setting only when the returned remediation explicitly
|
||||
recommends that change. Restart REA when that text says the value is read at
|
||||
startup. A mention that a control does not raise the deadline is not a
|
||||
recommendation to change it. Do not invent a setting the remediation does not
|
||||
name. Raising only the client deadline does not change
|
||||
this server budget. For static JavaScript,
|
||||
`analysis_timeout_ms` (CLI `rea analyze-javascript-application <path>
|
||||
--analysis-timeout-ms <milliseconds>`) controls each source's
|
||||
startup, analysis and transfer deadline in **milliseconds**. Increase that
|
||||
budget or reduce semantic work / select exact inventoried `source_paths` when
|
||||
the task permits. Omit `source_paths` for all sources; `[]` is invalid.
|
||||
`detail: "summary"` reduces delivery, not analysis work. A per-source budget
|
||||
does not bound the elapsed time for an entire multi-source application.
|
||||
Partial-Evidence inspection can avoid recomputation; an unavailable reader is
|
||||
not itself a reason to block a corrected static-analysis retry.
|
||||
The shorthand `rea analyze <path>` does not expose this timeout flag; use the
|
||||
explicit JavaScript command when changing that budget.
|
||||
|
||||
For invalid arguments, use the connected tool's current input schema, or the
|
||||
installed CLI's command help. Refresh the catalog if stale; do not guess fields
|
||||
from an older skill or return the entire catalog to repair one field. Correct
|
||||
all reported fields before retrying.
|
||||
|
||||
## Recovery ladder
|
||||
|
||||
Follow the returned remediation. Change a request field or a `REA_*` setting
|
||||
only when that text explicitly recommends the change. Restart REA when it says
|
||||
the value is read at startup. A mention that a control does not raise a limit
|
||||
is not a recommendation to change it. Do not invent a setting. Host memory and
|
||||
`NODE_OPTIONS` do not raise a REA admission unless that remediation says so.
|
||||
|
||||
- MCP connection dropped: retry that call once only when it is safe to repeat
|
||||
or known not to have started. If it may already have changed the target, or
|
||||
it fails again, tell the user REA's connection failed and ask them to restart
|
||||
the MCP client. Do not run `rea doctor` for the drop itself.
|
||||
- REA could not start its connection: retry once, then tell the user and ask
|
||||
them to restart the MCP client. Run `rea doctor` when REA could not start or
|
||||
tools stay missing after that restart. Do not retry a startup failure that
|
||||
returns the same unchanged result.
|
||||
- `cleanup_incomplete`, or retained cleanup owners in the error details: retry
|
||||
that exact cleanup owner. Do not start new work that needs those resources
|
||||
until that owner confirms release. Repeating the original call is not that
|
||||
retry.
|
||||
- Capacity, when the result is `resource_constraint` and `retryable` is true:
|
||||
wait for the active admission to finish, then retry within the reported
|
||||
limits. Do not restart REA to free the slot.
|
||||
- `configuration_invalid`: correct the named setting in the environment that
|
||||
starts REA (the shell or the MCP registration), then restart REA. Run
|
||||
`rea doctor` to see the `runtime-configuration:` check. Re-run `rea setup`
|
||||
for that client when the registration must carry the new setting, then
|
||||
reconnect.
|
||||
- `invalid_request`: correct the reported fields from the current schema. Do
|
||||
not repeat the unchanged call.
|
||||
|
||||
## REA installed as a client plugin
|
||||
|
||||
A Claude Code or Codex plugin owns its `rea` server and this skill. Doctor's
|
||||
`--client` and `--skill` checks and `rea setup` read and write only
|
||||
setup-owned state, so for a plugin-only install they report `missing`, and
|
||||
applying setup would add a second `rea` server. If this skill was loaded from
|
||||
a plugin, or the client lists a `rea` plugin, do not prepare a setup plan for
|
||||
missing tools. Instead:
|
||||
|
||||
- Check that the plugin is installed and enabled with the client's own
|
||||
listing: `claude plugin list` and `/mcp` in Claude Code, `codex plugin list`
|
||||
in Codex.
|
||||
- Restart or reconnect the client, then read its launch error for the `rea`
|
||||
server if tools are still absent. The plugin starts its bundled runtime; a missing build or version mismatch
|
||||
requires a reinstall, or a fresh build when developing from source.
|
||||
- Check analysis engines through the connected server's
|
||||
`binary_session.result.tool_availability`. `rea doctor --provider <id> --json`
|
||||
from a shell reports that shell's environment, which can differ from the
|
||||
environment the client gave the plugin server. If you still run it through
|
||||
the package runner, use the version pinned in the plugin manifest's server
|
||||
entry (`rea-agents@<version>`), not `@latest`, so it matches the server the
|
||||
plugin starts.
|
||||
|
||||
Use the setup repair below only when the user wants a setup-owned registration
|
||||
instead of the plugin, and remove or disable the plugin first.
|
||||
|
||||
## Repair registration when needed
|
||||
|
||||
For Codex:
|
||||
@@ -154,12 +46,9 @@ 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`, `trae`, `grok_build`, `omp`, `pi`,
|
||||
`hermes`, `qoder`, `droid`, `zcode`, `dsh`, `freebuff`, or `grok_bot`. If unknown, inspect `doctor --json` before choosing a
|
||||
`hermes`, `qoder`, `droid`, `zcode`, `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 manual stdio registration with the selected
|
||||
installation’s absolute runtime and entry script, or continue through the CLI.
|
||||
Acquire packages from a trusted directory outside the target project. Keep a
|
||||
package-runner installation and its runtime available while registrations use it.
|
||||
For unsupported clients, use version-pinned manual stdio registration or CLI.
|
||||
|
||||
Show the plan's exact paths, backups, changes and bundled-skill replacement.
|
||||
Obtain approval for writes when that concrete scope is not already authorized,
|
||||
@@ -204,17 +93,6 @@ writing. Managed policy can constrain client settings. Consult the
|
||||
for the installed client's supported settings. This is an optional recovery
|
||||
choice, not a prerequisite for every REA task.
|
||||
|
||||
## Repair an editable REA limit
|
||||
|
||||
If `details.limit` names an editable setting and its bounds, call
|
||||
`update_analysis_settings` directly with a `changes` entry containing `setting`,
|
||||
`action: "set"` and `value`. `inspect_analysis_settings` is optional when those
|
||||
facts are already present. Retry the same input after work and cleanup settle.
|
||||
Runtime defaults affect new work only; explicit per-call arguments take
|
||||
precedence and must be corrected directly when selected. Reset restores launch
|
||||
values. Updates survive target close/open but disappear on REA restart. They do
|
||||
not edit host deadlines, response buffers, registrations or fixed limits.
|
||||
|
||||
## Continue through an installed CLI
|
||||
|
||||
Check the command environment only if the launcher fails:
|
||||
|
||||
@@ -1,287 +0,0 @@
|
||||
# Agent environments from a live application
|
||||
|
||||
Use this when the user asks for an agent environment (an AgentEnv bundle) of a
|
||||
web application: a folder `agent-env` deploys, whose MCP tools answer from what
|
||||
one recording of the application observed and serve the writes that recording
|
||||
confirmed. The workflow records the application once with
|
||||
`capture_browser_scenario`, checks what the environment would serve, records
|
||||
again if a hint names a gap, then writes the bundle with
|
||||
`export_environment_bundle`. For a HAR the user already has, skip to
|
||||
[export](#export-and-hand-off) through `inspect_web_network_capture`, or use
|
||||
`rea environment-build <capture.har> <dir>`.
|
||||
|
||||
## Before recording
|
||||
|
||||
- Record only against an instance the user says is disposable or seeded for
|
||||
this purpose: every write step creates, edits or deletes its data. If the
|
||||
user has not said so, ask before any scenario that writes. REA does not
|
||||
start, seed or reset applications.
|
||||
- Credentials come from environment variables the user names. Declare each as a
|
||||
scenario secret, `{secret_id, environment_variable}`; the variable must be set
|
||||
in the environment REA's MCP server started with. Never ask for a password in
|
||||
chat. Declared secrets are redacted as the capture records.
|
||||
- Map the application first with `inspect_web_page` (and `capture_web_screenshot`
|
||||
when layout matters): the login form's locators, and the API calls behind the
|
||||
pages that list and edit the entities the user asked about.
|
||||
- `inspect_web_page` cannot sign in. To see pages behind sign-in, run a short
|
||||
`capture_browser_scenario` that signs in and opens them, with
|
||||
`"capture": {"after_each_step": ["accessibility"]}` (`"dom"` for markup).
|
||||
Read a step's tree with `inspect_analysis_view`, `view: {kind:
|
||||
"browser-scenario", collection: "text", step_index, artifact:
|
||||
"accessibility", offset: 0, limit: 2048}`, paging with `offset`. Capture
|
||||
options are only `after_each_step`, `at_end`, `events`, `network` and
|
||||
`limits`.
|
||||
|
||||
## Record a session an environment can learn writes from
|
||||
|
||||
One `capture_browser_scenario` records the whole session. Start from this
|
||||
shape, with the application's own locators, paths and headers:
|
||||
|
||||
```json
|
||||
{
|
||||
"browser": { "mode": "launch", "executable_path": "/usr/bin/chromium" },
|
||||
"start_url": { "url": "http://127.0.0.1:3000/login" },
|
||||
"secrets": [
|
||||
{ "secret_id": "admin_password", "environment_variable": "APP_PASSWORD" }
|
||||
],
|
||||
"capture": {
|
||||
"network": {
|
||||
"request_body": true,
|
||||
"response_body": true,
|
||||
"header_values": true
|
||||
}
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"step_id": "user",
|
||||
"action": "fill",
|
||||
"timeout_ms": 15000,
|
||||
"locator": { "kind": "css", "selector": "input[name=username]" },
|
||||
"value": { "source": "literal", "value": "admin" }
|
||||
},
|
||||
{
|
||||
"step_id": "password",
|
||||
"action": "fill",
|
||||
"timeout_ms": 15000,
|
||||
"locator": { "kind": "css", "selector": "input[name=password]" },
|
||||
"value": { "source": "secret", "secret_id": "admin_password" }
|
||||
},
|
||||
{
|
||||
"step_id": "sign-in",
|
||||
"action": "press",
|
||||
"timeout_ms": 15000,
|
||||
"locator": { "kind": "css", "selector": "input[name=password]" },
|
||||
"key": "Enter"
|
||||
},
|
||||
{
|
||||
"step_id": "signed-in",
|
||||
"action": "wait_for",
|
||||
"timeout_ms": 15000,
|
||||
"locator": { "kind": "css", "selector": "nav a[href='/settings']" },
|
||||
"state": "visible"
|
||||
},
|
||||
{
|
||||
"step_id": "folders-1",
|
||||
"action": "request",
|
||||
"timeout_ms": 15000,
|
||||
"method": "GET",
|
||||
"path": "/api/folders",
|
||||
"expected_status": [200]
|
||||
},
|
||||
{
|
||||
"step_id": "new-folder-1",
|
||||
"action": "click",
|
||||
"timeout_ms": 15000,
|
||||
"locator": { "kind": "role", "role": "button", "name": "New folder" }
|
||||
},
|
||||
{
|
||||
"step_id": "folder-title-1",
|
||||
"action": "fill",
|
||||
"timeout_ms": 15000,
|
||||
"locator": { "kind": "css", "selector": "input[name=title]" },
|
||||
"value": { "source": "literal", "value": "Scratch folder 1" }
|
||||
},
|
||||
{
|
||||
"step_id": "create-folder-1",
|
||||
"action": "click",
|
||||
"timeout_ms": 15000,
|
||||
"locator": { "kind": "role", "role": "button", "name": "Create" },
|
||||
"save_response": {
|
||||
"method": "POST",
|
||||
"path": "/api/folders",
|
||||
"save": [{ "name": "folder_1", "json_pointer": "/id" }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_id": "read-folder-1",
|
||||
"action": "request",
|
||||
"timeout_ms": 15000,
|
||||
"method": "GET",
|
||||
"path": "/api/folders/{saved:folder_1}",
|
||||
"expected_status": [200]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Give each step that accepts `timeout_ms` a deadline. Without one, a step waits
|
||||
until it completes or the scenario is cancelled, so a locator that never
|
||||
matches hangs the capture and the client with it. The `wait_for_timeout` pause
|
||||
instead uses `duration_ms` and does not accept `timeout_ms`. Match `wait_for`
|
||||
locators to exactly one element.
|
||||
|
||||
1. Sign in through the UI: `fill` the user and password fields (the password
|
||||
as a secret reference), submit, then `wait_for` an element only a signed-in
|
||||
user sees.
|
||||
2. Read with `request` steps: each collection the environment needs, twice
|
||||
before any write, so fields that change on their own show up; then each
|
||||
object a list shows by id, up to ten per collection. An object known only
|
||||
from a list that leaves out fields its own read shows cannot be read by id
|
||||
or updated in the environment.
|
||||
3. Write the way the application's UI does: fill its forms and click its
|
||||
buttons, so each request carries the fields the UI sends, including ones
|
||||
the server would otherwise derive. Give the click or key press that submits
|
||||
a create `save_response` to keep the new object's ID, then `goto` its page
|
||||
(`/items/{saved:item_1}`) or read it with a `request` step. Never guess an
|
||||
ID, and never create a probe object to learn the next one. Write with
|
||||
`request` steps instead only when the UI cannot reach the operation (an
|
||||
API-only resource, or a UI that does not call the JSON API), or when coverage
|
||||
withholds the UI-shaped write as `unexplained-change` or `contradicted`;
|
||||
that counts as one of the two re-recordings. A `request` step write that
|
||||
coverage withholds that way usually lacks fields the UI sends: record it
|
||||
through the UI instead. If a journey cannot be scripted, leave the write
|
||||
unserved and report the limitation.
|
||||
Record two numbered scratch objects per write type,
|
||||
with varied free text. After each write, and before the next one, read the
|
||||
object back by id and then its list: create 1, read 1, list, create 2,
|
||||
read 2, list; never several writes before their reads.
|
||||
4. Leave more than a second between two creates and between a create and its
|
||||
first edit (`wait_for_timeout` with `duration_ms: 1100`), so lists sorted
|
||||
by creation time show them in order.
|
||||
5. Update both objects and read each back, changing the fields a person
|
||||
would change (a name, an email address, a status, a due date) rather than
|
||||
only a description: a served update accepts only the members a recorded
|
||||
update stored. Run an action that turns something on or off (star,
|
||||
bookmark, archive) twice in a row on one object, reading it back after
|
||||
each: the second call shows whether it sets or flips the field, and the
|
||||
environment serves the action only once a call has shown it. Delete both
|
||||
and read each back (expect 404, or the object marked deleted) and the list.
|
||||
6. Vary what the two creates send, however you write them: the first every
|
||||
field the application takes, the second only what a person must give (its
|
||||
title or name), with the other fields left out or empty. A served create
|
||||
requires each member both creates filled in and lets a caller leave out one
|
||||
a create left out or sent empty, so an agent that names only a title is not
|
||||
refused for fields it had no reason to set.
|
||||
|
||||
About `request` steps:
|
||||
|
||||
- `path` is on the start URL's origin; the page must be on that origin.
|
||||
- Header values are literals, declared secrets, a cookie the browser holds
|
||||
(`{source: "cookie", name}`), a local storage entry
|
||||
(`{source: "local_storage", key, json_pointer}`), a saved value
|
||||
(`{source: "saved", name}`), or Basic credentials
|
||||
(`{source: "basic_credentials", parts: [username, password]}`, sent as the
|
||||
base64 of `username:password` and redacted), with an optional `prefix` such as
|
||||
`"Bearer "` or `"Basic "`. Each credential part is a literal, secret, saved
|
||||
value, cookie or local storage entry, so a header the frontend builds from a
|
||||
login token (the username and the token) can be copied too; the password is
|
||||
a secret or saved value, never a literal. Copy the
|
||||
headers the application's own requests send (a CSRF header such as
|
||||
`X-CSRFToken` from the `csrftoken` cookie, a bearer token). Values read from the page are redacted from records made after they
|
||||
are read, not from the login response that returned them, and values
|
||||
shorter than 8 characters are not redacted at all.
|
||||
- `save: [{name, json_pointer, secret}]` keeps a string or number from the
|
||||
step's JSON response, such as the created object's `/id`, for later steps;
|
||||
`secret: true` redacts it, as for a token the API returns, so mark every
|
||||
saved token, key or session value secret. This also protects the selected
|
||||
source response before its body or headers are retained. Compressed or
|
||||
unknown-size UI responses cannot supply saved values. On a `click` or `press`,
|
||||
`save_response: {method, path, save}` keeps values from the response to the
|
||||
new request observed while the step waits (`{*}` in `path` matches one segment).
|
||||
Matching does not prove UI causality: choose a path distinct from polling;
|
||||
multiple observed matches fail as ambiguous. The step
|
||||
fails unless a 2xx JSON response arrives before its `timeout_ms`. Later
|
||||
steps send a saved value as `{saved:name}` in a request `path` (and its
|
||||
query) or a `goto` URL; in the `json` body, where a string that is only
|
||||
`{saved:name}` keeps the saved JSON type; or as `{source: "saved", name}` in
|
||||
a header or credential part.
|
||||
- Reading a page value makes later bodies costlier to keep: after a CSRF
|
||||
token, bodies over about 47 KiB are kept as `unavailable`. When the API
|
||||
returns larger responses, raise `capture.limits`
|
||||
(`max_network_body_allocation_bytes`, `max_total_event_allocation_bytes`).
|
||||
- Request steps follow no redirects: a redirect fails the step, and
|
||||
`expected_status` cannot name a 3xx status. Use the path the redirect names,
|
||||
such as the one with the trailing slash.
|
||||
- Set `expected_status` on every step, so a refused call fails its step and
|
||||
the later steps report not having run, instead of recording read-backs of
|
||||
nothing.
|
||||
|
||||
Keep it to about 60 to 160 requests. A scenario with no `request` step is
|
||||
recorded exactly as before.
|
||||
|
||||
The capture returns its whole Evidence, every recorded body included, which is
|
||||
often more than a client shows inline. Before checking coverage, confirm that
|
||||
every entry of `normalized_result.steps` has `status: "completed"`; if the
|
||||
client saved the result to a file, search it for `"status":"failed"` and read
|
||||
that step's `error`. A failed sign-in or write cancels every step after it.
|
||||
Use the result's `evidence_id` for coverage and export; do not paste the
|
||||
Evidence back into another tool.
|
||||
|
||||
## Check coverage
|
||||
|
||||
`inspect_analysis_view` with `source: {kind: "retained-evidence",
|
||||
evidence_id}` (the capture's id) and `view: {kind: "environment-coverage",
|
||||
template_rules: ["after-uid-literal", "opaque-token", "sibling-variance",
|
||||
"prefix-variance"]}`. It reports read tools
|
||||
answered from the state or replayed (`reads.recorded_reasons`), the served write
|
||||
tools, and each withheld write with its `reason` and `hint`. It does not apply
|
||||
the serve-time catalog and state size checks; the export reports those.
|
||||
|
||||
Re-record a withheld write at most twice, and only for a recording gap. Stop at
|
||||
once when the reason is application behaviour, and report the write as
|
||||
withheld with its hint:
|
||||
|
||||
| Reason | What to do |
|
||||
| --------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `no-state-target` | Check the path template and recorded views; record missing reads. The route form may not be supported as a mutation target. |
|
||||
| `no-observation` | Re-record: read the entity back by id after the write; read it first for an update or delete. |
|
||||
| `unconfirmed` | Re-record: read the entity and its list after the write. |
|
||||
| `incomplete-entities` | Re-record: read the entity through every view that lists it. |
|
||||
| `unexplained-change` | Re-record once with a second, different call of the same write. |
|
||||
| `no-call` | Re-record once with a write that succeeds. |
|
||||
| `side-effect` | Stop: the application changes other entities. |
|
||||
| `contradicted` | Stop: a later read disagrees with the modelled effect. |
|
||||
| `body-unusable` | Re-record a UI write once with `request` steps (its body was not exposed); else stop. |
|
||||
|
||||
Use new scratch names on each attempt, and delete what an earlier attempt
|
||||
created before recording again (through the UI, or a `request` step with the
|
||||
id that capture shows): the environment starts from what the recording first
|
||||
reads, so a leftover object becomes seeded data that write tasks then find.
|
||||
New browser scenario captures include request times, so a fully timed capture
|
||||
can learn a write clock and serve updates that stamp modification times. Older
|
||||
captures or ones missing a request start keep record order and learn no clock.
|
||||
When coverage reports that limitation, record again with the current version
|
||||
before concluding that the timestamp change cannot be served.
|
||||
|
||||
## Export and hand off
|
||||
|
||||
`export_environment_bundle` with `source: {kind: "retained-evidence",
|
||||
evidence_id}` (or `{kind: "evidence-file", path}`), an absolute
|
||||
`output_directory` that does not exist yet and whose parent does, and a `name`
|
||||
(required when the application runs on `localhost` or an IP address). It returns
|
||||
the coverage again, `tool_count`, the written files and warnings; `dry_run:
|
||||
true` reports without writing. The bundle installs `rea-agents` of this REA's
|
||||
version from the registry; when the user runs an unreleased REA build, pass the
|
||||
`package_tarball` they give you (a packed `rea-agents` tarball) instead.
|
||||
|
||||
Tell the user how many tools answer from the state, which writes are served and
|
||||
which are withheld and why, and the run command (`agent-env run <folder>`). Say
|
||||
that the bundle ships the recorded responses and the capture's local paths
|
||||
(for a browser scenario, the browser executable's path), and
|
||||
list any `credential-named-members` warning pointers for review. Never publish
|
||||
or share the bundle yourself.
|
||||
|
||||
See
|
||||
[agent environments](https://github.com/morluto/rea/blob/main/docs/agent-environments.md)
|
||||
for what the state, replay and the bundle establish, and
|
||||
[evidence workflows](evidence-workflows.md) for the manifest and state views.
|
||||
@@ -11,44 +11,6 @@ returned inline. When complete `inspect_binary_layout` or
|
||||
`inspect_analysis_view` for a summary, one section/symbol/module, or a stable
|
||||
page instead of repeating the producer.
|
||||
|
||||
For any logical Evidence source that advertises retained references, pass
|
||||
`{kind: "retained-evidence", evidence_id}` instead of copying the complete
|
||||
record back into a call. The same contract covers individual, optional, array
|
||||
and nested sources across native, process, JavaScript, managed, mobile and WASM
|
||||
workflows. Sources can independently mix inline records and exact references.
|
||||
Resolution preserves the source's identity, provenance, coverage, partial facts
|
||||
and unknowns; it neither reruns analysis nor makes incomplete facts complete.
|
||||
Keep domain role and identity requirements: a native dossier cannot substitute
|
||||
for a managed member observation, for example.
|
||||
|
||||
When starting from a summary or inspection page, use its
|
||||
`normalized_result.parent_evidence_id` to reference the complete source.
|
||||
The view's own `evidence_id` names
|
||||
only that view. `compare_artifacts` explicitly supports artifact summary inputs
|
||||
and resolves their parents; do not assume every consumer does. When a workflow
|
||||
follows source links, keep those linked records available too.
|
||||
|
||||
MCP references belong to the current connection's ledger. If the ID is known,
|
||||
pass it directly; no discovery call or provider setup is needed. If it is missing,
|
||||
import the exported bundle or supply complete inline Evidence. Do not retry an
|
||||
unchanged reference or rerun a producer merely to move an existing record.
|
||||
Export before closing the session when the source must survive it. For CLI JSON
|
||||
workflows that advertise `--evidence-bundle`, the same reference JSON resolves
|
||||
against one explicitly selected authenticated bundle:
|
||||
|
||||
```bash
|
||||
rea trace-application-feature request.json --evidence-bundle /absolute/path/evidence.json --json
|
||||
```
|
||||
|
||||
This includes JavaScript traces/comparisons and runtime reconciliation, managed
|
||||
reconstruction/native verification/graph projection, Android/Apple/Harmony graph
|
||||
projection, WASM interface comparison and retained analysis views. The managed
|
||||
member comparison CLI still takes two assembly paths; process comparison takes
|
||||
two capture Evidence JSON paths. Check command help for the installed version.
|
||||
An input bundle grants no implicit file lookup: `evidence-file` and historical
|
||||
graph file selectors are accepted only by contracts advertising them. See the
|
||||
[CLI reuse guide](https://github.com/morluto/rea/blob/main/docs/cli.md#import-export-and-compare-evidence).
|
||||
|
||||
Completed `inspect_artifact` inventory also supports a summary and
|
||||
`view: {kind: "inventory", collection: "occurrences", logical_paths: ["package.json", "main.js"]}`.
|
||||
Omit `logical_paths` to page all available occurrences; `[]` selects none.
|
||||
@@ -62,25 +24,6 @@ retain its complete source and receive that view immediately. Without that
|
||||
advertised choice, keep the existing complete call. CLI inspection returns
|
||||
portable complete Evidence; save it before projecting a selected inline view.
|
||||
|
||||
For native procedure/name/string inventories, select `detail: "summary"` when
|
||||
the connected tool advertises it and the task needs counts or selected rows.
|
||||
Use its parent Evidence ID with
|
||||
`view: {kind: "native-inventory", offset: 0, limit: 32, fields: ["value"]}`.
|
||||
Search tools retain the existing pattern/mode/case/document selection. Omit
|
||||
`fields` for complete records; `record.address` and `source_index` remain on
|
||||
selected rows. Selecting `value` keeps its string/decoding qualifications.
|
||||
Parent Evidence ID plus source index preserves same-address
|
||||
alternatives. Follow `coverage.next_offset` only as far as the question needs;
|
||||
exhausting retained rows does not establish complete program discovery.
|
||||
|
||||
`batch_decompile` returns every body unless you select `detail: "summary"` when
|
||||
the connected tool advertises it. Use its parent Evidence ID with
|
||||
`view: {kind: "batch-decompile", collection: "items"}` for body-free rows and
|
||||
exact `where.indices`, `addresses`, `procedure_addresses` or `statuses`. Add
|
||||
`include_pseudocode: true` for several chosen bodies, or use
|
||||
`collection: "pseudocode"` with one row's `index` to page a large body. Batch
|
||||
limitations aggregate every item, so do not attribute them to one successful row.
|
||||
|
||||
Use `record_unknown` to track unresolved questions in the user's investigation
|
||||
and name the authority or environment still required. Supply supporting and
|
||||
contradicting evidence IDs. It updates the session registry directly.
|
||||
@@ -92,22 +35,16 @@ Use comparisons with complete, compatible page sets when claiming equivalence
|
||||
or absence:
|
||||
|
||||
- `compare_artifacts` compares inventory pages by occurrence path, content,
|
||||
metadata, and graph relations. Name each side as
|
||||
`{kind: "retained-evidence", evidence_id}` (an `inspect_artifact` result,
|
||||
its nested inventory or its summary) or
|
||||
`{kind: "evidence-file", path, evidence_id?}` instead of resending Evidence.
|
||||
metadata, and graph relations.
|
||||
- `compare_functions` compares explicit function Evidence, not fuzzy
|
||||
whole-binary matches. It rejects an `analyze_function` summary reply; pass
|
||||
the summary's `parent_evidence_id` as a `retained-evidence` reference or
|
||||
the complete dossier.
|
||||
whole-binary matches.
|
||||
- `compare_bundles` compares canonical bundle membership and unknown history.
|
||||
- `find_changed_behavior` combines existing comparisons; static differences
|
||||
remain candidates, not causal proof. Its `comparisons` accept exact references.
|
||||
remain candidates, not causal proof.
|
||||
- `build_call_path` needs explicit function dossiers from one artifact/provider;
|
||||
pass complete parent IDs in `functions`. An incomplete frontier makes absence
|
||||
unknown.
|
||||
an incomplete frontier makes absence unknown.
|
||||
- `correlate_static_and_runtime` uses explicit mappings; matching patterns are
|
||||
hypotheses, not causality. Both comparison arrays accept exact references.
|
||||
hypotheses, not causality.
|
||||
- `verify_reconstruction` evaluates a finite typed specification. A pass covers
|
||||
only declared comparable claims, not global equivalence.
|
||||
|
||||
@@ -128,49 +65,3 @@ Use `inspect_analysis_view` with `view: {kind: "api-surface"}` on retained
|
||||
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.
|
||||
When the connected schema advertises `include_query_parameter_names`, select
|
||||
it in the view for distinct observed names with original record ordinals. Query
|
||||
values are omitted; the names establish neither required parameters nor a
|
||||
complete server schema. Keep the original parent Evidence for exact URLs.
|
||||
|
||||
Use `inspect_analysis_view` with `view: {kind: "environment-manifest"}` on the
|
||||
same retained HAR Evidence to project the primary origin's request lines into
|
||||
named tools with path templates, parameters, observed query names, value-free
|
||||
body shapes and record pointers for an agent environment. Opt into
|
||||
`template_rules` only when recorded identifiers need them; every template,
|
||||
parameter and merged sibling is an inference, no header, cookie or body value is
|
||||
copied, and the three stated unknowns stay unresolved.
|
||||
|
||||
Use `view: {kind: "environment-state"}` with the manifest's `template_rules` to
|
||||
extract the data those reads observed instead: entity types and identity
|
||||
fields, entities with recorded values, documents, and per-tool render plans
|
||||
checked against the recordings. The state comes only from reads before the
|
||||
first related write; types, identity and list membership are inferences. The
|
||||
view also gives each state-changing tool a write plan, served only when a later
|
||||
recorded read confirms its effect and none contradicts it.
|
||||
`rea environment-serve manifest.json --evidence capture.json` derives that
|
||||
state at startup, answers read tools from it, serves the write tools whose plan
|
||||
is served, each applying its effect to that state, and serves `data/reset`,
|
||||
`data/add` and `data/get` on `/agentenv`; other write tools are not served, and
|
||||
each plan states why in its `reason`. It replays the capture instead with
|
||||
`--replay-only` or when the state is unavailable (not derivable, or a catalog
|
||||
over the discovery budget), and its stopped document's `state` says which
|
||||
applied.
|
||||
Use `view: {kind: "environment-coverage"}` with the same rules to see what an
|
||||
environment built from a capture would serve: read tools answered from the
|
||||
state or replayed with their reason, and each write tool served or withheld
|
||||
with a hint about what to record next; it does not apply the serve-time
|
||||
catalog and state size checks. `rea environment-build <capture.har> <dir>`
|
||||
inspects, projects and exports in one step and prints the same coverage, or
|
||||
`null` when the bundle serves replay.
|
||||
|
||||
For a PE/ELF export question, `inspect_native_exports` accepts `detail: "summary"`
|
||||
and retains the complete table. Query `inspect_analysis_view` with
|
||||
`view: {kind: "native-exports", collection: "exports", names: ["selected_name"],
|
||||
fields: ["name"], offset: 0, limit: 32}` and the reported parent Evidence ID.
|
||||
All same-name versions and aliases survive; use `source_index` with the parent ID
|
||||
for stable identity. `collection: "issues"` pages interpretation failures. Page
|
||||
coverage describes selected retained rows; `summary.coverage` or `exports.coverage`
|
||||
still describes the source table, so an empty partial page does not prove absence.
|
||||
CLI `inspect-native-exports --detail summary --evidence_output PATH` saves the
|
||||
complete bundle for later `inspect-analysis-view --evidence-bundle PATH` requests.
|
||||
@@ -17,21 +17,10 @@ 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, or `--source-paths-json` with a JSON
|
||||
file for selections too long for one command line. Omit the selection when the
|
||||
question requires complete application analysis; `source_paths: []` is invalid,
|
||||
not an unfiltered request. `detail: "summary"` reduces delivery
|
||||
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.
|
||||
|
||||
ASAR header/index admission defaults to 16 MiB / 16777216 bytes;
|
||||
an admission error names `asar.max_header_bytes` and its bounds (at most
|
||||
256 MiB). Call `update_analysis_settings` with that setting, `action: "set"`
|
||||
and enough bytes, then retry the same full-archive request. No inspection or
|
||||
REA restart is required. `REA_ASAR_MAX_HEADER_BYTES` seeds future REA processes.
|
||||
`source_paths`, `detail`, and `max_heap_mb` do not bypass or raise header admission.
|
||||
Runtime JavaScript timeout/heap defaults are also editable; explicit per-call
|
||||
`analysis_timeout_ms` and `max_heap_mb` take precedence.
|
||||
|
||||
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
|
||||
@@ -46,9 +35,7 @@ the complete application Evidence inline when it fits.
|
||||
When the connected schema advertises `max_heap_mb` and `analysis_timeout_ms`,
|
||||
these select the isolated source analyzer's old-space heap and per-source
|
||||
deadline. Defaults are 1024 MiB and 300000 ms. They do not change the MCP
|
||||
process heap or the client's tool deadline. For a host timeout without REA
|
||||
output, use the [deadline recovery guide](connection-and-recovery.md#distinguish-client-and-server-deadlines)
|
||||
instead of raising the server budget. A Bun-hosted REA does not enforce the heap choice; on Windows it
|
||||
process heap. A Bun-hosted REA does not enforce the heap choice; on Windows it
|
||||
instead limits the analyzer's committed memory to that choice plus a 512 MiB
|
||||
runtime reserve.
|
||||
Resource-limited analyses preserve completed static facts and
|
||||
@@ -64,29 +51,6 @@ in the same session. A returned `error.details.partial_observation` reference
|
||||
can be supplied directly to `inspect_analysis_view`. If the SDK cancels locally, retrieve its recorded ID from
|
||||
the Evidence bundle before inspecting the partial analysis.
|
||||
|
||||
## Inventory route candidates without returning the application graph
|
||||
|
||||
When the advertised schema supports `view`, use `detail: "summary"` with
|
||||
`view: {kind: "javascript-routes", collection: "routes", fields: ["path", "confidence"], limit: 20}`.
|
||||
The same view works with `analyze_web_bundle` on already selected captured sources.
|
||||
The complete source Evidence is retained; use its `parent_evidence_id` with
|
||||
`inspect_analysis_view` for `summary`, `routes`, `occurrences`, `sources`, or
|
||||
`unknowns`. Filter and batch IDs before requesting more fields, and follow
|
||||
`coverage.next_offset`. CLI selections require `--evidence_output` to keep the
|
||||
complete record. Static patterns, inferred deployment bases, heuristic system
|
||||
classification, and unavailable sources never establish route reachability or
|
||||
the complete application route denominator. See the
|
||||
[route inventory guide](https://github.com/morluto/rea/blob/main/docs/javascript-route-inventory.md).
|
||||
|
||||
For captured endpoint observations and inference facts, `analyze_web_bundle`
|
||||
also accepts `view: {kind: "web-bundle", collection: "endpoints", limit: 32}`
|
||||
with `detail: "summary"`. Read later `endpoints`, `inferences`, or `summary`
|
||||
pages from the retained parent. Filter before requesting detail; retain
|
||||
`source_index` together with parent ID and collection, and request `location`
|
||||
or `basis` when tracing sources. Page exhaustion does not mean producer
|
||||
completeness: check `completeness`, structured `source_unknowns`, and limitations.
|
||||
Endpoint candidates remain inferences rather than confirmed network behavior.
|
||||
|
||||
## Read a module without repeating analysis
|
||||
|
||||
Use the actual returned parent ID, on the same connection, as `source.evidence_id`.
|
||||
@@ -130,36 +94,6 @@ recorded observations, identity, hashes, exports and source ranges; it does not
|
||||
include every child syntax observation, related edge or original source file.
|
||||
An absent item fact is not evidence of absence from the whole application.
|
||||
|
||||
For canonical identities and retained source-range counts, use a typed reference
|
||||
page instead of reading every full item:
|
||||
|
||||
```json
|
||||
{
|
||||
"source": {
|
||||
"kind": "retained-evidence",
|
||||
"evidence_id": "ev_ACTUAL_PARENT_ID"
|
||||
},
|
||||
"view": {
|
||||
"kind": "javascript",
|
||||
"collection": "modules",
|
||||
"projection": "references",
|
||||
"where": { "path": "main.js", "kind": "javascript-module" },
|
||||
"offset": 0,
|
||||
"limit": 16
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Filters match exact retained strings; omit a filter to include all values, use
|
||||
`path: null` for unavailable paths, and `path: ""` for a recorded empty path.
|
||||
Rows preserve canonical `identity` and distinct observation support states in
|
||||
`basis`. `retained_source_range_count` counts retained source-range observations,
|
||||
not runtime imports or unique reference sites. Use a row's `node_id` with the item
|
||||
view above for exact locations and full supporting observations. Follow
|
||||
`coverage.next_offset`; page exhaustion describes matching retained rows only.
|
||||
Check `source_coverage` for source selection, partial/unknown graph coverage and
|
||||
unknown counts before drawing a negative conclusion.
|
||||
|
||||
## Trace only the remaining question
|
||||
|
||||
Use `trace_application_feature` on the parent application Evidence for one
|
||||
@@ -169,10 +103,7 @@ leaving it, and `both` when the question needs both. Trace traversal includes
|
||||
containment relationships, so even a narrow seed can expand many nodes; a
|
||||
bidirectional trace can reach unrelated sibling modules. A module item or page
|
||||
is preferable when its observations already answer the question. Do not treat
|
||||
all reached nodes as participants in the feature. For a broad trace, pass
|
||||
`view: {"kind": "feature-trace", "collection": "paths"}` (or `summary`) to
|
||||
retain the complete trace and read terminal paths first; page `nodes` or `edges`
|
||||
with exact `where.node_ids` through `inspect_analysis_view` as needed.
|
||||
all reached nodes as participants in the feature.
|
||||
|
||||
Reuse retained parent Evidence when the server advertises it, or supply complete
|
||||
Evidence inline. Include Hopper or Ghidra Evidence only when its artifact digest
|
||||
@@ -234,33 +165,11 @@ accept complete inline Evidence or
|
||||
`{"kind":"retained-evidence","evidence_id":"ev_<64 lowercase hex characters>"}`
|
||||
for their application input (`application`, or `left`/`right`). `inspect_analysis_view`
|
||||
uses the same retained-reference form in `source`, or portable inline Evidence,
|
||||
to project a summary, module page, or one module with its recorded observations.
|
||||
This notation is
|
||||
to project a summary, module page, or one module with its recorded observations. This notation is
|
||||
a template: replace it with the actual returned ID. Versions before 4.1.0 accept only full
|
||||
inline Evidence. Use the exact ID
|
||||
returned by the producer on the same MCP connection. Resolution does not run
|
||||
analysis or select a provider; findings remain inline. For summary or inspection
|
||||
replies, select the complete `normalized_result.parent_evidence_id`. Native
|
||||
observation arrays and runtime reconciliation's `static_layers[].analysis` and
|
||||
`runtime_observations[]` also accept exact references where advertised. Each
|
||||
source can independently be inline or retained, preserving identity, coverage
|
||||
and unknowns. CLI workflows advertising `--evidence-bundle` resolve these inputs
|
||||
from the explicitly selected bundle. `close_binary` releases the engine while
|
||||
retaining references and complete Unknown history on the same MCP connection.
|
||||
`reset_investigation` explicitly clears them; optional `save_path` checkpoints the
|
||||
complete bundle before clearing. Import that bundle in another connection to
|
||||
restore its original references. Retention is bounded, in-memory and never silently
|
||||
evicted; `binary_session.investigation` reports the budget. A missing reference
|
||||
includes its ID and recovery guidance.
|
||||
|
||||
For exact file/native metadata or processing gaps, use
|
||||
`view: {kind: "javascript-facts", where: {paths: ["package.json"]}, property_keys: ["name", "operation", "analysis_status"], limit: 32}`.
|
||||
Rows retain canonical node/observation identity and original Evidence. Paths
|
||||
match Evidence artifact-path/source-range locations; missing properties remain
|
||||
omitted and source coverage stays separate from page exhaustion. Batch paths and
|
||||
select only needed property keys; complete Evidence remains accessible by export.
|
||||
If names are unknown, opt into `include_available_property_keys: true`; it lists
|
||||
the original keys per returned observation without unselected values. Keep each
|
||||
row's node/observation identity paired when composing facts.
|
||||
These rows select application-graph observations; semantic relationships and
|
||||
semantic unknown records remain in the complete parent Evidence and trace tools.
|
||||
analysis or select a provider; findings remain inline. Native observation
|
||||
arrays still take full Evidence. `close_binary` clears retained references;
|
||||
export a bundle before closing, or supply portable inline Evidence in another
|
||||
connection. A missing reference includes its ID and recovery guidance.
|
||||
@@ -1,71 +1,8 @@
|
||||
# Native, managed, and packaged artifacts
|
||||
|
||||
For release comparisons on one MCP connection, retain `list_procedures` Evidence
|
||||
from each target and reuse its exact IDs after target switches or `close_binary`.
|
||||
For a handoff or new process, export the complete bundle and import it there,
|
||||
including its source closure. `reset_investigation` explicitly clears retained
|
||||
records; optional `save_path` checkpoints them before clearing.
|
||||
`match_native_functions` pairs unique exact names and, when supplied,
|
||||
complete observed body bytes. Reuse optional same-context `list_names`,
|
||||
`procedure_info`, `read_bytes` and existing `analyze_function` dossiers as
|
||||
`left_observations`/`right_observations`; an MCP `analyze_function` summary
|
||||
reply contributes its `parent_evidence_id` as a `retained-evidence` reference,
|
||||
not the summary itself. Avoid decompiling every function merely to match it. Its summary retains a parent for
|
||||
`inspect_analysis_view` `function-matches` collections `matches` and `functions`.
|
||||
Select columns, batch IDs and page individual facts; aliases remain unresolved.
|
||||
Unpaired observations do not prove additions/removals. Existing matched dossier
|
||||
pairs include inputs for `compare_functions`; otherwise analyze just chosen pairs.
|
||||
|
||||
Aggregate completed comparisons with `find_changed_behavior`, selecting each
|
||||
comparison's exact `retained-evidence` ID instead of resending full records.
|
||||
Inline comparison Evidence remains accepted. References belong to the current
|
||||
server: after handoff, import the original bundle including its source closure.
|
||||
Do not substitute dossier IDs or summary views. Static differences remain
|
||||
behavior candidates, with runtime behavior unknown unless separately observed.
|
||||
|
||||
For a matching representation refusal, inspect `details.limit` and raise
|
||||
`native.matching_representation_bytes` with `update_analysis_settings` before
|
||||
retrying the unchanged MCP request. CLI launch seed:
|
||||
`REA_NATIVE_MATCHING_REPRESENTATION_BYTES`. The default is 96 MiB; the policy
|
||||
range is 1 byte to 1 GiB and does not bound process RSS. Keep the complete
|
||||
inventories: splitting them changes global name/body uniqueness.
|
||||
|
||||
## Apple app resources
|
||||
|
||||
Use `open_bundle_resources` with the selected app path, then `inspect_plist`,
|
||||
`decode_interface_builder`, `inspect_asset_catalog`, or `inspect_keyed_archive`.
|
||||
The resource session reads the app without resolving `CFBundleExecutable` or
|
||||
starting a deep provider. Preserve `resource_root` provenance, per-resource
|
||||
hashes where reported, and null executable identity; a directory path is not
|
||||
an executable digest. Export retained Evidence before closing or switching
|
||||
roots. Binary analysis snapshots do not apply. Use `open_binary` to switch to
|
||||
executable analysis, and `close_binary` to release either session kind.
|
||||
|
||||
The four matching CLI commands accept the app path directly. For an archive
|
||||
inside the app, use `inspect-keyed-archive APP_PATH RELATIVE_ARCHIVE_PATH`.
|
||||
Host requirements for plist and compiled asset/Interface Builder tooling still
|
||||
apply; a resource-only session does not establish provider availability.
|
||||
|
||||
## Desktop installation directories
|
||||
|
||||
When an installation directory is supplied and its runtime is unknown, use
|
||||
`inspect_desktop_application` with its absolute path. Default MCP summary
|
||||
delivery retains complete Evidence. Read `files`, `omissions`, `candidates`, `components` or
|
||||
`next_probes` with `inspect_analysis_view` kind `desktop-application`, selecting
|
||||
exact paths, batched file/omission IDs or families and only needed fields and pages.
|
||||
Summary `probe_issues` retains counts and bounded previews. Select files with
|
||||
`has_issues: true` and fields `path`, `read_status`, `issues` to retrieve exact
|
||||
failures directly; preview text is display data, while `file_id` selects full facts. Preserve source
|
||||
coverage, read-range identity and unknowns; directory snapshot identity is not
|
||||
a digest of every file's contents. Component path markers do not establish
|
||||
runtime presence. For compact CLI output, use
|
||||
`inspect-desktop-application PATH --detail summary --evidence_output BUNDLE`.
|
||||
See [desktop installations](https://github.com/morluto/rea/blob/main/docs/desktop-applications.md).
|
||||
|
||||
## Go build metadata
|
||||
|
||||
Use `inspect_go_binary` for an explicit local ELF, PE or Mach-O (universal
|
||||
files need `architecture`) when the
|
||||
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`.
|
||||
@@ -74,153 +11,6 @@ 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.
|
||||
|
||||
## Model files
|
||||
|
||||
Use `inspect_model_artifact` (`inspect-model-artifact PATH`) for local GGUF v2/v3
|
||||
and safetensors declarations without a model runtime. Header-only mode does not
|
||||
read weights or establish full-file identity. `hash: "full"` explicitly streams
|
||||
all bytes. Retained `model-artifact` views page `metadata`, `tensors` and derived
|
||||
`types`, with exact batch filters and selected fields. Arrays retain typed counts
|
||||
and eight-element previews; omitted values, unknown types and runtime behavior
|
||||
stay explicit. See [model files](https://github.com/morluto/rea/blob/main/docs/model-artifacts.md).
|
||||
|
||||
## IL2CPP metadata
|
||||
|
||||
Use `inspect_il2cpp_metadata` for an explicit local Unity `global-metadata.dat`
|
||||
(versions 24, 27, 29, 31 and 39) when the question concerns assembly images,
|
||||
names, member owners, string literals, namespaces or index ranges. No `open_binary`, Unity installation or target
|
||||
execution is required. The CLI equivalent is `inspect-il2cpp-metadata PATH`.
|
||||
MCP defaults to counts with complete retained Evidence. Query `sections`,
|
||||
`images`, `types`, `methods`, `fields`, `properties`, `events`, `parameters` or
|
||||
`string_literals` through `inspect_analysis_view`; use exact names,
|
||||
batch indices and selected fields. Row indices are scoped by artifact SHA-256
|
||||
and collection; page offsets count filtered matches. Parameters support batch
|
||||
`method_indices` to read one method's parameters directly. Historical reports
|
||||
without member collections retain unknown counts rather than empty inventories. Preserve
|
||||
artifact SHA-256 and index widths. Unrecognized magic may mean encryption and
|
||||
is unsupported, not decrypted. Linking names to `GameAssembly` or
|
||||
`UnityFramework` code addresses remains a separate native-analysis question.
|
||||
See [IL2CPP metadata](https://github.com/morluto/rea/blob/main/docs/il2cpp-metadata.md).
|
||||
|
||||
## Rust dependency metadata
|
||||
|
||||
Use `inspect_rust_binary` for an explicit local ELF, PE, or Mach-O (universal
|
||||
files need `architecture`) when
|
||||
the question concerns the cargo-auditable crate list embedded in `.dep-v0`
|
||||
(Mach-O: `__DATA,.dep-v0`). No `open_binary`, Rust installation, or target
|
||||
execution is required. The CLI equivalent is `inspect-rust-binary PATH`.
|
||||
Preserve artifact SHA-256, package order, dependency indices, and the section
|
||||
file range. A missing section is null metadata, not proof the binary has no
|
||||
dependencies. An omitted package kind stays null. A rustc commit hash is
|
||||
inferred from a read-only `/rustc/<40-hex>/` path and does not by itself
|
||||
identify the compiler. See [Rust binaries](https://github.com/morluto/rea/blob/main/docs/rust-binaries.md).
|
||||
|
||||
## PE Authenticode certificate tables
|
||||
|
||||
Use `inspect_pe_certificates` for an explicit local PE32/PE32+ image when the
|
||||
question concerns the Authenticode certificate table, WIN_CERTIFICATE types, or
|
||||
embedded PKCS#7 signer and X.509 identity fields. No `open_binary`, Windows SDK,
|
||||
or target execution is required. The CLI equivalent is
|
||||
`inspect-pe-certificates PATH`. Preserve artifact SHA-256, table file ranges,
|
||||
payload hashes, original validity-time spellings, and
|
||||
`subject_key_identifier_hex` when the signer uses that form. A missing
|
||||
`IMAGE_DIRECTORY_ENTRY_SECURITY` directory is observed absence of that table,
|
||||
not proof the image is unsigned in every catalog or overlay sense. Signatures,
|
||||
chains, timestamps, and Authenticode digest coverage remain unobserved. See
|
||||
[PE Authenticode certificate tables](https://github.com/morluto/rea/blob/main/docs/pe-certificates.md).
|
||||
|
||||
## PE import and delay-load tables
|
||||
|
||||
Use `inspect_pe_imports` for an explicit local PE32/PE32+ image when the
|
||||
question concerns imported DLL and function names, ordinals, delay-load
|
||||
descriptors, or the pefile import hash. No `open_binary`, Windows SDK, or
|
||||
target execution is required. The CLI equivalent is `inspect-pe-imports PATH`.
|
||||
Preserve artifact SHA-256, directory file ranges, original DLL spellings, and
|
||||
ILT/IAT RVAs. A missing `IMAGE_DIRECTORY_ENTRY_IMPORT` directory is observed
|
||||
absence of that table, not proof the image never imported APIs through another
|
||||
table or overlay. Bound snapshots, loaded IAT contents, and export-forward
|
||||
resolution remain unobserved. See
|
||||
[PE import and delay-load tables](https://github.com/morluto/rea/blob/main/docs/pe-imports.md).
|
||||
|
||||
## PE TLS callbacks and load configuration
|
||||
|
||||
Use `inspect_pe_load_config` for an explicit local PE32/PE32+ image when the
|
||||
question concerns code that runs before the entry point through TLS callbacks,
|
||||
or the security cookie, SafeSEH, CFG, EH-continuation and XFG declarations in
|
||||
the load configuration. No `open_binary`, Windows SDK, or target execution is
|
||||
required. The CLI equivalent is `inspect-pe-load-config PATH`. Preserve artifact
|
||||
SHA-256, directory and callback-array file ranges, and callback VAs as
|
||||
hexadecimal strings. Fields outside the declared `Size` are `null`. Guard-flag
|
||||
mitigations carry `evidence_kind: "inferred"`; enforcement remains unobserved.
|
||||
See
|
||||
[PE TLS callbacks and load configuration](https://github.com/morluto/rea/blob/main/docs/pe-load-config.md).
|
||||
|
||||
## ELF note tables
|
||||
|
||||
Use `inspect_elf_notes` for an explicit local ELF32/ELF64 image when the
|
||||
question concerns PT_NOTE/SHT_NOTE owners, GNU build-id, or GNU ABI-tag
|
||||
fields. No `open_binary` or target execution is required. The CLI equivalent
|
||||
is `inspect-elf-notes PATH`. Preserve artifact SHA-256, table file ranges,
|
||||
descriptor hashes, and original owner spellings. A missing table is observed
|
||||
absence of those declarations, not proof the image never carried notes in
|
||||
another container. See
|
||||
[ELF note tables](https://github.com/morluto/rea/blob/main/docs/elf-notes.md).
|
||||
|
||||
## Apple IPS crash reports
|
||||
|
||||
Use `inspect_apple_ips` for an explicit local Apple `.ips` file when the
|
||||
question concerns crash metadata, exception and termination facts, threads,
|
||||
usedImages, lossless registers, or documented crash classes. No `open_binary`,
|
||||
Xcode, atos, or target execution is required. The CLI equivalent is
|
||||
`inspect-apple-ips PATH`. Preserve artifact SHA-256, `bug_type` as a string,
|
||||
uppercase image UUIDs, and integer literals that exceed IEEE-754 range. Only
|
||||
`bug_type` `"309"` yields a crash body; other DiagnosticReports types return
|
||||
no crash result. Opened Mach-O and dSYM UUID joins remain unobserved. See
|
||||
[Apple IPS crash reports](https://github.com/morluto/rea/blob/main/docs/apple-ips.md).
|
||||
|
||||
## Linux kernel modules
|
||||
|
||||
Use `inspect_kernel_module` for an explicit local ELF32/ELF64 relocatable
|
||||
object when the question concerns `.modinfo` license, vermagic, alias,
|
||||
depends, or parm strings, the `__versions` symbol CRCs, or an appended module
|
||||
signature trailer. No `open_binary`, Linux host, or `modinfo` program is
|
||||
required. The CLI equivalent is `inspect-kernel-module PATH`. Preserve
|
||||
artifact SHA-256, key order, duplicate keys, CRC hex, and section file ranges.
|
||||
A missing section is observed absence when section names are known, not proof
|
||||
the module never carried that metadata. `.gnu.linkonce.this_module` is reported
|
||||
as present or absent; `struct module` stays unknown because its layout changes
|
||||
with the kernel version. `__ksymtab` records are not decoded. The PKCS#7 range
|
||||
and its SHA-256 are observed; certificate, signer, and digest coverage remain
|
||||
unvalidated. An absent trailer is not proof the module is unsigned in another
|
||||
container. gzip, xz, and zstd whole-file containers are unsupported and name
|
||||
`external_decompression` as the next probe: use an external codec utility to
|
||||
decompress into a separate file, then call `inspect_kernel_module` on that file. See
|
||||
[Linux kernel modules](https://github.com/morluto/rea/blob/main/docs/kernel-modules.md).
|
||||
|
||||
## CPython pyc files
|
||||
|
||||
Use `inspect_python_pyc` for an explicit local CPython 3.7–3.13 `.pyc` when the
|
||||
question concerns the header magic, PEP 552 source hash or timestamp, nested
|
||||
code-object names, or bytecode identity. No `open_binary`, Python installation,
|
||||
or target execution is required. The CLI equivalent is `inspect-python-pyc PATH`.
|
||||
Preserve artifact SHA-256, the 16-byte header fields, nested code names, and
|
||||
bytecode SHA-256. Bytecode is not decompiled. A decoded header does not prove
|
||||
the matching `.py` is present. See [CPython pyc inspection](https://github.com/morluto/rea/blob/main/docs/python-pyc.md).
|
||||
|
||||
## Erlang BEAM files
|
||||
|
||||
Use `inspect_erlang_beam` for an explicit local `FOR1`/`BEAM` file when the
|
||||
question concerns IFF chunks, atom names, exports, imports, lambdas, or the
|
||||
Code header. No `open_binary`, Erlang installation, or bytecode execution is
|
||||
required. The CLI equivalent is `inspect-erlang-beam PATH`. Preserve artifact
|
||||
SHA-256, chunk file ranges, 1-based atom indices, and original atom spellings.
|
||||
Missing optional tables stay null. For a few facts, use `detail: "summary"` with
|
||||
a `beam` view selecting a collection, exact names/indices and fields; read later
|
||||
pages from `parent_evidence_id` or the complete Evidence bundle. Same-name
|
||||
exports retain every arity and original atom index. CLI summary delivery requires
|
||||
`--evidence_output`. Null table counts remain distinct from decoded empty tables. See
|
||||
[Erlang BEAM files](https://github.com/morluto/rea/blob/main/docs/erlang-beam.md).
|
||||
|
||||
## SQLite snapshots
|
||||
|
||||
Use `inspect_sqlite_database` with an explicit local database snapshot for its
|
||||
@@ -275,30 +65,6 @@ and tool profile. Text offsets count UTF-16 code units; follow `next_offset`.
|
||||
The complete source remains retained after an oversized MCP reply. No target
|
||||
execution or network acquisition is implied, and WAT is decoded representation.
|
||||
|
||||
## Offline APK signatures
|
||||
|
||||
Use `inspect_apk_signature` for a selected local APK/ZIP/AAB. Default MCP summaries
|
||||
retain complete Evidence. Select `view.collection: signers/digests/lineage`,
|
||||
with scheme or ID batches, fields and paging; use `inspect_analysis_view` kind
|
||||
`apk-signature` for retained reads after deletion. IDs retain artifact/scheme
|
||||
scope and signer links; `fields: []` keeps identity links. Empty pages preserve
|
||||
partial decoding reasons and unknown authenticity. CLI compact delivery needs
|
||||
`--evidence_output` to preserve the complete parent.
|
||||
It reads the APK
|
||||
Signing Block (v2/v3/v3.1) and v1 `META-INF` signing without executing anything,
|
||||
and reports each signer's certificate facts (for v1, the certificate the PKCS#7
|
||||
block's SignerInfo names), the declared v3 rotation lineage,
|
||||
the declared source-stamp certificate and, for SHA-256/SHA-512 digests, whether
|
||||
the declared content digest matches the current bytes. That match is not a
|
||||
signature verification: anyone who changes the bytes can recompute the digest.
|
||||
`posture` summarises v1-only signing (which cannot tell a v1-only APK from one
|
||||
whose v2/v3 block was removed), observed signer agreement across schemes,
|
||||
whether the v1 signer equals the block-scheme signer (`false` is notable),
|
||||
whether every differing signer appears in a declared (unverified) rotation
|
||||
lineage, and the strongest scheme.
|
||||
It never claims authenticity: no signature or lineage is cryptographically
|
||||
verified.
|
||||
|
||||
## Native targets
|
||||
|
||||
After `open_binary`, use focused search, procedure, or function tools directly.
|
||||
@@ -311,9 +77,6 @@ unknown rather than false.
|
||||
For a completed `analyze_function` dossier, use `inspect_analysis_view` with
|
||||
`view: {kind: "native", facet: "value_flow_operations", offset: 0, limit: 32}`
|
||||
and the exact retained Evidence ID to read selected facts without reanalysis.
|
||||
The default MCP reply is already a summary of that dossier: its
|
||||
`normalized_result.parent_evidence_id` is the retained ID to page, and its
|
||||
null counts mark facets the provider did not record or declared unavailable.
|
||||
Native facets also include procedure, pseudocode, assembly, basic blocks,
|
||||
comments, callers/callees, references, unresolved calls, referenced strings and
|
||||
names, the native API record, and value-flow summaries. Pseudocode offsets and limits count UTF-16
|
||||
@@ -333,142 +96,22 @@ run, use `observe_native_calls` with explicit breakpoints and a bounded
|
||||
`duration_ms`/`max_events`. It launches a new process under LLDB, so the target
|
||||
runs and may change files or use the network. Hardened-runtime targets need the
|
||||
`get-task-allow` entitlement.
|
||||
Function breakpoints use exact LLDB runtime names: pass an `inspect_macho` export
|
||||
or import `runtime_name` (`main`), not its symbol-table `name` (`_main`). REA does
|
||||
not translate breakpoint names. Set `module` to the image
|
||||
file name when selecting that image only; otherwise loaded system images also
|
||||
match. Inspect retained locations and module paths before attributing a call.
|
||||
|
||||
Use `binary_session` with no arguments to check the open target, selected
|
||||
provider, and alignment. Its default summary `result.tool_availability` carries
|
||||
the complete tool inventory grouped by current status. Expand each group's
|
||||
`names` and inherit the inventory-level client requirements unless a group
|
||||
overrides them; availability, reasons, remediation, and modes remain inline.
|
||||
Provider candidates keep availability and target-support status, code, and
|
||||
reason. `omitted_fields` names what the summary leaves out; `{"detail":
|
||||
"complete"}` adds each unavailable candidate's `remediation`, provider
|
||||
diagnostics, candidate capability sets, and tool surface, `effects`, and
|
||||
`annotations`. Those arrays name true flags from fixed
|
||||
vocabularies, so an omitted known flag is false. `tools/list` remains the
|
||||
canonical source for full tool schemas when target or provider state changes.
|
||||
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, returned with complete
|
||||
detail, describe possible providers. Top-level session capabilities describe the current route, and
|
||||
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.
|
||||
|
||||
## Reuse caller-selected Ghidra analysis
|
||||
|
||||
When the caller supplies an expanded project, configure `REA_GHIDRA_PROJECT_PATH`
|
||||
(the `.gpr` marker) and optionally `REA_GHIDRA_PROGRAM_NAME` (one root program),
|
||||
then restart/reconnect before `open_binary`. CLI uses the same configuration.
|
||||
REA opens only a fingerprint-bound owned copy with `-process -noanalysis -readOnly`;
|
||||
original project files are not opened by the engine or saved. Database edits,
|
||||
import overrides and repair scripts are unavailable in this mode. Treat original
|
||||
analysis completeness/settings as unknown and prior edits as caller-project state.
|
||||
The program's recorded import digest must match the target; changed project content
|
||||
requires reopening for a fresh profile. See the
|
||||
[setup and preparation guide](https://github.com/morluto/rea/blob/main/docs/installation.md#reuse-an-existing-ghidra-project).
|
||||
|
||||
## Ghidra startup deadline recovery
|
||||
|
||||
`provider_timeout` during the first Ghidra query can cover import, auto-analysis,
|
||||
bridge startup, and readiness. The default 330,000 ms is configurable through
|
||||
`REA_GHIDRA_STARTUP_TIMEOUT_MS`; a smaller query still needs the same cold import.
|
||||
|
||||
When the error names `ghidra.startup_timeout_ms`, call
|
||||
`update_analysis_settings` with `action: "set"` and a larger value within the
|
||||
reported bounds. Resolve retained cleanup before the next startup attempt.
|
||||
No REA restart is needed; an already accepted startup keeps its original
|
||||
budget. A new attempt repeats import. Preserve useful Evidence before closing
|
||||
and reopening the target when remediation requires that lifecycle step.
|
||||
|
||||
For a per-function timeout, update `ghidra.decompile_timeout_seconds` and retry
|
||||
the same function on the existing Program. Explicit `null` removes that
|
||||
per-function deadline. Inspection is optional when the error supplies the
|
||||
setting and bounds. Runtime updates disappear when REA exits; environment
|
||||
changes only seed future REA processes.
|
||||
|
||||
The client's tool-call deadline must allow startup plus the requested query;
|
||||
server settings and progress notifications cannot override it. CLI analysis
|
||||
avoids the MCP client deadline, but retains the provider deadline. For example:
|
||||
|
||||
```sh
|
||||
REA_GHIDRA_STARTUP_TIMEOUT_MS=1800000 rea analyze /absolute/path/to/app.exe --provider ghidra --json
|
||||
```
|
||||
|
||||
Thirty minutes is an example budget, not a completion guarantee. Keep host CPU
|
||||
and memory controls in place. Disable specific expensive analyzers only when
|
||||
their missing coverage is acceptable and retain the resulting limitations.
|
||||
Exported C and embedded resources remain useful independent evidence, but a
|
||||
timeout does not establish complete analysis or absence of code. See the
|
||||
[deadline and client guide](https://github.com/morluto/rea/blob/main/docs/mcp-contracts.md#ghidra-first-query-deadlines-and-recovery).
|
||||
|
||||
## Bridge request ceiling
|
||||
|
||||
Hopper and Cutter share `REA_BRIDGE_MAX_REQUEST_BYTES`. Unset, both sides use
|
||||
16777216 bytes. The payload budget excludes the terminating LF. A decimal
|
||||
integer from 7464 to 536870887 raises or lowers that ceiling. The floor fits
|
||||
the largest bounded symbol-name command. A tight Cutter status still returns
|
||||
its session id and up to 64 completion receipts. Its path is included when it
|
||||
fits, and is unknown when it does not, so an older descriptor path is not
|
||||
paired with the new generation. The maximum is the encoded payload, one byte under Node's string ceiling so
|
||||
the framing LF still fits. Cutter measures, signs, and writes a fitting
|
||||
response in bounded chunks. The bridge searches only the newly read bytes
|
||||
for the framing LF. The client unescapes each socket chunk into one response
|
||||
string before parsing it, so this ceiling does not keep the paged wire frame
|
||||
and another concatenated copy. Invalid values are configuration errors and do not fall back to 16777216. Set the variable in the environment that starts REA, restart REA,
|
||||
and start Hopper or Cutter from that same environment. An out-of-band bridge
|
||||
without the setting stays at 16777216 bytes. The client reports that mismatch
|
||||
during discovery only after the hello proves a live authenticated bridge, and
|
||||
does not send a status request or the command. A stale descriptor is ignored.
|
||||
Discovery fails if any such live ceiling differs, even when another live
|
||||
session matches. A command response discarded for that ceiling stays an
|
||||
unknown execution with `output_truncated`. A hello
|
||||
that never sends its authenticated frame does not hold the accept loop.
|
||||
At most 8 connection threads run; a further accept is closed, and a failure
|
||||
to start a thread does not stop the listener. `completed_connection` is the
|
||||
longest prefix of connections that have terminated. Dense scalar replies are
|
||||
encoded in bounded batches.
|
||||
The 25000 ms Cutter request deadline and the 256-candidate discovery budget
|
||||
still have no runtime override.
|
||||
|
||||
## Selected native facts and call graphs
|
||||
|
||||
For a large `get_call_graph`, or repeated `procedure_address`,
|
||||
`procedure_callees`, `procedure_callers` and `resolve_containing_procedure`
|
||||
queries, select `detail: "summary"` when the first 64 recorded observations
|
||||
suffice. Read further `native-observation` pages from the returned parent Evidence
|
||||
ID. Graph message IDs index that page's exact-text dictionary; keep level,
|
||||
source index, failed nodes, and partial/unknown relationship coverage.
|
||||
Read referenced source context through `native-context` facets `overview`,
|
||||
`analysis-profile`, `limitations` and `result-limitations`. Reuse inspected
|
||||
context/profile values only for the same digest; until caveats are read,
|
||||
interpretation beyond the returned facts remains unknown. Complete Evidence
|
||||
remains retained and exportable. Small complete replies can cost fewer tokens
|
||||
than additional context/page calls; count the completed task and discovery.
|
||||
|
||||
## Static memory and original file bytes
|
||||
|
||||
For Ghidra, `read_bytes` reads initialized analysis memory, including loader
|
||||
fixups; it does not read a running process. Check `returned_bytes` and `complete`
|
||||
before decoding a table. An incomplete read is missing coverage, not a zero-filled
|
||||
tail. Use `list_segments` to inspect the selected block's initialization and address-space
|
||||
facts, and retain unavailable facts as unknown.
|
||||
|
||||
Use `address_to_file_offset` to anchor one observed address to original file
|
||||
bytes. A mapping for the first address does not establish a contiguous mapping
|
||||
for an entire table, and loader fixups can make analysis bytes differ from source
|
||||
bytes. Keep source ranges and analysis-memory observations separate.
|
||||
|
||||
A pointer table can refer to writable slots populated by a constructor rather
|
||||
than directly to immutable resources. An uninitialized block or an observed zero
|
||||
does not establish the slot's later runtime value. Follow `xrefs` and the relevant
|
||||
writer's assembly or pseudocode; distinguish recorded assignments from inferred
|
||||
execution order. Do not replace unreadable bytes with zeros or conclude that a
|
||||
resource is absent without the necessary initialization evidence.
|
||||
|
||||
## DOS MZ with Ghidra
|
||||
|
||||
For an admitted DOS MZ target, use `inspect_native_load_image` to check measured
|
||||
@@ -476,18 +119,12 @@ loaded bytes, file mappings, relocations and the entry against the immutable
|
||||
snapshot. Keep `mismatch` and `unsupported` explicit. Verified import covers the
|
||||
reported static image, not runtime DOS or PC-98 hardware behavior.
|
||||
|
||||
For a large load-image report, select `detail: "summary"` and query its
|
||||
`parent_evidence_id` with `inspect_analysis_view` kind `native-load-image`.
|
||||
Choose a collection such as `relocations` or `mappings`, select only needed
|
||||
fields, and page with offset/limit. Original indices preserve same-address
|
||||
alternatives. Null collections remain unrecorded or inapplicable; paging does
|
||||
not upgrade unsupported verification. CLI requires `--evidence_output` for
|
||||
summary delivery so complete source Evidence remains accessible.
|
||||
|
||||
For Ghidra decompilation, `procedure_pseudo_code` also accepts `procedure: "file-offset:0x..."`; CLI `rea decompile <path> --file-offset=0x... --provider ghidra` resolves the source byte offset internally. An unresolved 0x-prefixed numeric procedure selector also falls back to a unique file mapping. Existing symbols and analysis addresses win; multiple mappings fail as ambiguous. Preserve the returned file-offset and address locations and raw selection observations. ELF virtual addresses are distinct from file offsets; never assume an arbitrary offset plus image base is an analysis address.
|
||||
|
||||
Complete function body ranges use inclusive ends and can contain gaps. Do not
|
||||
treat the enclosing span as code.
|
||||
Use `read_bytes` for initialized analysis memory and `address_to_file_offset` to
|
||||
anchor one observed address to original file bytes. Loader fixups can change a
|
||||
word; a source offset does not imply byte equality. Complete function body ranges
|
||||
use inclusive ends and can contain gaps. Do not treat the enclosing span as code.
|
||||
For packed targets, retain the original and separately derived artifact identities;
|
||||
decompiling an unpacking stub does not recover the unpacked program.
|
||||
|
||||
@@ -520,19 +157,6 @@ repeat parsing. Returned-row exhaustion
|
||||
does not prove complete metadata or absence under partial/unavailable coverage;
|
||||
preserve build-local tokens and their artifact/MVID scope.
|
||||
|
||||
`inspect_managed_members` always returns the assembly-wide `member_refs`, even
|
||||
for one selected type. For questions about a selected type, select
|
||||
`detail: "summary"` with `type_names`; it returns the selection (with unmatched
|
||||
names), coverage and per-collection counts. Page rows with
|
||||
`view: {kind: "managed-members", collection: "call_edges", offset: 0, limit: 64}`
|
||||
over `types`, `exported_types`, `fields`, `methods`, `member_refs`, `call_edges`,
|
||||
`field_accesses` or `user_strings`.
|
||||
Use exact `names` and `declaring_types` filters before paging supported
|
||||
collections. For a compact method lookup, request `fields: ["name",
|
||||
"declaring_type", "signature"]`; omit `body` to avoid anchors. Identity tokens,
|
||||
original `source_index`, module/MVID and source coverage remain. Check
|
||||
`unresolved_filter_rows` before interpreting an empty match as absence.
|
||||
|
||||
### .NET NativeAOT
|
||||
|
||||
NativeAOT binaries contain native machine code, so ordinary CIL decompilers
|
||||
@@ -551,9 +175,7 @@ additional evidence only when the provider accepts and matches them.
|
||||
REA has a built-in read-only snapshot parser for x64 PE targets with .NET 8 RTR
|
||||
9.1 metadata. An optional headless Ghidra adapter adds database annotations and
|
||||
broader metadata recovery on Linux; it requires an explicitly configured
|
||||
NativeAOT JAR. These are separate routes. The built-in parser also runs on
|
||||
Windows Ghidra P0; read frozen literals from its report, because
|
||||
`search_strings` does not include them. See the
|
||||
NativeAOT JAR. These are separate routes. See the
|
||||
[NativeAOT guide](https://github.com/morluto/rea/blob/main/docs/ghidra-nativeaot.md)
|
||||
for setup, supported layouts, and host restrictions. Preserve binary identity
|
||||
and distinguish derived metadata from Ghidra database observations. Recovered
|
||||
@@ -582,72 +204,37 @@ every executable in the bundle. It also lists missing or weak loads and earlier
|
||||
`architecture`. System paths stay undetermined because the dyld shared cache
|
||||
provides them.
|
||||
|
||||
Use `extract_artifact` when materialized files from a supported format are needed.
|
||||
It extracts the active target and accepts optional `integrity_policy`; see the [MCP contracts](https://github.com/morluto/rea/blob/main/docs/mcp-contracts.md).
|
||||
Use `extract_artifact` when materialized files are needed. It extracts the active
|
||||
target and accepts optional `integrity_policy`; see the [MCP contracts](https://github.com/morluto/rea/blob/main/docs/mcp-contracts.md).
|
||||
It materializes all regular files into a fresh temporary directory chosen by
|
||||
REA. Symlinks and encrypted entries are inventory facts, not extractable files.
|
||||
The result reports the materialized directory; callers do not choose the
|
||||
destination. No separate permission grant is needed.
|
||||
|
||||
For classic iNES cartridges, `open_binary` recognizes the byte signature without
|
||||
an executable architecture. `inspect_artifact` retains header facts and each
|
||||
PRG/CHR file unit's offset, length and SHA-256; its nested inventory's
|
||||
`ines.reconstruction` verifies the actual ordered region bytes against the
|
||||
whole-file digest. Extraction includes the original header, optional trainer
|
||||
and uninterpreted trailing bytes. These file units are not mapper/CPU bank
|
||||
maps. NES 2.0, UNIF, dirty legacy headers and truncated payloads are explicitly
|
||||
refused. No emulator or 6502 analysis is performed. See the
|
||||
[iNES guide](https://github.com/morluto/rea/blob/main/docs/ines-containers.md).
|
||||
|
||||
Native DMG traversal automatically uses a read-only mount on supported macOS
|
||||
hosts, with an owned temporary mount directory and cleanup. It needs no approval
|
||||
flag. Unsupported hosts or mount failures remain explicit limitations; do not
|
||||
claim child inventory when only root identity is available. DMG extraction is
|
||||
unavailable through `extract_artifact`. To access DMG files on macOS, create a
|
||||
read-only mount with your own tools, select files from that mount for analysis,
|
||||
and detach the mount you created when finished. REA's inspection mount is
|
||||
temporary, not a retained export of the image's contents.
|
||||
claim child inventory when only root identity is available. Extraction is a
|
||||
separate filesystem-writing operation.
|
||||
|
||||
For headerless DOS COM, explicitly open with `format: "dos-com"` (CLI
|
||||
`--target-format dos-com`). Inspect the load image before trusting function
|
||||
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).
|
||||
|
||||
Use `inspect_byte_entropy` to triage high- and low-entropy ranges before extraction.
|
||||
|
||||
For other headerless images (embedded firmware without ELF/Mach-O/PE/MZ
|
||||
headers), open with `format: "raw-binary"`, `architecture` (`x86`, `x86_64`,
|
||||
`arm`, `arm64`, `6502`, or `m68k`), and `entry_point` (`0x` hex). File offset 0 is mapped at
|
||||
`arm`, `arm64`, or `6502`), and `entry_point` (`0x` hex). File offset 0 is mapped at
|
||||
that address. Hopper and IDA refuse this interpretation; Ghidra uses
|
||||
`BinaryLoader` with a generic language: big-endian for `m68k`, little-endian
|
||||
otherwise. Big-endian ARM, Thumb-only, and Cortex-M variants are unsupported.
|
||||
`6502` selects NMOS 6502; the entry and complete file mapping must fit within
|
||||
`0x0000..0xffff`. 65C02, 65816, cartridge containers, bank switching, and mapper
|
||||
semantics are unsupported. `m68k` requires an even entry and decodes the 68040
|
||||
superset of the 68000 family; Amiga/Atari containers and vector-table entry
|
||||
discovery are unsupported. Windows Ghidra P0 admits raw images only for `6502`. See [raw binary guide](https://github.com/morluto/rea/blob/main/docs/raw-binary.md).
|
||||
|
||||
Intel HEX, S-record, UF2 and DfuSe files are containers, not raw images:
|
||||
`inspect_artifact` lists address regions (UF2 per familyID, DfuSe per element
|
||||
with its alternate setting), and `extract_artifact` writes each region. Open an
|
||||
extracted region as `raw-binary` with its start address as `entry_point`. UF2
|
||||
numbering gaps leave coverage unknown; a DfuSe suffix CRC mismatch follows
|
||||
`integrity_policy`. See [UF2 and DfuSe](https://github.com/morluto/rea/blob/main/docs/uf2-dfuse.md).
|
||||
`BinaryLoader` with a little-endian generic language. Big-endian, Thumb-only,
|
||||
and Cortex-M variants are unsupported. `6502` selects NMOS 6502; the entry and
|
||||
complete file mapping must fit within `0x0000..0xffff`. 65C02, 65816, cartridge
|
||||
containers, bank switching, and mapper semantics are unsupported. Windows
|
||||
Ghidra P0 admits raw images only for `6502`. See [raw binary guide](https://github.com/morluto/rea/blob/main/docs/raw-binary.md).
|
||||
|
||||
### Function annotations in Ghidra
|
||||
|
||||
Fresh-import sessions admit annotations; existing-project reuse is read-only.
|
||||
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. Ghidra rejects batch addresses that request the same name or a name another selected address holds; `raw_result.failures` gives each failure's reason.
|
||||
|
||||
For `compare_functions`, obtain `analyze_function` Evidence before and after the
|
||||
edit while the MCP session is open; annotation Evidence is not a comparison
|
||||
input. Export the needed Evidence before close if comparison will happen later.
|
||||
Authenticated same-artifact coordinates preserve renamed entries as
|
||||
`matched / same-entry`, with the canonical `entry_address` inline; name and
|
||||
exact-text changes remain separate dimensions. This requires compatible
|
||||
architecture, provider-owned mapping and selected-image commitments. Equal
|
||||
numeric addresses alone cannot establish correspondence; inspect retained
|
||||
source Evidence and coverage when the match remains ambiguous.
|
||||
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.
|
||||
|
||||
## Offline PE resource inventory
|
||||
|
||||
@@ -660,121 +247,3 @@ resolution is explicit; missing references and size mismatch remain visible.
|
||||
Other payloads are opaque, and this operation does not decode images or simulate
|
||||
Windows resource loading. Consult the budgets and supported tree profile in
|
||||
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/binary-diagnostics.md).
|
||||
|
||||
## Offline PE and ELF export tables
|
||||
|
||||
Use `inspect_native_exports` with an absolute `path`, or
|
||||
`rea inspect-native-exports ./libexample.so --json`, to read a PE export
|
||||
directory or ELF dynamic symbol table on any host without opening a native
|
||||
target. Ordinal-only, forwarded and hidden-version exports stay distinct;
|
||||
partial or unsupported coverage means the inventory is incomplete, not that
|
||||
exports are absent. Use `inspect_macho` for Mach-O. See
|
||||
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/binary-diagnostics.md).
|
||||
|
||||
For exact core-module import/export differences, use `compare_wasm_interfaces`
|
||||
with two complete inspection Evidence records (or current-session retained
|
||||
references) and explicit scope. Preserve unknown types/duplicate names; static
|
||||
interface equality is not runtime compatibility. CLI accepts complete inline
|
||||
Evidence in `rea compare-wasm-interfaces input.json --json`, or exact references
|
||||
with `--evidence-bundle /absolute/path/evidence.json`. Use the complete parent
|
||||
ID when the inspection returned a summary.
|
||||
|
||||
## Godot PCK
|
||||
|
||||
For a standalone PCK, `open_binary` then `inspect_artifact` with
|
||||
`detail: "summary"` retains the complete static inventory. Use the returned
|
||||
`parent_evidence_id` with `inspect_analysis_view` inventory occurrences filtered
|
||||
by exact `logical_paths` or `occurrence_ids`, and join `artifact_id` to nodes
|
||||
filtered by `artifact_ids`. Select only needed fields; identity links remain.
|
||||
Prefer `inspect-artifact --detail summary --evidence_output PATH` for CLI tasks;
|
||||
the same CLI discovers embedded packs via their EOF trailer without opening a
|
||||
native analysis provider. Normal native opening remains unchanged.
|
||||
|
||||
Distinguish declared MD5 from observed SHA-256 and graph/page completeness from
|
||||
`source_coverage`. Encrypted directories and sparse bundles have unknown entry
|
||||
coverage; encrypted members have unknown content identity. An empty filtered
|
||||
page cannot prove absence in unknown coverage. Retained views are historical,
|
||||
not fresh filesystem checks. No decryption, Godot resource decoding, GDScript
|
||||
decompilation or target execution is provided. See
|
||||
[Godot PCK](https://github.com/morluto/rea/blob/main/docs/godot-pck.md).
|
||||
|
||||
UnityFS AssetBundle containers (`.bundle`, `.ab`, YooAsset packages and other
|
||||
byte-identified files) list `CAB-*` SerializedFiles and `.resS` members with
|
||||
header and node facts; none/LZ4/LZ4HC member blocks stream on demand. Bundles
|
||||
declaring asset-bundle encryption keep header facts with unknown directory
|
||||
coverage; LZMA member bytes retain named occurrences with unavailable hashes.
|
||||
Virtual member offsets are not physical carrier locations. Format 6–8 readers
|
||||
cache one decoded block and support retained filtered/paged views. No
|
||||
decryption or SerializedFile object decoding is provided. See
|
||||
[UnityFS](https://github.com/morluto/rea/blob/main/docs/unityfs.md).
|
||||
|
||||
U-Boot legacy uImages use the same inspection/extraction workflows. Prefer a
|
||||
retained summary and selected inventory views for header facts or component
|
||||
IDs. Header addresses/CPU/OS are declarations; payloads remain as stored.
|
||||
Multi-file components retain their aligned physical ranges; the full payload
|
||||
is also materialized. Unknown name text retains its original 32 bytes. See
|
||||
[U-Boot uImage](https://github.com/morluto/rea/blob/main/docs/uimage.md).
|
||||
|
||||
## ESP images
|
||||
|
||||
For an ESP32-family app or bootloader image, `open_binary` then
|
||||
`inspect_artifact` with `detail: "summary"`. `root_format_metadata` holds the
|
||||
observed `header` and `descriptor` (its `kind` is the app or bootloader role)
|
||||
and the derived `chip`, `checksum`, `appended_sha256` and
|
||||
`undescribed_trailing` ranges. Segment rows (`segment-NN.bin`) carry
|
||||
`load_addr`, `classification` (`mapped`, `loaded`, `not-loaded` or `unknown`,
|
||||
per ESP-IDF `should_map`/`should_load`; RAM ranges are not checked) and
|
||||
`source_location`; select them with `inspect_analysis_view` and
|
||||
`logical_paths`. Signature blocks are `unverified`; `public_key_sha256`
|
||||
matches eFuse key digests.
|
||||
|
||||
Unknown `source_coverage` means truncation or undescribed trailing bytes, such
|
||||
as the flash segments of a `--ram-only-header` image; do not infer absence
|
||||
from it. Erased flash is described. Integrity mismatches are facts, not
|
||||
refusals. ESP8266 images are refused, and flash-encrypted images are not
|
||||
recognized. Segments are not disassembled, and RISC-V or Xtensa segments
|
||||
cannot be opened as raw-binary. See
|
||||
[ESP images](https://github.com/morluto/rea/blob/main/docs/esp-image.md).
|
||||
|
||||
## ESP flash images
|
||||
|
||||
A whole-flash dump opens as `esp-flash-image`. `root_format_metadata` holds
|
||||
`partition_table` (its `method`: `default`, `caller` or a derived
|
||||
`scan`), `bootloader` and `apps` ESP image facts, the `otadata` records,
|
||||
`partitions_outside_dump` and `boot_selection`. Every table has a
|
||||
`boot_selection`: its `basis` names the bootloader's branch (`no-otadata`
|
||||
without otadata, `none` when the bootloader rejects the table, `unknown`
|
||||
when a record is missing), and it lists its `assumptions`;
|
||||
`running_partition` is always `unknown`. Each image names its entry
|
||||
`path`. With `detail: "summary"` the root kind is `esp-flash-image-summary`
|
||||
and each image is a compact `summary` (chip, entry point, segment count,
|
||||
classified load ranges, signature block count, descriptor identity,
|
||||
integrity); complete detail keeps every segment. Partition rows
|
||||
(`partitions/<label>.bin`) carry the declared `type`, `subtype`, names,
|
||||
`flash_address`, `size` and flags; `encrypted` is a storage flag, not
|
||||
proof of ciphertext. `written_offset`/`written_end` bound the bytes that
|
||||
are not erased: an app slot whose facts say `erased` can still hold an older
|
||||
image, and a limitation says so. For a dump read from another flash address, call
|
||||
`open_binary` with `format: "esp-flash-image"` and `flash_offset`; an
|
||||
`esp-image` whose `flash_dump` is set names that offset in a next probe; a
|
||||
relocated table can be named with `partition_table_offset`. No table found
|
||||
means unknown, often flash encryption. Extraction writes `nvs_keys` as
|
||||
ordinary data. See
|
||||
[ESP flash images](https://github.com/morluto/rea/blob/main/docs/esp-flash-image.md).
|
||||
|
||||
## Bun standalone executables
|
||||
|
||||
A `bun build --compile` executable stores its module graph in Mach-O
|
||||
`__BUN,__bun`, a PE or ELF `.bun` section, or an appended trailer.
|
||||
`open_binary` then `inspect_artifact` reports root format `bun-standalone` with
|
||||
the carrier, structural layout, module count, entry point, raw flags and
|
||||
compile exec-argv; each module keeps its `/$bunfs/...` source name, loader,
|
||||
encoding, sizes and SHA-256, with sourcemap, bytecode and module-info regions as
|
||||
separate children. Use summary root metadata `entry_point_logical_path` as the
|
||||
exact inventory `logical_paths` selector, then read the selected content node by
|
||||
its artifact ID; the reader owns virtual-name normalization and indexed fallback. `extract-artifact` writes stored contents through the safe
|
||||
output tree; route extracted JavaScript to `analyze_javascript_application`.
|
||||
Layout labels describe record shapes, not a detected compiler version; unknown
|
||||
or ambiguous layouts are unsupported. Bytecode is retained, not decoded, and the
|
||||
executable is never run. See
|
||||
[Bun standalone executables](https://github.com/morluto/rea/blob/main/docs/bun-standalone.md).
|
||||
@@ -1,52 +1,4 @@
|
||||
# Runtime observation
|
||||
|
||||
## Native runtime questions
|
||||
|
||||
Start from the requested observation. State the selected target, input, expected
|
||||
result, failure signal and stopping condition before a run. Reuse static Evidence
|
||||
to choose a function or hypothesis; `trace_native_values` follows recovered
|
||||
static value dependencies and does not measure runtime values. Prefer static
|
||||
inspection or existing capture comparison when it already answers the question.
|
||||
|
||||
| Question | Existing workflow | Host and effects |
|
||||
| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Which selected native entry is reached, with which integer argument registers? | `observe_native_calls` on the active Mach-O, with exact function/Objective-C breakpoints and a duration/event bound | macOS with LLDB and required debugger access. Launches a new owned process, stops at entries, records and continues; target execution may write files, show UI or use the network. It never attaches to an existing process. Inspect termination and partial coverage after the run. |
|
||||
| What does caller-authored instrumentation observe? | `list_frida_devices` / `list_frida_processes` when discovery is needed; `start_frida_session`, `load_frida_script`, then status/unload/close | Requires compatible installed Frida bindings and selected device access; the real REA verifier covers Linux. Attach selects an existing PID; spawn selects a program and starts it paused until `resume_frida_session`. Scripts execute in the target and can change memory, files, network activity and behavior. This is not passive inspection. |
|
||||
| Which debugger state or controlled execution explains the behavior? | `start_gdb_session` and `gdb_console`, or `start_rizin_debug_session` and `rizin_debug_command` | Persistent debugger workflows are Linux-only. GDB starts a debugger; console commands choose/load/attach/run targets. Rizin debug start launches the selected target and requires a durable `inferior_tty`. Both consoles are unrestricted and can change target memory/registers, run shell commands, write files and use the network. |
|
||||
| How do terminal output, process lifetime and selected files differ between runs? | `capture_process_scenario`, then `compare_process_captures` with compatible recorded inputs | New captures require Linux or macOS and a working native PTY backend; macOS also needs `xcrun`/Swift for ownership inspection. Capture launches an owned command with ordinary user permissions. Observation paths select snapshots, not a filesystem sandbox. Comparing retained captures does not execute targets and is also available on Windows. |
|
||||
| What does an existing desktop window show, or what changes after a declared UI action? | `observe_native_ui`; use `capture_native_ui_scenario` only for requested actions | macOS 14+ with required Screen Recording/Accessibility access. Select an existing PID/window. Observation does not launch or own the app; scenario actions can change its data or trigger network activity, and leave its state as-is. |
|
||||
|
||||
Close each persistent session with its matching tool. `close_frida_session`
|
||||
unloads scripts and detaches; it does not kill the target. A still-paused spawn
|
||||
is resumed only after instrumentation cleanup is confirmed. GDB and Rizin close
|
||||
can detach a live inferior and let it continue only after verifying its durable
|
||||
output terminal. When disposition or cleanup is unresolved, preserve the owned
|
||||
session and reported recovery; do not assume that closing the debugger terminated
|
||||
the target. Process capture and LLDB call observation instead own their launched
|
||||
processes and report their termination/cleanup outcome. Cancellation is not proof
|
||||
that cleanup completed.
|
||||
|
||||
Unrestricted GDB/Rizin commands and relevant start/close operations retire an
|
||||
active binary-analysis session in the same MCP server. Close an active Ghidra
|
||||
session explicitly first; after target effects settle, reopen the artifact before
|
||||
new static analysis. A Cutter bridge command is likewise unrestricted and may
|
||||
affect its GUI/target; it is not a passive native observation substitute.
|
||||
|
||||
To test a static hypothesis across two builds, retain compatible static
|
||||
comparison Evidence and `compare_process_captures` Evidence. Supply explicit
|
||||
finding/dimension mappings to `correlate_static_and_runtime`. It does not ingest
|
||||
arbitrary Frida, LLDB or debugger traces, auto-match names, or prove causality
|
||||
from cochange. Preserve those traces as their own Evidence and leave unobserved
|
||||
behavior unknown. Record truncation, unmatched breakpoints, changed target
|
||||
identity and cleanup uncertainty as limitations, rather than treating no retained
|
||||
event as proof that the behavior never occurs.
|
||||
|
||||
Detailed contracts and prerequisites: [Frida](https://github.com/morluto/rea/blob/main/docs/frida-instrumentation.md),
|
||||
[GDB/Rizin/Cutter](https://github.com/morluto/rea/blob/main/docs/reverse-engineering-tools.md),
|
||||
[process capture](https://github.com/morluto/rea/blob/main/docs/process-capture.md),
|
||||
and [native calls/UI](https://github.com/morluto/rea/blob/main/docs/native-investigation.md#native-desktop-observation).
|
||||
|
||||
## Browser and Electron
|
||||
# Passive browser and Electron observation
|
||||
|
||||
Supply the existing browser or Electron instance's literal-port loopback CDP
|
||||
endpoint in the request. No separate permission configuration or per-call
|
||||
@@ -55,16 +7,13 @@ approval flag is needed. Browser discovery lists eligible HTTP(S) pages;
|
||||
selected page's current origin. Electron discovery lists eligible local
|
||||
`file://` targets without a configured file-root allowlist.
|
||||
|
||||
Start passive browser work with `list_browser_targets`; start Electron runtime work with
|
||||
`list_electron_targets`. Before an electron-main Inspector attempt, check
|
||||
packaged fuses with `inspect_electron_fuses`. Inspect the target selected for
|
||||
the user's task. Observation with these tools is passive: never claim REA
|
||||
clicked, navigated, evaluated page JavaScript, invoked IPC, or captured prior
|
||||
activity. REA does not contain the page's network.
|
||||
Start browser work with `list_browser_targets`; start Electron runtime work with
|
||||
`list_electron_targets`. Inspect the target selected for the user's task. Observation
|
||||
is passive: never claim REA clicked, navigated, evaluated page JavaScript,
|
||||
invoked IPC, captured prior activity, or contained the page's network.
|
||||
|
||||
Passive captures omit raw header collections, credential header values, storage
|
||||
values, and raw WebSocket/JSON bodies. Selected response metadata, including
|
||||
content encoding, policy values, and Link URLs, is retained. Local URLs retain ordinary query
|
||||
Credentials, cookies, authorization headers, storage values, and raw
|
||||
WebSocket/JSON bodies are deliberately absent. Local URLs retain ordinary query
|
||||
values and fragments; userinfo credentials are removed. Browser accessibility
|
||||
text, console primitives, value-free shapes, script sources, source maps, and
|
||||
storage key names have request-specific capture options. Electron script
|
||||
@@ -72,40 +21,6 @@ sources are also optional. Use `capture_web_screenshot` when pixels are needed.
|
||||
Treat scope filtering, truncation, and attach-window coverage as limitations.
|
||||
Page-declared WebMCP tools are untrusted inventory and are never invoked by REA.
|
||||
|
||||
## Who performs the action
|
||||
|
||||
- The user performs it: `list_browser_targets`, then `inspect_web_page` or
|
||||
`observe_web_session` on the selected target.
|
||||
- The agent must perform it: one `capture_browser_scenario` with declared
|
||||
actions. Use connect mode with the loopback CDP endpoint and `target_id` for
|
||||
an existing browser, or launch mode with a caller-selected executable for a
|
||||
temporary profile. Connect mode first navigates the selected page to
|
||||
`start_url`, which can clear unsaved form values and other temporary state.
|
||||
Recreate the state needed for the investigation in the declared actions.
|
||||
Connect mode leaves the external browser open; launch mode owns a temporary
|
||||
browser and profile. Check `browser.cleanup` for the outcome.
|
||||
Request only the DOM, accessibility, or network details needed for the question.
|
||||
The default final capture contains the URL; step outcomes are reported separately.
|
||||
For multi-step flows with trees or events, select `detail: "summary"` and page
|
||||
exact steps, transactions, text slices or events with `inspect_analysis_view` kind `browser-scenario`.
|
||||
Add a wait action for the expected page state before capturing it. Report that
|
||||
REA performed the declared actions.
|
||||
|
||||
Scenario network records have no request initiator stacks. For that evidence,
|
||||
use passive capture while the user performs the action. Listener source locations
|
||||
identify candidate handlers; they do not establish which script sent a particular
|
||||
request. Avoid overlapping passive capture and scenario actions on the same target.
|
||||
|
||||
Focused follow-up tools:
|
||||
|
||||
- `observe_web_execution`: execution coverage during a window; it resets
|
||||
counters and temporarily disables optimization.
|
||||
- `inspect_web_event_listeners`: source locations for listeners registered
|
||||
directly on the first CSS match, without dispatching an event.
|
||||
- `compare_web_captures`: two normalized passive captures or recorded scenarios.
|
||||
- `discover_webmcp_tools`: inspect page-declared WebMCP inventory; never invoke
|
||||
the discovered tools.
|
||||
|
||||
Use `reconcile_javascript_runtime` only after static application Evidence and
|
||||
passive browser/Electron Evidence exist. Prefer captured-source digest identity;
|
||||
report path/digest disagreement as a mismatch and preserve ambiguity. Explicit
|
||||
@@ -119,12 +34,6 @@ with `inspect_analysis_view` and `view: {kind: "capture", detail: "preview", off
|
||||
Select complete record pages only for needed headers, metadata or payloads; views
|
||||
read the historical parent without repeating capture decoding.
|
||||
|
||||
For selected protobuf/gRPC bodies, keep raw bytes as the source and use
|
||||
`inspect_analysis_view` kind `protobuf`, collection `bodies`, `frames` or
|
||||
`fields`. Batch exact record ordinals and field numbers/IDs, and select
|
||||
`schema_value` or `text` only when needed. Supply a local descriptor set and
|
||||
message type for schema interpretation; compressed payloads remain unknown.
|
||||
|
||||
Inspect native `.saz` snapshots with `inspect_web_network_capture` and
|
||||
`format: "saz"`; encrypted archives are unsupported. For live
|
||||
traffic analysis, connect Everywhere's separate MCP server and use its
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
||||
"name": "rea",
|
||||
"description": "Reverse engineer anything with agents, from app behavior down to native binaries.",
|
||||
"owner": {
|
||||
"name": "morluto",
|
||||
"url": "https://github.com/morluto"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "rea",
|
||||
"description": "Reverse engineer anything with agents, from app behavior down to native binaries.",
|
||||
"source": {
|
||||
"source": "npm",
|
||||
"package": "rea-agents",
|
||||
"version": "6.8.0"
|
||||
},
|
||||
"category": "development"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "rea",
|
||||
"version": "6.8.0",
|
||||
"description": "Reverse engineer anything with agents, from app behavior down to native binaries.",
|
||||
"author": {
|
||||
"name": "morluto",
|
||||
"url": "https://github.com/morluto"
|
||||
},
|
||||
"homepage": "https://rea.tools/",
|
||||
"repository": "https://github.com/morluto/rea",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"reverse-engineering",
|
||||
"binary-analysis",
|
||||
"decompiler",
|
||||
"mcp-server"
|
||||
],
|
||||
"skills": "./skills",
|
||||
"mcpServers": {
|
||||
"rea": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"${CLAUDE_PLUGIN_ROOT}/scripts/plugin-mcp-launcher.mjs",
|
||||
"6.8.0"
|
||||
]
|
||||
}
|
||||
},
|
||||
"icon": "./assets/rea-plugin.png"
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"rea": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"./scripts/plugin-mcp-launcher.mjs",
|
||||
"6.8.0"
|
||||
],
|
||||
"cwd": ".",
|
||||
"startup_timeout_sec": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"name": "rea",
|
||||
"version": "6.8.0",
|
||||
"description": "Reverse engineer anything with agents, from app behavior down to native binaries.",
|
||||
"author": {
|
||||
"name": "morluto",
|
||||
"url": "https://github.com/morluto"
|
||||
},
|
||||
"homepage": "https://rea.tools/",
|
||||
"repository": "https://github.com/morluto/rea",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"reverse-engineering",
|
||||
"binary-analysis",
|
||||
"decompiler",
|
||||
"mcp-server"
|
||||
],
|
||||
"skills": "./skills",
|
||||
"mcpServers": "./.codex-plugin/mcp.json",
|
||||
"interface": {
|
||||
"displayName": "REA",
|
||||
"shortDescription": "Reverse engineer anything",
|
||||
"longDescription": "Reverse engineer anything with agents, from app behavior down to native binaries.",
|
||||
"developerName": "morluto",
|
||||
"category": "Developer Tools",
|
||||
"capabilities": [
|
||||
"Interactive",
|
||||
"Read",
|
||||
"Write"
|
||||
],
|
||||
"websiteURL": "https://rea.tools/",
|
||||
"defaultPrompt": [
|
||||
"Use REA to explain how this binary implements a feature.",
|
||||
"Use REA to compare two versions of this application.",
|
||||
"Use REA to recover the sources from this JavaScript bundle."
|
||||
],
|
||||
"composerIcon": "./assets/rea-plugin.png",
|
||||
"logo": "./assets/rea-plugin.png"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
# Review changes to security-sensitive automation and dependency inputs.
|
||||
/.github/CODEOWNERS @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
|
||||
/.github/dependabot.yml @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
|
||||
/.github/workflows/ @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
|
||||
/.github/actions/ @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
|
||||
/package.json @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
|
||||
/package-lock.json @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
|
||||
/scripts/verify-tools/skills/package.json @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
|
||||
/scripts/verify-tools/skills/package-lock.json @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
|
||||
/scripts/fixtures/http3-lab-requirements.txt @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
|
||||
/website/requirements.txt @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
|
||||
/third_party/ @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
|
||||
@@ -1,5 +1,5 @@
|
||||
name: Bug report
|
||||
description: Report a problem in REA with observations or source evidence
|
||||
description: Report a reproducible problem in REA
|
||||
title: "[Bug] "
|
||||
labels:
|
||||
- bug
|
||||
@@ -8,12 +8,8 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Report one primary behavior per issue. Keep answers concise and grounded
|
||||
in available evidence. Distinguish observed behavior, source-derived
|
||||
reasoning, and suspected causes; do not claim a reproduction you did not run.
|
||||
If a narrative answer is not known, write `NOT_ENOUGH_INFO` and describe
|
||||
what is missing. Intermittent failures and clearly labeled source-only
|
||||
reports are welcome. Do not include credentials or sensitive target data.
|
||||
Please report one primary behavior per issue. Keep observed behavior
|
||||
separate from suspected causes, and remove credentials and private paths.
|
||||
|
||||
- type: checkboxes
|
||||
id: before-submitting
|
||||
@@ -46,7 +42,7 @@ body:
|
||||
id: observed-behavior
|
||||
attributes:
|
||||
label: Observed behavior
|
||||
description: Describe the actual result, including the exact command or MCP request, relevant provider/target, and supporting evidence below. Label source-only findings instead of claiming execution.
|
||||
description: What happened? Include the command, MCP tool, provider, target type, and conditions that matter.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -54,7 +50,7 @@ body:
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction
|
||||
description: Give the shortest steps you actually ran, with the relevant input or configuration. State how often they reproduced the problem when known. If reproduction is unavailable, say so and provide the observations or source reference you have.
|
||||
description: Give the smallest realistic command, configuration, input, or event sequence that demonstrates the problem.
|
||||
placeholder: |
|
||||
1. Configure or start ...
|
||||
2. Run ...
|
||||
@@ -66,7 +62,7 @@ body:
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: State the expected result, contract, or recovery action. Cite documentation, source, or a known-good version when available; mark an unverified expectation or unknown reference clearly.
|
||||
description: What result, contract, invariant, recovery action, or state transition should have occurred?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -74,7 +70,7 @@ body:
|
||||
id: impact-scope
|
||||
attributes:
|
||||
label: Impact and scope
|
||||
description: Describe observed impact and frequency, including effects on evidence integrity, process isolation, compatibility, or recovery when known. Leave untested scope and severity unknown rather than guessing.
|
||||
description: What is affected, what is not affected, and does this affect evidence integrity, process isolation, compatibility, or recovery?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -83,7 +79,7 @@ body:
|
||||
attributes:
|
||||
label: REA version
|
||||
description: Use `rea --version` or provide the source revision.
|
||||
placeholder: Exact rea --version output or source commit SHA
|
||||
placeholder: 3.1.0 or git revision
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -120,7 +116,7 @@ body:
|
||||
id: failure-stage
|
||||
attributes:
|
||||
label: Earliest failure stage
|
||||
description: Select where the incorrect behavior first becomes observable. Choose Unknown if this has not been established.
|
||||
description: Select where the incorrect behavior first becomes observable.
|
||||
options:
|
||||
- Request parsing or validation
|
||||
- Setup or provider admission
|
||||
@@ -137,14 +133,8 @@ body:
|
||||
id: relevant-evidence
|
||||
attributes:
|
||||
label: Relevant output or evidence
|
||||
description: Include the smallest useful output, MCP result, error, screenshot, artifact, or exact source reference. Say how it was obtained and which claim it supports. If unavailable, write NOT_ENOUGH_INFO and explain what is missing. Redact credentials and sensitive target data.
|
||||
placeholder: |
|
||||
Command or MCP request actually run:
|
||||
Relevant result, error, or artifact:
|
||||
For source-only findings, link the file/revision and relevant code:
|
||||
Missing evidence or remaining uncertainty:
|
||||
validations:
|
||||
required: true
|
||||
description: Paste the smallest useful CLI output, MCP result, error, trace, or evidence excerpt. Redact sensitive values.
|
||||
render: text
|
||||
|
||||
- type: textarea
|
||||
id: typed-outcome
|
||||
@@ -156,10 +146,10 @@ body:
|
||||
id: analysis
|
||||
attributes:
|
||||
label: Analysis or suspected cause
|
||||
description: Optional. Link supporting source and distinguish source-derived reasoning from a hypothesis. A root-cause analysis or proposed fix is not required.
|
||||
description: Optional. Clearly mark source-derived reasoning or a hypothesis; do not present an unverified cause as fact.
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add reduced fixtures, screenshots, or related issues. For observed regressions, include the last known good and first known bad versions when available. Label untested checks as proposals.
|
||||
description: Add links, reduced fixtures, screenshots, or a proposed regression test.
|
||||
@@ -41,7 +41,7 @@ body:
|
||||
id: context-evidence
|
||||
attributes:
|
||||
label: Context and evidence
|
||||
description: Explain the problem or motivation and link relevant evidence when available. Distinguish observed behavior, source-derived reasoning, proposed behavior, and unknowns. A question without measurements or a finished design is welcome.
|
||||
description: Explain the problem or motivation. If you have evidence, distinguish observed behavior from ideas or guesses.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -5,13 +5,6 @@ labels:
|
||||
- documentation
|
||||
- triage
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Report one documentation problem with the exact location and the smallest
|
||||
useful supporting passage, example, or result. Keep answers concise.
|
||||
If something is unknown, write `NOT_ENOUGH_INFO` and explain what is missing.
|
||||
|
||||
- type: checkboxes
|
||||
id: before-submitting
|
||||
attributes:
|
||||
@@ -35,7 +28,7 @@ body:
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem
|
||||
description: Quote or describe the current content, explain what is unclear, missing, outdated, or incorrect, and say how to observe the problem. State who is affected when known.
|
||||
description: What is unclear, missing, outdated, or incorrect, and who is affected?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -43,7 +36,7 @@ body:
|
||||
id: expected-content
|
||||
attributes:
|
||||
label: Expected content
|
||||
description: Describe the accurate behavior or example that should be documented, with a reference when available. Label a proposed clarification or unknown behavior clearly.
|
||||
description: Describe the accurate behavior or example that should be documented.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -51,9 +44,7 @@ body:
|
||||
id: evidence
|
||||
attributes:
|
||||
label: Supporting evidence
|
||||
description: Include an exact passage, source reference, command output, provider documentation, or screenshot supporting the report. For missing or unclear guidance, point to the relevant section and explain the gap. If evidence is unavailable, write NOT_ENOUGH_INFO and explain what is missing.
|
||||
validations:
|
||||
required: true
|
||||
description: Add source, command output, provider documentation, or a link that supports the correction.
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
|
||||
@@ -11,8 +11,6 @@ body:
|
||||
Describe one problem and the outcome you would like. Short answers are
|
||||
welcome. Choose "Not sure yet" wherever needed; you do not need code, a
|
||||
finished design, or a complete test plan to propose an idea.
|
||||
Use concrete examples when available, distinguish current limitations
|
||||
from proposed behavior, and leave unmeasured impact or unknowns explicit.
|
||||
|
||||
Read the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope).
|
||||
Maintainers can help resolve scope and technical questions. Unresolved
|
||||
@@ -60,7 +58,7 @@ body:
|
||||
id: use-cases
|
||||
attributes:
|
||||
label: Use cases
|
||||
description: Optional examples of affected workflows, targets, or callers and their practical impact. Include observed frequency or measurements when available; do not invent them. Leave blank if covered above.
|
||||
description: Optional additional examples of applications, targets, or callers. Leave blank if covered above.
|
||||
|
||||
- type: textarea
|
||||
id: desired-outcome
|
||||
@@ -140,4 +138,4 @@ body:
|
||||
id: references
|
||||
attributes:
|
||||
label: References
|
||||
description: Optional supporting examples, provider documentation, source, measurements, artifacts, issues, or prior art. Distinguish observed results from expected benefits; an idea without evidence is welcome.
|
||||
description: Link relevant provider documentation, source, measurements, artifacts, issues, or discussions.
|
||||
@@ -1,52 +0,0 @@
|
||||
name: Cache npm package downloads
|
||||
description: Restore npm's download cache. Trusted default-branch jobs that opt in may save it.
|
||||
inputs:
|
||||
save:
|
||||
description: Save the shared download cache when this event is allowed to write it.
|
||||
required: false
|
||||
default: "false"
|
||||
outputs:
|
||||
can-save:
|
||||
description: Whether this run may update the shared npm download cache.
|
||||
value: ${{ steps.context.outputs.can-save }}
|
||||
path:
|
||||
description: The npm download-cache directory.
|
||||
value: ${{ steps.context.outputs.path }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- id: context
|
||||
shell: bash
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
LOCKFILES_HASH: ${{ hashFiles('package-lock.json', 'npm-shrinkwrap.json', '.npmrc') }}
|
||||
run: |
|
||||
cache_path="$(npm config get cache)"
|
||||
runtime_arch="$(node -p 'process.arch')"
|
||||
can_save=false
|
||||
if [[ -n "${DEFAULT_BRANCH}" && "${GITHUB_REF}" == "refs/heads/${DEFAULT_BRANCH}" && ( "${GITHUB_EVENT_NAME}" == "push" || "${GITHUB_EVENT_NAME}" == "schedule" || "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ) ]]; then
|
||||
can_save=true
|
||||
fi
|
||||
cache_key="npm-download-${RUNNER_OS}-${runtime_arch}-${LOCKFILES_HASH}"
|
||||
# Same platform only. A lockfile change can reuse tarballs from the previous key.
|
||||
restore_key="npm-download-${RUNNER_OS}-${runtime_arch}-"
|
||||
{
|
||||
printf 'can-save=%s\n' "${can_save}"
|
||||
printf 'path=%s\n' "${cache_path}"
|
||||
printf 'key=%s\n' "${cache_key}"
|
||||
printf 'restore-key=%s\n' "${restore_key}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
- name: Restore and save npm package downloads
|
||||
if: steps.context.outputs.can-save == 'true' && inputs.save == 'true'
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ${{ steps.context.outputs.path }}
|
||||
key: ${{ steps.context.outputs.key }}
|
||||
restore-keys: ${{ steps.context.outputs.restore-key }}
|
||||
- name: Restore npm package downloads without saving
|
||||
if: steps.context.outputs.can-save != 'true' || inputs.save != 'true'
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ${{ steps.context.outputs.path }}
|
||||
key: ${{ steps.context.outputs.key }}
|
||||
restore-keys: ${{ steps.context.outputs.restore-key }}
|
||||
@@ -24,7 +24,19 @@ runs:
|
||||
BUILD_CACHE_PREFIX: rea-portable-build-v2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.npmrc', '.nvmrc', 'package-lock.json') }}
|
||||
run: |
|
||||
npm run --silent test:prepare -- --dry=json |
|
||||
node scripts/ci/portable-build-cache.mjs key >> "$GITHUB_OUTPUT"
|
||||
node --input-type=module -e '
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
const { tasks } = JSON.parse(readFileSync(0, "utf8"));
|
||||
const hashes = tasks.map(({ taskId, hash }) => {
|
||||
if (typeof hash !== "string")
|
||||
throw new Error(`Build task ${taskId} must have an eager Turbo hash`);
|
||||
return `${taskId}:${hash}`;
|
||||
}).sort();
|
||||
const hash = createHash("sha256").update(hashes.join("\n")).digest("hex");
|
||||
console.log(`key=${process.env.BUILD_CACHE_PREFIX}-${hash}`);
|
||||
console.log(`prefix=${process.env.BUILD_CACHE_PREFIX}-`);
|
||||
' >> "$GITHUB_OUTPUT"
|
||||
- name: Restore portable build outputs
|
||||
if: inputs.build_cache_artifact == '' && inputs.save != 'true'
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
name: Prepare qualified release inputs
|
||||
description: Restore and validate the exact qualified tarball and its verifier runtime
|
||||
outputs:
|
||||
version:
|
||||
description: Package version validated against the source and qualified manifest
|
||||
value: ${{ steps.package.outputs.version }}
|
||||
tarball:
|
||||
description: Absolute path to the validated qualified npm tarball
|
||||
value: ${{ steps.package.outputs.tarball }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Download the exact qualified package
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: verified-release-package
|
||||
path: .cache/verified-package/
|
||||
- name: Verify qualified package provenance and digest
|
||||
id: package
|
||||
shell: bash
|
||||
run: node scripts/ci/verified-package.mjs validate .cache/verified-package
|
||||
- name: Restore qualified verifier runtime
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: rea-test-runtime
|
||||
path: .
|
||||
@@ -1,41 +0,0 @@
|
||||
name: Publish MCP Registry metadata
|
||||
description: Validate and publish one released server.json, preserving an identical existing version
|
||||
inputs:
|
||||
directory:
|
||||
description: Directory containing the released server.json
|
||||
default: .
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install MCP Registry publisher
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.directory }}
|
||||
env:
|
||||
MCP_PUBLISHER_VERSION: v1.8.0
|
||||
MCP_PUBLISHER_SHA256_LINUX_AMD64: 1370446bbe74d562608e8005a6ccce02d146a661fbd78674e11cc70b9618d6cf
|
||||
run: |
|
||||
archive="mcp-publisher_linux_amd64.tar.gz"
|
||||
curl -fsSLo "${archive}" \
|
||||
"https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/${archive}"
|
||||
echo "${MCP_PUBLISHER_SHA256_LINUX_AMD64} ${archive}" | sha256sum --check --strict
|
||||
tar xzf "${archive}" mcp-publisher
|
||||
chmod +x mcp-publisher
|
||||
- name: Validate MCP Registry metadata
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.directory }}
|
||||
run: ./mcp-publisher validate
|
||||
- name: Check existing registry version
|
||||
id: registry
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.directory }}
|
||||
run: node "${GITHUB_ACTION_PATH}/registry-state.mjs"
|
||||
- name: Authenticate to MCP Registry
|
||||
if: steps.registry.outputs.exists != 'true'
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.directory }}
|
||||
run: ./mcp-publisher login github-oidc
|
||||
- name: Publish MCP Registry metadata
|
||||
if: steps.registry.outputs.exists != 'true'
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.directory }}
|
||||
run: ./mcp-publisher publish
|
||||
@@ -1,4 +0,0 @@
|
||||
export function registryPublicationState(
|
||||
server: Readonly<{ name: string; version: string } & Record<string, unknown>>,
|
||||
request?: (url: URL, init: RequestInit) => Promise<Response>,
|
||||
): Promise<"present" | "absent">;
|
||||
@@ -1,89 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { appendFile, readFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
/** Exact-version routes currently return false 404s; enumerate the filtered listing. */
|
||||
export async function registryPublicationState(server, request = fetch) {
|
||||
assert.equal(typeof server?.name, "string");
|
||||
assert.equal(typeof server?.version, "string");
|
||||
const cursors = new Set();
|
||||
let cursor;
|
||||
const matches = [];
|
||||
do {
|
||||
const url = new URL(
|
||||
"https://registry.modelcontextprotocol.io/v0.1/servers",
|
||||
);
|
||||
url.search = new URLSearchParams({
|
||||
search: server.name,
|
||||
version: server.version,
|
||||
include_deleted: "true",
|
||||
limit: "100",
|
||||
}).toString();
|
||||
if (cursor !== undefined) url.searchParams.set("cursor", cursor);
|
||||
const response = await request(url, {
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
});
|
||||
if (!response.ok)
|
||||
throw new Error(`Registry lookup failed: HTTP ${response.status}`);
|
||||
const page = await response.json();
|
||||
assert(Array.isArray(page?.servers), "Registry response omitted servers");
|
||||
assert(
|
||||
page.metadata && typeof page.metadata === "object",
|
||||
"Registry response omitted metadata",
|
||||
);
|
||||
for (const entry of page.servers) {
|
||||
assert(
|
||||
typeof entry?.server?.name === "string" &&
|
||||
typeof entry.server.version === "string",
|
||||
"Malformed registry server record",
|
||||
);
|
||||
if (
|
||||
entry.server.name === server.name &&
|
||||
entry.server.version === server.version
|
||||
)
|
||||
matches.push(entry);
|
||||
}
|
||||
cursor = page.metadata.nextCursor;
|
||||
if (cursor !== undefined) {
|
||||
assert(
|
||||
typeof cursor === "string" && cursor.length > 0 && !cursors.has(cursor),
|
||||
"Registry returned a non-advancing cursor",
|
||||
);
|
||||
cursors.add(cursor);
|
||||
}
|
||||
} while (cursor !== undefined);
|
||||
assert(
|
||||
matches.length <= 1,
|
||||
"Registry returned ambiguous exact-version records",
|
||||
);
|
||||
if (matches.length === 0) return "absent";
|
||||
assert.equal(
|
||||
matches[0]._meta?.["io.modelcontextprotocol.registry/official"]?.status,
|
||||
"active",
|
||||
"Existing registry version is not active",
|
||||
);
|
||||
assert.deepEqual(
|
||||
matches[0].server,
|
||||
server,
|
||||
"Existing registry version differs from released metadata",
|
||||
);
|
||||
return "present";
|
||||
}
|
||||
|
||||
if (
|
||||
process.argv[1] !== undefined &&
|
||||
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
||||
) {
|
||||
const state = await registryPublicationState(
|
||||
JSON.parse(await readFile("server.json", "utf8")),
|
||||
);
|
||||
assert(process.env.GITHUB_OUTPUT, "GITHUB_OUTPUT is required");
|
||||
await appendFile(
|
||||
process.env.GITHUB_OUTPUT,
|
||||
`exists=${state === "present"}\n`,
|
||||
);
|
||||
process.stdout.write(
|
||||
`Registry version is ${state}; ${state === "present" ? "preserving identical published metadata" : "publication may proceed"}.\n`,
|
||||
);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
name: REA production code
|
||||
|
||||
# Match the existing default-setup scan's local-input threat model.
|
||||
threat-models: [local]
|
||||
|
||||
# Retain root traversal, including future production roots. Hidden directories
|
||||
# need their own root; use a directory so Python can share this configuration.
|
||||
paths:
|
||||
- .
|
||||
- docs/.vitepress
|
||||
|
||||
# Keep the default query suite and all production roots, including bridge/,
|
||||
# install.sh, runtime scripts, release controllers, and GitHub Actions.
|
||||
# Exclude test inputs at extraction time so they cannot taint production callers.
|
||||
paths-ignore:
|
||||
- tests
|
||||
- scripts/fixtures
|
||||
- "**/*.test.*"
|
||||
- "**/*.fixture.*"
|
||||
- "**/test_*.py"
|
||||
- "**/*_test.py"
|
||||
@@ -1,33 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directories:
|
||||
- /
|
||||
- /scripts/verify-tools/skills
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
open-pull-requests-limit: 5
|
||||
groups:
|
||||
npm-minor-and-patch:
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: /website
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 5
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 5
|
||||
groups:
|
||||
actions-minor-and-patch:
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
@@ -17,17 +17,14 @@ describe the use case and observable outcome. -->
|
||||
## Change and scope
|
||||
|
||||
<!-- Explain the chosen approach, why it fits REA's architecture, and what is
|
||||
intentionally not included. If adding complexity for a speculative or vanishingly
|
||||
unlikely race or security edge case, record the concrete failure mode, likelihood,
|
||||
complexity cost, and the user's buy-in obtained before implementation. Do not
|
||||
silently expand scope for theoretical robustness. -->
|
||||
intentionally not included. -->
|
||||
|
||||
Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope).
|
||||
Unresolved scope, authority, side effects, or required verification block
|
||||
integration. Link the required maintainer scope decision for new providers or
|
||||
capabilities involving target execution, injection, mutation, or protection
|
||||
recovery; ordinary fixes within existing boundaries may state "not applicable".
|
||||
Draft PRs may leave the scope decision pending and the readiness and merge checklists
|
||||
Draft PRs may leave the scope decision pending and the final review checklist
|
||||
unchecked. Maintainers can help resolve open questions.
|
||||
|
||||
## Contract and boundary impact
|
||||
@@ -36,9 +33,6 @@ unchecked. Maintainers can help resolve open questions.
|
||||
Use "none" or "not applicable" only when established. -->
|
||||
|
||||
- Semantic owner and earliest changed stage:
|
||||
<!-- Name the producer of parsed facts or resources and its affected consumers.
|
||||
Summarize superseded paths removed and any distinct policy, lifecycle,
|
||||
lazy-loading, or compatibility responsibility retained. -->
|
||||
- Contribution scope fit and linked maintainer decision:
|
||||
- CLI and MCP/tool-catalog contract:
|
||||
- Provider, bridge, target-format, or platform compatibility:
|
||||
@@ -61,20 +55,13 @@ Unchecked items do not establish absence; leave uncertainties visible.
|
||||
## Evidence and regression coverage
|
||||
|
||||
<!-- State whether evidence is executed, source-derived, or proposed. For fixes,
|
||||
show the original failure and explain how the regression would fail on the affected
|
||||
base revision when practical. Include the most useful proof inline; link long logs
|
||||
or artifacts, or put them in optional details. Documentation-only changes can use
|
||||
source, link, or rendered-document checks. -->
|
||||
explain how the regression would fail on the affected base revision when practical. -->
|
||||
|
||||
- Tests added or updated:
|
||||
- Base reproduction or other evidence:
|
||||
- User-visible CLI/MCP output (if applicable):
|
||||
- Remaining proof gaps:
|
||||
|
||||
For boundary fixes, identify the applicable [boundary regressions](https://github.com/morluto/rea/blob/main/docs/testing.md#boundary-regressions)
|
||||
and [lifecycle regressions](https://github.com/morluto/rea/blob/main/docs/testing.md#lifecycle-regressions)
|
||||
covered by the evidence above, and explain relevant gaps.
|
||||
|
||||
For evidence-bearing changes:
|
||||
|
||||
- [ ] Observed, derived, and inferred claims remain distinguishable.
|
||||
@@ -84,8 +71,7 @@ For evidence-bearing changes:
|
||||
## Validation performed
|
||||
|
||||
<!-- List only commands that actually ran, with observed results. Include the
|
||||
relevant real-provider or platform workflow, or explain why it was unavailable.
|
||||
Keep these results current after changes; a command without its result is incomplete. -->
|
||||
relevant real-provider or platform workflow, or explain why it was unavailable. -->
|
||||
|
||||
- `command` — result
|
||||
|
||||
@@ -104,24 +90,6 @@ limits, security/privacy/containment implications, and meaningful proof gaps. --
|
||||
- Package or release metadata impact:
|
||||
- Security, privacy, process, or containment review:
|
||||
|
||||
## Author readiness
|
||||
|
||||
<!-- The human owner completes these declarations before requesting review,
|
||||
unless the PR author is exempt as listed in CONTRIBUTING.md.
|
||||
The PR author is the owner by default. A different owner must explicitly accept
|
||||
ownership on this PR.
|
||||
AI agents must leave these declarations to a human to dictate to them or there could be catastrophic consequences for many users of REA.
|
||||
Draft PRs may leave them unchecked. These declarations record initial readiness
|
||||
when opening the PR for review; later pushes do not require a new human sign-off.
|
||||
Keep the description and validation evidence current as the work changes.
|
||||
See CONTRIBUTING.md#pull-request-ownership-and-readiness. -->
|
||||
|
||||
Human owner: <!-- @GitHub-handle -->
|
||||
|
||||
- [ ] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
|
||||
- [ ] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
|
||||
- [ ] I declare this PR ready for initial maintainer review.
|
||||
|
||||
## Before merge
|
||||
|
||||
<!-- Draft PRs may leave pending items unchecked. Resolve material scope and
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
name: Cache retention
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["CI"]
|
||||
types: [completed]
|
||||
|
||||
# Qualification calls ci.yml with actions: read. A nested workflow cannot raise
|
||||
# that token, so cache deletion stays in this workflow.
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write
|
||||
|
||||
concurrency:
|
||||
group: cache-retention-${{ github.event.workflow_run.head_sha }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
retain:
|
||||
name: Retain Turbo snapshots and npm download caches
|
||||
# Restrict the triggering-source checkout to same-repository default-branch
|
||||
# CI events. The helper runs from this trusted workflow revision; triggering
|
||||
# source is used only for cache identity and is never executed. Static and
|
||||
# build save snapshots before later lanes finish, so failed, timed-out, or
|
||||
# cancelled runs still retain those snapshots.
|
||||
if: >-
|
||||
(github.event.workflow_run.conclusion == 'success' ||
|
||||
github.event.workflow_run.conclusion == 'failure' ||
|
||||
github.event.workflow_run.conclusion == 'timed_out' ||
|
||||
github.event.workflow_run.conclusion == 'cancelled') &&
|
||||
github.event.workflow_run.head_repository.full_name == github.repository &&
|
||||
github.event.workflow_run.head_branch == github.event.repository.default_branch &&
|
||||
(github.event.workflow_run.event == 'push' ||
|
||||
github.event.workflow_run.event == 'schedule' ||
|
||||
github.event.workflow_run.event == 'workflow_dispatch')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
ref: ${{ github.workflow_sha }}
|
||||
path: trusted-retention
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha }}
|
||||
path: triggering-source
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: trusted-retention/.nvmrc
|
||||
- name: Retain current Turbo snapshots and npm download caches
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
CI_REPOSITORY: ${{ github.repository }}
|
||||
CACHE_PREFIX: rea-static-v1-${{ runner.os }}-${{ runner.arch }}-
|
||||
CACHE_KEEP_KEY: rea-static-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('triggering-source/.nvmrc', 'triggering-source/.npmrc', 'triggering-source/package-lock.json') }}-${{ github.event.workflow_run.head_sha }}
|
||||
CACHE_DEEP_PREFIX: rea-static-deep-v1-${{ runner.os }}-${{ runner.arch }}-
|
||||
CACHE_DEEP_KEEP_KEY: rea-static-deep-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('triggering-source/.nvmrc', 'triggering-source/.npmrc', 'triggering-source/package-lock.json') }}-${{ github.event.workflow_run.head_sha }}
|
||||
CACHE_KNIP_PREFIX: knip-meta-v1-${{ runner.os }}-
|
||||
CACHE_KNIP_KEEP_KEY: knip-meta-v1-${{ runner.os }}-${{ hashFiles('triggering-source/knip.json', 'triggering-source/tsconfig.json', 'triggering-source/package.json', 'triggering-source/package-lock.json') }}-${{ github.event.workflow_run.head_sha }}
|
||||
CACHE_PORTABLE_PREFIX: rea-portable-build-v2-${{ runner.os }}-${{ runner.arch }}-
|
||||
DEDUPE_NPM_DOWNLOADS: "true"
|
||||
run: node trusted-retention/scripts/ci/cache-retention.mjs
|
||||
@@ -12,13 +12,12 @@ concurrency:
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
report:
|
||||
name: Collect recent completed workflow attempts
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
# Consume only API data and execute batch code from the trusted default branch.
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
@@ -28,18 +27,6 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
continue-on-error: true
|
||||
uses: ./.github/actions/npm-cache
|
||||
- name: Diagnose missing and startup-failed PR CI
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
CI_REPOSITORY: ${{ github.repository }}
|
||||
CI_REPOSITORY_ID: ${{ github.repository_id }}
|
||||
CI_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
run: node scripts/ci/pr-ci-health.mjs
|
||||
- name: Record queue, execution, and matched step timing
|
||||
continue-on-error: true
|
||||
env:
|
||||
@@ -57,28 +44,3 @@ jobs:
|
||||
if-no-files-found: error
|
||||
overwrite: true
|
||||
retention-days: 14
|
||||
- name: Install dependencies for static failure attribution
|
||||
id: failure-deps
|
||||
if: ${{ !cancelled() && hashFiles('.cache/ci-job-timings/batch-manifest.json') != '' }}
|
||||
continue-on-error: true
|
||||
run: npm ci
|
||||
- name: Attribute supported static failures against main
|
||||
if: ${{ !cancelled() && steps.failure-deps.outcome == 'success' && hashFiles('.cache/ci-job-timings/batch-manifest.json') != '' }}
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
CI_REPOSITORY: ${{ github.repository }}
|
||||
CI_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
run: node scripts/ci/failure-report.mjs
|
||||
- name: Upload CI health and failure diagnostics
|
||||
if: ${{ !cancelled() }}
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: ci-health-diagnostics
|
||||
path: |
|
||||
.cache/ci-pr-health/
|
||||
.cache/ci-failures/
|
||||
include-hidden-files: true
|
||||
if-no-files-found: warn
|
||||
retention-days: 14
|
||||
+62
-462
@@ -15,15 +15,6 @@ on:
|
||||
description: Exact qualification source; must match the caller revision.
|
||||
required: true
|
||||
type: string
|
||||
remote_cache_write:
|
||||
description: Trusted release qualification may publish the remote cache.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
TURBO_TOKEN:
|
||||
description: Remote cache token. Only trusted release qualification passes it.
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -38,20 +29,11 @@ concurrency:
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# Set Actions secret TURBO_TOKEN and variable TURBO_TEAM. OIDC can mint
|
||||
# TURBO_TOKEN via vercel/setup-turborepo-remote-cache-action; do not commit a token.
|
||||
# A called workflow sees the caller event, so workflow_call is not an event
|
||||
# name here. Release qualification sets remote_cache_write and passes the
|
||||
# token. Open pull requests get an empty token and the local cache only.
|
||||
# Remote reads need a credential, and pull requests do not receive one.
|
||||
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
name: Classify changes
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -60,10 +42,6 @@ jobs:
|
||||
tier: ${{ steps.plan.outputs.tier }}
|
||||
qualification_run_id: ${{ steps.qualification.outputs.run_id }}
|
||||
code: ${{ steps.plan.outputs.code }}
|
||||
reverse_tools: ${{ steps.plan.outputs.reverse_tools }}
|
||||
python: ${{ steps.plan.outputs.python }}
|
||||
packet_capture: ${{ steps.plan.outputs.packet_capture }}
|
||||
signal_recording: ${{ steps.plan.outputs.signal_recording }}
|
||||
sqlite: ${{ steps.plan.outputs.sqlite }}
|
||||
runtime: ${{ steps.plan.outputs.runtime }}
|
||||
package: ${{ steps.plan.outputs.package }}
|
||||
@@ -75,15 +53,12 @@ jobs:
|
||||
browser: ${{ steps.plan.outputs.browser }}
|
||||
binary_layout: ${{ steps.plan.outputs.binary_layout }}
|
||||
evm: ${{ steps.plan.outputs.evm }}
|
||||
apk_signature: ${{ steps.plan.outputs.apk_signature }}
|
||||
javascript_recovery: ${{ steps.plan.outputs.javascript_recovery }}
|
||||
recorded_crash: ${{ steps.plan.outputs.recorded_crash }}
|
||||
web_network: ${{ steps.plan.outputs.web_network }}
|
||||
web_runtime: ${{ steps.plan.outputs.web_runtime }}
|
||||
web_source_map: ${{ steps.plan.outputs.web_source_map }}
|
||||
agentenv: ${{ steps.plan.outputs.agentenv }}
|
||||
nativeaot: ${{ steps.plan.outputs.nativeaot }}
|
||||
ghidra_bridge: ${{ steps.plan.outputs.ghidra_bridge }}
|
||||
website: ${{ steps.plan.outputs.website }}
|
||||
matrix: ${{ steps.plan.outputs.matrix }}
|
||||
test_selection: ${{ steps.plan.outputs.test_selection }}
|
||||
@@ -147,53 +122,20 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Validate source architecture before dependency installation
|
||||
if: needs.changes.outputs.code == 'true'
|
||||
run: node scripts/verify/architecture-guards.mjs
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
with:
|
||||
# Linux x64 writer. Parallel Linux jobs restore this key.
|
||||
save: "true"
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Restore static task cache
|
||||
if: needs.changes.outputs.code == 'true'
|
||||
id: static-cache
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: .turbo/cache/
|
||||
key: rea-static-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.nvmrc', '.npmrc', 'package-lock.json') }}-${{ github.sha }}
|
||||
# Turbo validates each task's inputs after restoring this snapshot.
|
||||
restore-keys: rea-static-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.nvmrc', '.npmrc', 'package-lock.json') }}-
|
||||
- name: Validate workflow syntax and expressions
|
||||
if: needs.changes.outputs.workflows == 'true'
|
||||
run: go run github.com/rhysd/actionlint/cmd/[email protected] -shellcheck= -pyflakes=
|
||||
# Code tiers run this source-only policy test in their existing shards.
|
||||
# Workflow-only changes need the guard, without preparing runtime artifacts.
|
||||
- name: Check action pins for workflow-only changes
|
||||
if: needs.changes.outputs.workflows == 'true' && needs.changes.outputs.code != 'true'
|
||||
run: node scripts/run-exact-tests.mjs run tests/boundary/filesystem/workflowActionPinning.test.ts
|
||||
- name: Record static task source and execution identity
|
||||
continue-on-error: true
|
||||
if: needs.changes.outputs.code == 'true'
|
||||
env:
|
||||
CI_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
CI_STATIC_CONTEXT_SCRIPT: check:ci:fast:tasks
|
||||
run: npm run ci:static-context
|
||||
- if: needs.changes.outputs.code == 'true'
|
||||
run: npm run check:ci:fast
|
||||
run: npm run check:ci
|
||||
- if: needs.changes.outputs.code == 'true'
|
||||
run: npm run verify:test-discovery
|
||||
- name: Check LLDB producer retention and launch contracts
|
||||
if: needs.changes.outputs.code == 'true'
|
||||
run: npm run test:python
|
||||
- if: needs.changes.outputs.code != 'true'
|
||||
run: npm run format:check
|
||||
- name: Save validated static task cache
|
||||
if: >-
|
||||
success() && needs.changes.outputs.code == 'true' &&
|
||||
steps.npm-cache.outputs.can-save == 'true' &&
|
||||
steps.static-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: .turbo/cache/
|
||||
key: ${{ steps.static-cache.outputs.cache-primary-key }}
|
||||
- name: Upload complete static diagnostics
|
||||
if: always() && needs.changes.outputs.code == 'true'
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
@@ -204,121 +146,6 @@ jobs:
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
|
||||
static-deep:
|
||||
name: Deep static checks
|
||||
needs: changes
|
||||
# Full qualification only. Provider lanes wait on fast static, not this job.
|
||||
if: needs.changes.outputs.tier == 'full' && needs.changes.outputs.code == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
- run: npm ci
|
||||
- name: Restore deep static task cache
|
||||
id: static-cache
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: .turbo/cache/
|
||||
key: rea-static-deep-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.nvmrc', '.npmrc', 'package-lock.json') }}-${{ github.sha }}
|
||||
restore-keys: rea-static-deep-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.nvmrc', '.npmrc', 'package-lock.json') }}-
|
||||
- name: Restore Knip analysis cache
|
||||
id: knip-cache
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: node_modules/.cache/knip
|
||||
# Each trusted commit publishes a new entry. Restore still matches the
|
||||
# configuration prefix from the previous successful full run.
|
||||
key: knip-meta-v1-${{ runner.os }}-${{ hashFiles('knip.json', 'tsconfig.json', 'package.json', 'package-lock.json') }}-${{ github.sha }}
|
||||
restore-keys: knip-meta-v1-${{ runner.os }}-${{ hashFiles('knip.json', 'tsconfig.json', 'package.json', 'package-lock.json') }}-
|
||||
- name: Record deep static task source and execution identity
|
||||
continue-on-error: true
|
||||
env:
|
||||
CI_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
CI_STATIC_CONTEXT_SCRIPT: check:ci:deep:tasks
|
||||
run: npm run ci:static-context
|
||||
- name: Run deep static checks
|
||||
run: npm run check:ci:deep
|
||||
- name: Save validated deep static task cache
|
||||
if: >-
|
||||
success() && steps.npm-cache.outputs.can-save == 'true' &&
|
||||
steps.static-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: .turbo/cache/
|
||||
key: ${{ steps.static-cache.outputs.cache-primary-key }}
|
||||
- name: Save Knip analysis cache
|
||||
if: >-
|
||||
success() && steps.npm-cache.outputs.can-save == 'true' &&
|
||||
steps.knip-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: node_modules/.cache/knip
|
||||
key: ${{ steps.knip-cache.outputs.cache-primary-key }}
|
||||
- name: Upload complete deep static diagnostics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: static-deep-check-reports
|
||||
path: .cache/static-checks/
|
||||
include-hidden-files: true
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
|
||||
python:
|
||||
name: Python producer tests
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true' && needs.changes.outputs.tier == 'fast'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
- run: npm ci
|
||||
# Analysis-efficiency checks import compiled process modules.
|
||||
- name: Reuse portable build outputs across runs
|
||||
uses: ./.github/actions/portable-build-cache
|
||||
- run: npm run test:prepare
|
||||
- run: npm run test:python
|
||||
|
||||
test-source:
|
||||
name: Source tests
|
||||
needs: changes
|
||||
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.tier == 'fast'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
- run: npm ci
|
||||
- name: Run source tests without waiting for build
|
||||
run: npm run test:fast -- --reporter=default --reporter=blob
|
||||
env:
|
||||
VITEST_BLOB_LABEL: source
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: vitest-report-source
|
||||
path: .vitest/blob/*
|
||||
include-hidden-files: true
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
test-shard:
|
||||
name: Test shard (${{ matrix.shard }})
|
||||
needs: [changes, build]
|
||||
@@ -326,23 +153,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
# Failed PRs release the remaining matrix slots; qualification retains
|
||||
# every shard's diagnostics and coverage. Passing PRs run the full suite.
|
||||
fail-fast: ${{ github.event_name == 'pull_request' && needs.changes.outputs.tier == 'fast' }}
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Fast feedback uses one source group and eight built groups so the
|
||||
# slowest built shard job stays inside a ten-minute execution budget.
|
||||
# Classification, build, and report merge sit outside that job.
|
||||
# Full qualification retains six complete shards with coverage.
|
||||
shard: ${{ fromJSON(needs.changes.outputs.tier == 'fast' && '["1/8","2/8","3/8","4/8","5/8","6/8","7/8","8/8"]' || '["1/6","2/6","3/6","4/6","5/6","6/6"]') }}
|
||||
shard: [1/6, 2/6, 3/6, 4/6, 5/6, 6/6]
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
@@ -353,55 +172,21 @@ jobs:
|
||||
run: npm run test:ci:shard:run -- --shard=${{ matrix.shard }}
|
||||
env:
|
||||
REA_CI_TEST_HISTORY: .cache/ci-test-history/test-results.json
|
||||
- name: Run built-dependent tests for fast feedback
|
||||
- name: Run complete portable suite for fast feedback
|
||||
if: needs.changes.outputs.tier == 'fast'
|
||||
run: npm run test:built -- --reporter=default --reporter=blob --shard=${{ matrix.shard }}
|
||||
run: npm run test:run -- --reporter=default --reporter=blob --shard=${{ matrix.shard }}
|
||||
env:
|
||||
VITEST_BLOB_LABEL: built
|
||||
REA_CI_TEST_HISTORY: .cache/ci-test-history/test-results.json
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: vitest-report-built-${{ strategy.job-index }}
|
||||
name: vitest-report-${{ strategy.job-index }}
|
||||
path: .vitest/blob/*
|
||||
include-hidden-files: true
|
||||
if-no-files-found: error
|
||||
overwrite: true
|
||||
retention-days: 7
|
||||
|
||||
reverse-tools-quality:
|
||||
name: Reverse tools quality
|
||||
needs: [changes, static]
|
||||
if: needs.changes.outputs.reverse_tools == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
- run: npm ci
|
||||
- name: Run reverse tools tests with coverage
|
||||
run: npm run test:coverage:reverse-tools
|
||||
- name: Preflight both reverse tools mutation configurations
|
||||
run: |
|
||||
node scripts/prepare-reverse-tools-mutation.mjs
|
||||
npx --no-install stryker run stryker.config.mjs --dryRunOnly
|
||||
npx --no-install stryker run stryker.semantics.config.mjs --dryRunOnly
|
||||
- name: Retain available reverse tools diagnostics
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: reverse-tools-quality-${{ needs.changes.outputs.tier }}-${{ github.run_attempt }}
|
||||
path: |
|
||||
coverage/reverse-tools/
|
||||
reports/mutation/
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
|
||||
build:
|
||||
name: Build package
|
||||
needs: changes
|
||||
@@ -412,15 +197,11 @@ jobs:
|
||||
needs.changes.outputs.browser == 'true' ||
|
||||
needs.changes.outputs.binary_layout == 'true' ||
|
||||
needs.changes.outputs.evm == 'true' ||
|
||||
needs.changes.outputs.apk_signature == 'true' ||
|
||||
needs.changes.outputs.javascript_recovery == 'true' ||
|
||||
needs.changes.outputs.recorded_crash == 'true' ||
|
||||
needs.changes.outputs.web_network == 'true' ||
|
||||
needs.changes.outputs.web_runtime == 'true' ||
|
||||
needs.changes.outputs.web_source_map == 'true' ||
|
||||
needs.changes.outputs.agentenv == 'true' ||
|
||||
needs.changes.outputs.packet_capture == 'true' ||
|
||||
needs.changes.outputs.signal_recording == 'true'
|
||||
needs.changes.outputs.web_source_map == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
@@ -431,18 +212,14 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Reuse portable build outputs across runs
|
||||
uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
save: ${{ github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||
- run: npm run test:prepare
|
||||
# Fast and full shards measure different file costs. Download only the
|
||||
# matching history so one tier cannot pack the other with its weights.
|
||||
- name: Find available trusted timing history
|
||||
- name: Find available trusted full-suite timing history
|
||||
id: history
|
||||
continue-on-error: true
|
||||
env:
|
||||
@@ -450,14 +227,13 @@ jobs:
|
||||
CI_REPOSITORY: ${{ github.repository }}
|
||||
CI_REPOSITORY_ID: ${{ github.repository_id }}
|
||||
CI_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
CI_TIMING_TIER: ${{ needs.changes.outputs.tier }}
|
||||
run: node scripts/ci/test-history.mjs
|
||||
- name: Download previous timing report
|
||||
if: steps.history.outputs.run-id != ''
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: ${{ steps.history.outputs.artifact-name }}
|
||||
name: test-timings
|
||||
run-id: ${{ steps.history.outputs.run-id }}
|
||||
github-token: ${{ github.token }}
|
||||
path: .cache/ci-test-history
|
||||
@@ -478,30 +254,24 @@ jobs:
|
||||
# test:prepare caches only portable JS, skill, and catalog outputs.
|
||||
# Native package jobs still run their build and prepack checks; Turbo
|
||||
# restores matching outputs or rebuilds when their inputs differ.
|
||||
- name: Stage current portable build task cache
|
||||
shell: bash
|
||||
run: |
|
||||
# A new Turbo dry run can evict the entries this build just used.
|
||||
node scripts/ci/portable-build-cache.mjs snapshot < .cache/ci-build-plan.json
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: rea-build-cache
|
||||
path: .cache/ci-build-cache/
|
||||
path: .turbo/cache/
|
||||
include-hidden-files: true
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
sqlite-runtime:
|
||||
# Admit expensive host/provider work after fast static validation
|
||||
# (typecheck, lint, format). Deep static does not gate these lanes.
|
||||
# Portable tests still provide early feedback.
|
||||
# Preserve all selected coverage, but admit expensive host/provider work
|
||||
# only after static validation. Portable tests still provide early feedback.
|
||||
needs: [changes, static, build]
|
||||
if: needs.changes.outputs.sqlite == 'true'
|
||||
uses: ./.github/workflows/sqlite-runtime.yml
|
||||
|
||||
test:
|
||||
name: Portable tests
|
||||
needs: [changes, test-source, test-shard]
|
||||
needs: [changes, test-shard]
|
||||
if: ${{ !cancelled() && needs.changes.outputs.tier != 'reused' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
@@ -516,28 +286,18 @@ jobs:
|
||||
if: needs.changes.outputs.code == 'true' && success()
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
if: needs.changes.outputs.code == 'true' && success()
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- if: needs.changes.outputs.code == 'true' && success()
|
||||
run: npm ci
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
if: >-
|
||||
!cancelled() && needs.changes.outputs.code == 'true' &&
|
||||
(needs.test-shard.result == 'success' || needs.test-shard.result == 'failure' ||
|
||||
(needs.changes.outputs.tier == 'fast' &&
|
||||
(needs.test-source.result == 'success' || needs.test-source.result == 'failure')))
|
||||
if: needs.changes.outputs.code == 'true' && success()
|
||||
with:
|
||||
path: .vitest/blob
|
||||
pattern: vitest-report-*
|
||||
merge-multiple: true
|
||||
- name: Merge coverage, JUnit, and timing reports
|
||||
id: merge
|
||||
if: >-
|
||||
!cancelled() && needs.changes.outputs.code == 'true' &&
|
||||
needs.changes.outputs.tier == 'full' &&
|
||||
(needs.test-shard.result == 'success' || needs.test-shard.result == 'failure')
|
||||
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.tier == 'full' && success()
|
||||
run: >-
|
||||
npm run test:ci:merge --
|
||||
--coverage.reportsDirectory=coverage
|
||||
@@ -552,11 +312,7 @@ jobs:
|
||||
--outputFile.json=test-results.json
|
||||
- name: Merge fast-suite JUnit and timing reports
|
||||
id: merge-fast
|
||||
if: >-
|
||||
!cancelled() && needs.changes.outputs.code == 'true' &&
|
||||
needs.changes.outputs.tier == 'fast' &&
|
||||
(needs.test-shard.result == 'success' || needs.test-shard.result == 'failure' ||
|
||||
needs.test-source.result == 'success' || needs.test-source.result == 'failure')
|
||||
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.tier == 'fast' && success()
|
||||
run: >-
|
||||
npm exec -- vitest --merge-reports
|
||||
--reporter=default
|
||||
@@ -566,11 +322,9 @@ jobs:
|
||||
--outputFile.junit=test-results.xml
|
||||
--outputFile.json=test-results.json
|
||||
- name: Summarize slowest test files
|
||||
if: needs.changes.outputs.code == 'true' && !cancelled()
|
||||
if: needs.changes.outputs.code == 'true' && !cancelled() && hashFiles('test-timings.json') != ''
|
||||
env:
|
||||
SHARD_RESULT: ${{ needs.test-shard.result }}
|
||||
SOURCE_REQUIRED: ${{ needs.changes.outputs.tier == 'fast' }}
|
||||
SOURCE_RESULT: ${{ needs.test-source.result }}
|
||||
MERGE_RESULT: ${{ needs.changes.outputs.tier == 'full' && steps.merge.outcome || steps.merge-fast.outcome }}
|
||||
CI_VERIFY_TEST_INVENTORY: "true"
|
||||
CI_TEST_SELECTION: ${{ needs.changes.outputs.test_selection }}
|
||||
@@ -589,22 +343,11 @@ jobs:
|
||||
include-hidden-files: true
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
# Default-branch runs only. Pull-request artifacts share this name and
|
||||
# the lookup reads one page, so publishing them would hide main.
|
||||
- name: Publish successful timing history
|
||||
if: >-
|
||||
success() &&
|
||||
needs.changes.outputs.code == 'true' &&
|
||||
needs.test-shard.result == 'success' &&
|
||||
(
|
||||
needs.changes.outputs.tier == 'full' ||
|
||||
(needs.changes.outputs.tier == 'fast' && needs.test-source.result == 'success')
|
||||
) &&
|
||||
github.ref == format('refs/heads/{0}', github.event.repository.default_branch) &&
|
||||
(github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
||||
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.tier == 'full' && success() && needs.test-shard.result == 'success'
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: test-timings-${{ needs.changes.outputs.tier }}
|
||||
name: test-timings
|
||||
path: .cache/ci-test-history/test-results.json
|
||||
include-hidden-files: true
|
||||
if-no-files-found: error
|
||||
@@ -614,13 +357,7 @@ jobs:
|
||||
env:
|
||||
CODE_REQUIRED: ${{ needs.changes.outputs.code }}
|
||||
SHARD_RESULT: ${{ needs.test-shard.result }}
|
||||
SOURCE_REQUIRED: ${{ needs.changes.outputs.tier == 'fast' }}
|
||||
SOURCE_RESULT: ${{ needs.test-source.result }}
|
||||
run: |
|
||||
if [ "${CODE_REQUIRED}" = "true" ]; then
|
||||
test "${SHARD_RESULT}" = "success" || exit 1
|
||||
test "${SOURCE_REQUIRED}" != "true" || test "${SOURCE_RESULT}" = "success"
|
||||
fi
|
||||
run: test "${CODE_REQUIRED}" != "true" || test "${SHARD_RESULT}" = "success"
|
||||
|
||||
conformance-fixtures:
|
||||
name: Source-built conformance fixtures
|
||||
@@ -705,12 +442,7 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
with:
|
||||
# ubuntu-latest is the Linux x64 writer above. Other hosts save their own key.
|
||||
save: ${{ matrix.os != 'ubuntu-latest' }}
|
||||
cache: npm
|
||||
- name: Verify native runtime identity
|
||||
env:
|
||||
EXPECTED_CI_PLATFORM: ${{ matrix.platform }}
|
||||
@@ -732,6 +464,11 @@ jobs:
|
||||
- run: npm run verify:windows-native:artifact
|
||||
- name: Verify NativeAOT diagnostic applicability on this host
|
||||
run: npm run test:focused -- tests/evaluation/package/nativeAotDiagnostics.test.ts
|
||||
- name: Resolve npm cache before verifier HOME isolation
|
||||
id: npm-cache
|
||||
run: |
|
||||
cache_path="$(npm config get cache)"
|
||||
echo "path=${cache_path}" >> "${GITHUB_OUTPUT}"
|
||||
- run: npm run verify:package
|
||||
env:
|
||||
npm_config_cache: ${{ steps.npm-cache.outputs.path }}
|
||||
@@ -761,9 +498,7 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
@@ -779,23 +514,9 @@ jobs:
|
||||
npm run verify:legacy-simulator
|
||||
npm run verify:apple-dispatch
|
||||
npm run verify:interface-builder
|
||||
- name: Verify portable iNES CLI, MCP and source boundaries
|
||||
run: >-
|
||||
npm run test:focused --
|
||||
tests/acceptance/inesContainer.test.ts
|
||||
tests/boundary/filesystem/inesArtifact.test.ts
|
||||
tests/boundary/filesystem/inesCancellation.test.ts
|
||||
- name: Run native Apple and process boundary regressions
|
||||
run: >-
|
||||
npm run test:focused --
|
||||
tests/boundary/providers/hopper/hopperMacLaunchArguments.test.ts
|
||||
tests/acceptance/providers/hopperStartupRecovery.test.ts
|
||||
tests/boundary/providers/hopper/hopperClient.helperFailure.test.ts
|
||||
tests/boundary/providers/hopper/bridgeBootstrapCleanup.test.ts
|
||||
tests/boundary/providers/hopper/bridgeTargetLease.integration.test.ts
|
||||
tests/acceptance/artifactDmgAttachmentRecoveryCli.test.ts
|
||||
tests/boundary/providers/native/nativeDmgArtifactReader.test.ts
|
||||
tests/process-global/dmgCancellationReason.test.ts
|
||||
tests/boundary/filesystem/installScript.test.ts
|
||||
tests/boundary/filesystem/nibNativeHierarchy.test.ts
|
||||
tests/boundary/filesystem/interfaceBuilderBundle.test.ts
|
||||
@@ -803,18 +524,11 @@ jobs:
|
||||
tests/boundary/cli/nativePlistPrototypeKeyCli.test.ts
|
||||
tests/boundary/cli/nativePlistSelectionFailureCli.test.ts
|
||||
tests/boundary/cli/nativeDemangleCli.test.ts
|
||||
tests/boundary/cli/nativeDispatchPropertyAttributes.test.ts
|
||||
tests/boundary/mcp/nativeDemangleMcp.test.ts
|
||||
tests/boundary/mcp/nativeMachoSummaryMcp.test.ts
|
||||
tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts
|
||||
tests/boundary/providers/native/nativeUiOffscreenWindow.test.ts
|
||||
tests/boundary/process/nativeUiVerifierLane.test.ts
|
||||
tests/boundary/cli/nativeSignatureCli.test.ts
|
||||
tests/boundary/cli/nativeMachoArm64_32Cli.test.ts
|
||||
tests/boundary/cli/nativeMachoEncryptionCli.test.ts
|
||||
tests/boundary/mcp/nativeSignatureTargetBindingMcp.test.ts
|
||||
src/process/capture/FilesystemSnapshot.test.ts
|
||||
tests/process-global/filesystemSnapshotFallbackCancellation.test.ts
|
||||
tests/boundary/process/darwinProcessRunTokenReader.test.ts
|
||||
tests/boundary/process/directAnalysisSignals.test.ts
|
||||
tests/boundary/process/processCaptureLifecycle.test.ts
|
||||
@@ -832,11 +546,9 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Verify static CLI/MCP journeys and synthetic report conformance
|
||||
- name: Run the public CLI/MCP reconstruction journey
|
||||
env:
|
||||
REA_READINESS_REPORT_PATH: readiness-report.json
|
||||
run: |
|
||||
@@ -863,27 +575,18 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
with:
|
||||
save: "true"
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Typecheck with the official Windows compiler
|
||||
env:
|
||||
GOMAXPROCS: "2"
|
||||
GOMEMLIMIT: "3GiB"
|
||||
run: npm run typecheck -- --checkers 2
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: rea-build-cache
|
||||
path: .turbo/cache/
|
||||
- run: npm run build:cached
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: windows-native-controls
|
||||
path: native/windows/build/
|
||||
- run: npm run verify:windows-native:artifact
|
||||
- run: npm run build:cached
|
||||
- name: Record named Windows host capabilities
|
||||
run: node scripts/windows-capabilities.mjs | Out-File -Encoding utf8NoBOM windows-capabilities.json
|
||||
- name: Validate Windows host capability report
|
||||
@@ -899,53 +602,20 @@ jobs:
|
||||
# The matching native artifact establishes real Job Object and private
|
||||
# runtime boundaries for these fixtures. Ghidra and Java are not needed;
|
||||
# real engine claims remain in the separate controlled-provider workflow.
|
||||
# Run native proofs before the broad curated suite so an unrelated
|
||||
# fixture failure cannot hide the real control and recovery checks.
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: windows-process-boundary-fixture
|
||||
path: .cache/windows-process-boundary/
|
||||
# Passing the fixture adds the breakaway, caller/child token, environment
|
||||
# semantics and ancestor reparse observations, which the lane skips
|
||||
# entirely when it is absent.
|
||||
- run: >-
|
||||
npm run verify:windows-native -- .
|
||||
"${{ github.workspace }}\.cache\windows-process-boundary\process-boundary.exe"
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: windows-native-inspection-fixture
|
||||
path: .cache/windows-native-inspection-fixture/
|
||||
- name: Verify native inspection failure ownership and recovery
|
||||
run: node scripts/verify-windows-native-inspection.mjs
|
||||
- name: Run curated Windows capability tests
|
||||
run: >-
|
||||
npx vitest run --coverage.enabled=false
|
||||
src/process/capture/FilesystemSnapshot.test.ts
|
||||
tests/process-global/filesystemSnapshotFallbackCancellation.test.ts
|
||||
tests/boundary/filesystem/typescriptCompiler.test.ts
|
||||
tests/boundary/filesystem/releaseProposal.test.ts
|
||||
tests/boundary/filesystem/binaryTarget.test.ts
|
||||
src/contracts/sessionPathInputs.test.ts
|
||||
tests/boundary/filesystem/directAnalysisSnapshotReplay.test.ts
|
||||
tests/boundary/mcp/rawBinaryMcp.test.ts
|
||||
tests/boundary/filesystem/safeOutputTree.test.ts
|
||||
tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts
|
||||
tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts
|
||||
tests/boundary/filesystem/setupUnavailableProvider.test.ts
|
||||
tests/boundary/filesystem/windowsNpxRegistration.test.ts
|
||||
tests/boundary/filesystem/claudeDesktopSkillUninstallWindows.test.ts
|
||||
tests/boundary/filesystem/clientConfigurationEnvironment.test.ts
|
||||
tests/boundary/filesystem/clientConfigurationLayeredEnvironment.test.ts
|
||||
tests/acceptance/setup/registrationRuntimeEnvironmentCli.test.ts
|
||||
tests/boundary/android/workspaceGuardianRetry.test.ts
|
||||
tests/acceptance/setup/homeDirectoryFallback.test.ts
|
||||
tests/boundary/filesystem/javascriptApplicationWorkflows.test.ts
|
||||
tests/boundary/filesystem/webScriptManifestPaths.test.ts
|
||||
tests/boundary/process/ProcessEvidence.host.test.ts
|
||||
tests/boundary/process/packageNpmInvocation.test.ts
|
||||
tests/boundary/process/packedBridgeVerification.test.ts
|
||||
tests/boundary/process/windowsOutputCapture.test.ts
|
||||
tests/boundary/process/windowsExecutableSelection.test.ts
|
||||
tests/acceptance/webModuleCli.test.ts
|
||||
tests/acceptance/webSourceLocationCli.test.ts
|
||||
tests/boundary/cli/referenceSourceImportStatus.test.ts
|
||||
@@ -967,9 +637,23 @@ jobs:
|
||||
src/process/ProcessOwnership.validation.test.ts
|
||||
src/application/Update.test.ts
|
||||
tests/process-global/updateMaintenance.test.ts
|
||||
tests/process-global/runnerCancellation.test.ts
|
||||
tests/process-global/windowsOwnedCommand.test.ts
|
||||
tests/conformance/providers/ghidra/realGhidraWindowsWorkflow.test.ts
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: windows-process-boundary-fixture
|
||||
path: .cache/windows-process-boundary/
|
||||
# Passing the fixture adds the breakaway, caller/child token, environment
|
||||
# semantics and ancestor reparse observations, which the lane skips
|
||||
# entirely when it is absent.
|
||||
- run: >-
|
||||
npm run verify:windows-native -- .
|
||||
"${{ github.workspace }}\.cache\windows-process-boundary\process-boundary.exe"
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: windows-native-inspection-fixture
|
||||
path: .cache/windows-native-inspection-fixture/
|
||||
- name: Verify native inspection failure ownership and recovery
|
||||
run: node scripts/verify-windows-native-inspection.mjs
|
||||
- name: Verify real Node Inspector discovery and observation
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
@@ -985,7 +669,7 @@ jobs:
|
||||
tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22.23.4
|
||||
node-version: 22.19.0
|
||||
- name: Verify native path boundaries on Node 22
|
||||
run: >-
|
||||
npx vitest run --coverage.enabled=false
|
||||
@@ -1001,7 +685,7 @@ jobs:
|
||||
retention-days: 30
|
||||
|
||||
docs:
|
||||
name: Documentation
|
||||
name: Docs Generation
|
||||
needs: changes
|
||||
if: needs.changes.outputs.docs == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
@@ -1011,13 +695,9 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run docs:check
|
||||
- name: Render documentation and validate page links
|
||||
run: npm run docs:build
|
||||
- name: Generation leaves authored files unchanged
|
||||
run: git diff --exit-code
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
@@ -1029,137 +709,67 @@ jobs:
|
||||
skills/reverse-engineer-anything
|
||||
if-no-files-found: error
|
||||
|
||||
# Provider lanes wait for fast static and the portable build, not deep static.
|
||||
browser:
|
||||
needs: [changes, static, build]
|
||||
if: needs.changes.outputs.browser == 'true'
|
||||
secrets:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
uses: ./.github/workflows/real-browser.yml
|
||||
with:
|
||||
build_cache_artifact: rea-build-cache
|
||||
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
|
||||
|
||||
binary_layout:
|
||||
needs: [changes, static, build]
|
||||
if: needs.changes.outputs.binary_layout == 'true'
|
||||
secrets:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
uses: ./.github/workflows/real-binary-layout.yml
|
||||
with:
|
||||
build_cache_artifact: rea-build-cache
|
||||
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
|
||||
|
||||
evm:
|
||||
needs: [changes, static, build]
|
||||
if: needs.changes.outputs.evm == 'true'
|
||||
secrets:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
uses: ./.github/workflows/real-evm-interface.yml
|
||||
with:
|
||||
build_cache_artifact: rea-build-cache
|
||||
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
|
||||
|
||||
apk_signature:
|
||||
needs: [changes, static, build]
|
||||
if: needs.changes.outputs.apk_signature == 'true'
|
||||
secrets:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
uses: ./.github/workflows/real-apk-signature.yml
|
||||
with:
|
||||
build_cache_artifact: rea-build-cache
|
||||
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
|
||||
|
||||
javascript_recovery:
|
||||
needs: [changes, static, build]
|
||||
if: needs.changes.outputs.javascript_recovery == 'true'
|
||||
secrets:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
uses: ./.github/workflows/real-javascript-recovery.yml
|
||||
with:
|
||||
build_cache_artifact: rea-build-cache
|
||||
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
|
||||
|
||||
recorded_crash:
|
||||
needs: [changes, static, build]
|
||||
if: needs.changes.outputs.recorded_crash == 'true'
|
||||
secrets:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
uses: ./.github/workflows/real-recorded-crash.yml
|
||||
with:
|
||||
build_cache_artifact: rea-build-cache
|
||||
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
|
||||
|
||||
web_network:
|
||||
needs: [changes, static, build]
|
||||
if: needs.changes.outputs.web_network == 'true'
|
||||
secrets:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
uses: ./.github/workflows/real-web-network-captures.yml
|
||||
with:
|
||||
build_cache_artifact: rea-build-cache
|
||||
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
|
||||
|
||||
web_runtime:
|
||||
needs: [changes, static, build]
|
||||
if: needs.changes.outputs.web_runtime == 'true'
|
||||
secrets:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
uses: ./.github/workflows/real-web-runtime.yml
|
||||
with:
|
||||
build_cache_artifact: rea-build-cache
|
||||
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
|
||||
|
||||
web_source_map:
|
||||
needs: [changes, static, build]
|
||||
if: needs.changes.outputs.web_source_map == 'true'
|
||||
secrets:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
uses: ./.github/workflows/real-web-source-map.yml
|
||||
with:
|
||||
build_cache_artifact: rea-build-cache
|
||||
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
|
||||
|
||||
agentenv:
|
||||
needs: [changes, static, build]
|
||||
if: needs.changes.outputs.agentenv == 'true'
|
||||
secrets:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
uses: ./.github/workflows/real-agentenv.yml
|
||||
with:
|
||||
build_cache_artifact: rea-build-cache
|
||||
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
|
||||
|
||||
packet_capture:
|
||||
needs: [changes, static, build]
|
||||
if: needs.changes.outputs.packet_capture == 'true'
|
||||
secrets:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
uses: ./.github/workflows/real-packet-capture.yml
|
||||
with:
|
||||
build_cache_artifact: rea-build-cache
|
||||
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
|
||||
|
||||
signal_recording:
|
||||
needs: [changes, static, build]
|
||||
if: needs.changes.outputs.signal_recording == 'true'
|
||||
secrets:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
uses: ./.github/workflows/real-signal-recording.yml
|
||||
with:
|
||||
build_cache_artifact: rea-build-cache
|
||||
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
|
||||
|
||||
nativeaot:
|
||||
needs: [changes, static]
|
||||
if: needs.changes.outputs.nativeaot == 'true'
|
||||
uses: ./.github/workflows/nativeaot-fixtures.yml
|
||||
|
||||
ghidra_bridge:
|
||||
needs: [changes, static]
|
||||
if: needs.changes.outputs.ghidra_bridge == 'true'
|
||||
uses: ./.github/workflows/ghidra-bridge-sources.yml
|
||||
|
||||
website:
|
||||
needs: [changes, static]
|
||||
if: needs.changes.outputs.website == 'true'
|
||||
@@ -1167,20 +777,13 @@ jobs:
|
||||
|
||||
required:
|
||||
name: CI required
|
||||
# Report failed dependencies, but release superseded PR concurrency slots.
|
||||
if: ${{ !cancelled() }}
|
||||
if: ${{ always() }}
|
||||
needs:
|
||||
[
|
||||
changes,
|
||||
static,
|
||||
static-deep,
|
||||
build,
|
||||
reverse-tools-quality,
|
||||
python,
|
||||
packet_capture,
|
||||
signal_recording,
|
||||
test-shard,
|
||||
test-source,
|
||||
sqlite-runtime,
|
||||
test,
|
||||
conformance-fixtures,
|
||||
@@ -1193,18 +796,15 @@ jobs:
|
||||
browser,
|
||||
binary_layout,
|
||||
evm,
|
||||
apk_signature,
|
||||
javascript_recovery,
|
||||
recorded_crash,
|
||||
web_network,
|
||||
web_runtime,
|
||||
web_source_map,
|
||||
agentenv,
|
||||
nativeaot,
|
||||
ghidra_bridge,
|
||||
website,
|
||||
]
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
name: CodeQL production
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: "37 5 * * 1"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: codeql-production-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
# A repository administrator must disable default setup before enabling
|
||||
# this workflow; default setup rejects advanced CodeQL result uploads.
|
||||
if: vars.REA_CODEQL_ADVANCED == 'true'
|
||||
name: Analyze production (${{ matrix.language }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
language: [javascript-typescript, python, actions]
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Initialize production analysis
|
||||
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: none
|
||||
config-file: .github/codeql/production.yml
|
||||
threads: 2
|
||||
ram: 4096
|
||||
- name: Analyze production code
|
||||
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
|
||||
with:
|
||||
category: /language:${{ matrix.language }}
|
||||
output: ${{ runner.temp }}/codeql-production-results
|
||||
threads: 2
|
||||
ram: 4096
|
||||
- name: Retain complete analysis evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: codeql-production-${{ matrix.language }}
|
||||
path: ${{ runner.temp }}/codeql-production-results
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
@@ -1,97 +0,0 @@
|
||||
name: Ghidra bridge source compilation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
|
||||
jobs:
|
||||
ghidra-bridge-sources:
|
||||
name: Compile packaged Ghidra bridge sources
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
GHIDRA_RELEASE_URL: https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_12.1.4_build/ghidra_12.1.4_PUBLIC_20260921.zip
|
||||
GHIDRA_RELEASE_SHA256: ddac49f903da9d5bac833e5cc79395098b9c33cfd3279be5f31bd00387d2d4db
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Validate the SDK pin against the provider's verified release
|
||||
run: node scripts/verify-ghidra-bridge-sources.mjs --check-sdk-pin >> "$GITHUB_ENV"
|
||||
# The bridge probe imports only source-owned ARM/Thumb bytes and never
|
||||
# executes target code. All SDK/project state stays in runner storage.
|
||||
- name: Acquire checksum-verified Ghidra SDK in isolated runner storage
|
||||
shell: bash
|
||||
run: |
|
||||
ghidra_root="${RUNNER_TEMP}/rea-ghidra"
|
||||
mkdir -p "${ghidra_root}"
|
||||
curl -fsSL --retry 3 --write-out 'ghidra_download_bytes=%{size_download} ghidra_download_seconds=%{time_total}\n' -o "${ghidra_root}/ghidra.zip" "${GHIDRA_RELEASE_URL}"
|
||||
echo "${GHIDRA_RELEASE_SHA256} ${ghidra_root}/ghidra.zip" | sha256sum -c -
|
||||
unzip -q "${ghidra_root}/ghidra.zip" -d "${ghidra_root}"
|
||||
rm "${ghidra_root}/ghidra.zip"
|
||||
properties="$(find "${ghidra_root}" -path '*/Ghidra/application.properties' -print -quit)"
|
||||
test -n "${properties}"
|
||||
echo "GHIDRA_INSTALL_DIR=$(dirname "$(dirname "${properties}")")" >> "${GITHUB_ENV}"
|
||||
# JAVA_HOME_<major>_X64 comes from the runner image, not the env context.
|
||||
- name: Compile bridge sources with the verified runner JDK
|
||||
shell: bash
|
||||
run: |
|
||||
java_home_variable="JAVA_HOME_${GHIDRA_JAVA_MAJOR}_X64"
|
||||
test -n "${!java_home_variable}"
|
||||
JAVA_HOME="${!java_home_variable}" npm run verify:ghidra:bridge-sources -- --verified-sdk
|
||||
- name: Inspect real ARM and Thumb instruction context through the bridge
|
||||
shell: bash
|
||||
run: |
|
||||
java_home_variable="JAVA_HOME_${GHIDRA_JAVA_MAJOR}_X64"
|
||||
export JAVA_HOME="${!java_home_variable}"
|
||||
probe_root="${RUNNER_TEMP}/rea-arm-context"
|
||||
mkdir -p "${probe_root}/project"
|
||||
printf '\036\377\057\341\160\107\000\000' > "${probe_root}/arm-thumb.bin"
|
||||
timeout 180s "${GHIDRA_INSTALL_DIR}/support/analyzeHeadless" "${probe_root}/project" ArmContext \
|
||||
-import "${probe_root}/arm-thumb.bin" -loader BinaryLoader -processor ARM:LE:32:v7 \
|
||||
-noanalysis -readOnly -deleteProject \
|
||||
-scriptPath "${GITHUB_WORKSPACE}/tests/conformance/ghidra;${GITHUB_WORKSPACE}/bridge/ghidra" \
|
||||
-postScript ReaArmContextProbe.java "${GITHUB_WORKSPACE}/bridge/ghidra" \
|
||||
> "${probe_root}/output.log" 2>&1 || { cat "${probe_root}/output.log"; exit 1; }
|
||||
cat "${probe_root}/output.log"
|
||||
grep -F 'REA_ARM_CONTEXT_OK arm=0 thumb=1 unavailable=null (injected provider value)' "${probe_root}/output.log"
|
||||
# Each probe imports source-owned bytes at 0x1000, away from the Cortex-M
|
||||
# vector symbols that ARMCortex.pspec defines at 0, and decodes them from
|
||||
# the language's default context, so M-profile Thumb state must hold at
|
||||
# the first byte. The bytes are never executed.
|
||||
- name: Decode M-profile ARM and Xtensa bytes in their default context
|
||||
shell: bash
|
||||
run: |
|
||||
java_home_variable="JAVA_HOME_${GHIDRA_JAVA_MAJOR}_X64"
|
||||
export JAVA_HOME="${!java_home_variable}"
|
||||
probe_root="${RUNNER_TEMP}/rea-decode"
|
||||
mkdir -p "${probe_root}"
|
||||
# mrs r0, msp; bx lr
|
||||
printf '\357\363\010\200\160\107' > "${probe_root}/cortex.bin"
|
||||
# mrs r0, msplim (ARMv8-M only); bx lr
|
||||
printf '\357\363\012\200\160\107' > "${probe_root}/v8m.bin"
|
||||
# entry a1, 32; retw.n
|
||||
printf '\066\101\000\035\360' > "${probe_root}/xtensa.bin"
|
||||
decode() {
|
||||
local name="$1" language="$2"
|
||||
shift 2
|
||||
mkdir -p "${probe_root}/${name}-project"
|
||||
timeout 180s "${GHIDRA_INSTALL_DIR}/support/analyzeHeadless" "${probe_root}/${name}-project" Decode \
|
||||
-import "${probe_root}/${name}.bin" -loader BinaryLoader -loader-baseAddr 0x1000 \
|
||||
-processor "${language}" -noanalysis -readOnly -deleteProject \
|
||||
-scriptPath "${GITHUB_WORKSPACE}/tests/conformance/ghidra" \
|
||||
-postScript ReaDecodeProbe.java "${language}" "$@" \
|
||||
> "${probe_root}/${name}.log" 2>&1 || { cat "${probe_root}/${name}.log"; exit 1; }
|
||||
cat "${probe_root}/${name}.log"
|
||||
grep -F "REA_DECODE_OK ${language} $*" "${probe_root}/${name}.log"
|
||||
}
|
||||
decode cortex ARM:LE:32:Cortex mrs/4/msp bx/2/lr
|
||||
decode v8m ARM:LE:32:v8-m mrs/4/msplim bx/2/lr
|
||||
decode xtensa Xtensa:LE:32:default entry/3/a1 retw.n/2
|
||||
@@ -1,67 +0,0 @@
|
||||
name: Mutation sweep
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "47 4 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: mutation-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# Writes only on the default branch for push, schedule, manual dispatch,
|
||||
# and workflow_call. Pull requests get an empty token and remote read-only mode.
|
||||
TURBO_TOKEN: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && 'local:rw,remote:rw' || 'local:rw,remote:r' }}"
|
||||
|
||||
jobs:
|
||||
mutation:
|
||||
name: Mutation sweep (${{ matrix.suite }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 2
|
||||
matrix:
|
||||
include:
|
||||
- suite: reverse-tools
|
||||
config: stryker.config.mjs
|
||||
- suite: reverse-tools-semantics
|
||||
config: stryker.semantics.config.mjs
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
uses: ./.github/actions/npm-cache
|
||||
- run: npm ci
|
||||
- run: npm run test:prepare
|
||||
- name: Run the complete configuration
|
||||
env:
|
||||
MUTATION_CONFIG: ${{ matrix.config }}
|
||||
run: |
|
||||
node scripts/prepare-reverse-tools-mutation.mjs
|
||||
npx --no-install stryker run "${MUTATION_CONFIG}"
|
||||
- name: Require both reports from the completed sweep
|
||||
env:
|
||||
REPORT_BASE: reports/mutation/${{ matrix.suite }}
|
||||
run: |
|
||||
test -s "${REPORT_BASE}.json"
|
||||
test -s "${REPORT_BASE}.html"
|
||||
- name: Retain available mutation diagnostics
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: mutation-${{ matrix.suite }}-${{ github.run_attempt }}
|
||||
path: |
|
||||
reports/mutation/${{ matrix.suite }}.json
|
||||
reports/mutation/${{ matrix.suite }}.html
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
@@ -23,8 +23,6 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run docs:build
|
||||
@@ -1,51 +0,0 @@
|
||||
name: PR readiness
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [main, "release/*"]
|
||||
types:
|
||||
[
|
||||
opened,
|
||||
edited,
|
||||
synchronize,
|
||||
reopened,
|
||||
ready_for_review,
|
||||
converted_to_draft,
|
||||
]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pull_request_number:
|
||||
description: Open PR to recheck
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: pr-readiness-${{ github.event.pull_request.number || inputs.pull_request_number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
readiness:
|
||||
name: Validate author declaration
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 3
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
statuses: write
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
with:
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
persist-credentials: false
|
||||
- name: Check initial readiness
|
||||
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
|
||||
with:
|
||||
script: |
|
||||
const { pathToFileURL } = require("node:url");
|
||||
const entry = pathToFileURL(
|
||||
`${process.env.GITHUB_WORKSPACE}/scripts/ci/pr-readiness.mjs`,
|
||||
);
|
||||
const { runReadinessCheck } = await import(entry.href);
|
||||
await runReadinessCheck({ github, context, core });
|
||||
@@ -14,7 +14,7 @@ concurrency:
|
||||
jobs:
|
||||
pr-title:
|
||||
name: Conventional PR title
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- name: Validate title for Release Please
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
name: Real agentenv environment bundle
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
build_cache_artifact:
|
||||
description: Portable build cache artifact from the calling CI run.
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
remote_cache_write:
|
||||
description: Trusted release qualification may publish the remote cache.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
TURBO_TOKEN:
|
||||
description: Remote cache token. Only trusted callers pass it.
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# A called workflow sees the caller event, so workflow_call is not an event
|
||||
# name here. Writes when remote_cache_write is set, or on the default branch
|
||||
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
|
||||
# and use the local cache only.
|
||||
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
|
||||
NODE_OPTIONS: --max-old-space-size=768
|
||||
PIP_DISABLE_PIP_VERSION_CHECK: "1"
|
||||
|
||||
jobs:
|
||||
real-agentenv:
|
||||
name: Export and run an environment bundle with the agentenv runner
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
build_cache_artifact: ${{ inputs.build_cache_artifact }}
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
# Public ECR caps anonymous pulls by data per source IP and hosted runners share IPs, so the
|
||||
# framework's image builds can fail here for hours. mirror.gcr.io serves the same Docker
|
||||
# official images under the same digests, so each base image the pinned framework builds from
|
||||
# is pulled there and named as its Dockerfile names it, as the framework's own CI does.
|
||||
# Digest-pinned names are set in containerd, whose images Docker reads only on the containerd
|
||||
# image store; switching stores restarts dockerd, so it happens before anything uses Docker.
|
||||
- name: Use the containerd image store
|
||||
run: |
|
||||
[ -s /etc/docker/daemon.json ] || echo '{}' | sudo tee /etc/docker/daemon.json >/dev/null
|
||||
jq '.features["containerd-snapshotter"] = true' /etc/docker/daemon.json > "$RUNNER_TEMP/daemon.json"
|
||||
sudo mv "$RUNNER_TEMP/daemon.json" /etc/docker/daemon.json
|
||||
sudo systemctl restart docker
|
||||
docker info --format '{{json .DriverStatus}}' | grep -q io.containerd.snapshotter
|
||||
- name: Acquire the pinned agentenv runner in isolated runner storage and check host Docker
|
||||
shell: bash
|
||||
run: |
|
||||
runner_root="${RUNNER_TEMP}/rea-agentenv-runner"
|
||||
python -m venv "${runner_root}"
|
||||
"${runner_root}/bin/python" -m pip install --require-virtualenv --no-cache-dir 'agentenv-framework==0.9.1334'
|
||||
"${runner_root}/bin/agent-env" --version
|
||||
docker version --format '{{.Server.Version}}'
|
||||
echo "REA_AGENT_ENV_COMMAND=${runner_root}/bin/agent-env" >> "${GITHUB_ENV}"
|
||||
echo "REA_DOCKER_COMMAND=$(command -v docker)" >> "${GITHUB_ENV}"
|
||||
- name: Pull the pinned framework's public ECR base images from mirror.gcr.io
|
||||
shell: bash
|
||||
run: |
|
||||
package="$("${REA_AGENT_ENV_COMMAND%/agent-env}/python" -c 'import agent_env, pathlib; print(pathlib.Path(agent_env.__file__).parent)')"
|
||||
grep -rhoE '^FROM +public\.ecr\.aws/docker/library/[^ ]+' --include='Dockerfile*' "${package}" | awk '{print $2}' | sort -u |
|
||||
while read -r image; do
|
||||
name=${image#public.ecr.aws/docker/library/}
|
||||
source=mirror.gcr.io/library/$name
|
||||
case $name in *@*) source=mirror.gcr.io/library/${name%%[:@]*}@${name#*@} ;; esac
|
||||
for attempt in 1 2 3; do
|
||||
docker pull --quiet "$source" && break
|
||||
if [ "$attempt" = 3 ]; then echo "::warning::couldn't pull $source; $image's build will pull it"; continue 2; fi
|
||||
sleep $((attempt * 10))
|
||||
done
|
||||
case ${name%@*} in *:*) docker tag "$source" "${image%@*}" ;; esac
|
||||
case $image in *@*) sudo ctr --namespace moby images tag "$source" "$image" >/dev/null ;; esac
|
||||
done
|
||||
- name: Export a bundle from the source-owned capture and run it with the agentenv runner
|
||||
run: npm run verify:agentenv:environment
|
||||
@@ -1,63 +0,0 @@
|
||||
name: Real Android metadata bridge
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["**"]
|
||||
paths:
|
||||
- .github/workflows/real-android-metadata.yml
|
||||
- bridge/android/**
|
||||
- src/android/JadxRelease.ts
|
||||
- scripts/fixtures/android/**
|
||||
- tests/conformance/android/**
|
||||
- tests/fixtures/android/static-initializers/**
|
||||
- tests/fixtures/android/direct-invokes/**
|
||||
- tests/fixtures/android/external-invokes/**
|
||||
- .nvmrc
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: android-metadata-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
android-metadata:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Fetch checksum-pinned engine and public APK fixture
|
||||
run: node scripts/fixtures/android/fetch.mjs "$RUNNER_TEMP/rea-android"
|
||||
- name: Compile and exercise the real metadata bridge
|
||||
shell: bash
|
||||
run: |
|
||||
export JAVA_HOME="$JAVA_HOME_21_X64"
|
||||
android_root="$RUNNER_TEMP/rea-android"
|
||||
classes="$android_root/classes"
|
||||
jar="$android_root/jadx-headless-mcp-0.7.1-all.jar"
|
||||
mkdir -p "$classes"
|
||||
"$JAVA_HOME/bin/javac" -J-Xmx512m -J-XX:ActiveProcessorCount=2 \
|
||||
-classpath "$jar" -d "$classes" \
|
||||
bridge/android/ReaJadxBridge.java \
|
||||
tests/conformance/android/ReaJadxMetadataRegression.java \
|
||||
tests/conformance/android/ReaJadxExternalReferencesRegression.java
|
||||
"$JAVA_HOME/bin/java" -Xmx512m -XX:ActiveProcessorCount=2 \
|
||||
-classpath "$classes:$jar" ReaJadxMetadataRegression \
|
||||
"$android_root/ApiDemos-debug.apk" \
|
||||
"$GITHUB_WORKSPACE/tests/fixtures/android/static-initializers" \
|
||||
"$classes" "$GITHUB_WORKSPACE/tests/fixtures/android/direct-invokes" \
|
||||
| tee "$android_root/metadata.log"
|
||||
grep -F 'RAW_CLASS_SELECTOR_REGRESSION_PASS nested=true metadata=true references=true' \
|
||||
"$android_root/metadata.log"
|
||||
"$JAVA_HOME/bin/java" -Xmx512m -XX:ActiveProcessorCount=2 \
|
||||
-classpath "$classes:$jar" ReaJadxExternalReferencesRegression \
|
||||
"$GITHUB_WORKSPACE/tests/fixtures/android/external-invokes" "$classes" \
|
||||
| tee "$android_root/external-references.log"
|
||||
grep -F 'EXTERNAL_REFERENCE_REGRESSION_PASS prototypes=2 using_nodes=3 sites=4' \
|
||||
"$android_root/external-references.log"
|
||||
@@ -1,97 +0,0 @@
|
||||
name: Real APK signature inspection
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
build_cache_artifact:
|
||||
description: Portable build cache artifact from the calling CI run.
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
remote_cache_write:
|
||||
description: Trusted release qualification may publish the remote cache.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
TURBO_TOKEN:
|
||||
description: Remote cache token. Only trusted callers pass it.
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# A called workflow sees the caller event, so workflow_call is not an event
|
||||
# name here. Writes when remote_cache_write is set, or on the default branch
|
||||
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
|
||||
# and use the local cache only.
|
||||
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
|
||||
NODE_OPTIONS: "--max-old-space-size=768"
|
||||
RAYON_NUM_THREADS: "1"
|
||||
GOMAXPROCS: "2"
|
||||
UV_THREADPOOL_SIZE: "2"
|
||||
JAVA_TOOL_OPTIONS: "-Xmx512m -XX:ActiveProcessorCount=2"
|
||||
|
||||
jobs:
|
||||
real-apk-signature:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 12
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
build_cache_artifact: ${{ inputs.build_cache_artifact }}
|
||||
- name: Prepare pinned SDK oracle without installing host software
|
||||
shell: bash
|
||||
run: |
|
||||
python3 - <<'PYTHON'
|
||||
import hashlib, os, urllib.request, zipfile
|
||||
from pathlib import Path
|
||||
root = Path(os.environ["RUNNER_TEMP"]) / "rea-apk-signature-sdk"
|
||||
packages = [
|
||||
("build-tools_r36.1_linux.zip", "a7b5889e4a79fcf3b0976bef40d401f4240fb1eed891d9d91169da1111e11d78", 64195204, "build-tools/36.1.0", {"apksigner", "aapt2", "lib/apksigner.jar", "source.properties"}),
|
||||
("platform-36_r02.zip", "37607369a28c5b640b3a7998868d45898ebcb777565a0e85f9acf36f29631d2e", 65878410, "platforms/android-36", {"android.jar", "source.properties"}),
|
||||
]
|
||||
for name, expected, expected_size, directory, selected in packages:
|
||||
archive = root / name
|
||||
archive.parent.mkdir(parents=True, exist_ok=True)
|
||||
digest = hashlib.sha256()
|
||||
total = 0
|
||||
with urllib.request.urlopen("https://dl.google.com/android/repository/" + name, timeout=60) as response, archive.open("wb") as output:
|
||||
while chunk := response.read(1024 * 1024):
|
||||
total += len(chunk)
|
||||
if total > expected_size:
|
||||
raise RuntimeError("SDK archive exceeds the pinned package size budget")
|
||||
digest.update(chunk)
|
||||
output.write(chunk)
|
||||
if total != expected_size or digest.hexdigest() != expected:
|
||||
raise RuntimeError("SDK oracle archive digest mismatch")
|
||||
extracted = set()
|
||||
with zipfile.ZipFile(archive) as source:
|
||||
for entry in source.infolist():
|
||||
relative = entry.filename.partition("/")[2]
|
||||
if relative not in selected:
|
||||
continue
|
||||
target = root / directory / relative
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(source.read(entry))
|
||||
target.chmod((entry.external_attr >> 16) & 0o777 or 0o644)
|
||||
extracted.add(relative)
|
||||
if extracted != selected:
|
||||
raise RuntimeError("Pinned SDK package is missing required oracle files")
|
||||
with Path(os.environ["GITHUB_ENV"]).open("a") as environment:
|
||||
environment.write("ANDROID_HOME=" + str(root) + "\n")
|
||||
PYTHON
|
||||
- run: npm run verify:apk-signature -- --require-tools
|
||||
@@ -14,15 +14,6 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
remote_cache_write:
|
||||
description: Trusted release qualification may publish the remote cache.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
TURBO_TOKEN:
|
||||
description: Remote cache token. Only trusted callers pass it.
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -30,13 +21,6 @@ permissions:
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=768
|
||||
HUSKY: "0"
|
||||
# A called workflow sees the caller event, so workflow_call is not an event
|
||||
# name here. Writes when remote_cache_write is set, or on the default branch
|
||||
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
|
||||
# and use the local cache only.
|
||||
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
|
||||
REA_VERIFY_LARGE_ELF_MCP: ${{ inputs.large_mcp && '1' || '0' }}
|
||||
|
||||
jobs:
|
||||
@@ -48,19 +32,11 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
build_cache_artifact: ${{ inputs.build_cache_artifact }}
|
||||
- name: Install AArch64 and ELF32 cross toolchains for source-owned fixtures
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --yes --no-install-recommends \
|
||||
gcc-aarch64-linux-gnu gcc-i686-linux-gnu gcc-arm-linux-gnueabi gcc-mips-linux-gnu gcc-mipsel-linux-gnu \
|
||||
libc6-dev-arm64-cross libc6-dev-i386-cross libc6-dev-armel-cross libc6-dev-mips-cross libc6-dev-mipsel-cross
|
||||
- name: Acquire exact upstream libraries in an isolated runner environment
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -8,26 +8,10 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
remote_cache_write:
|
||||
description: Trusted release qualification may publish the remote cache.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
TURBO_TOKEN:
|
||||
description: Remote cache token. Only trusted callers pass it.
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# A called workflow sees the caller event, so workflow_call is not an event
|
||||
# name here. Writes when remote_cache_write is set, or on the default branch
|
||||
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
|
||||
# and use the local cache only.
|
||||
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -41,20 +25,12 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
build_cache_artifact: ${{ inputs.build_cache_artifact }}
|
||||
# Own a fresh bus so Chromium never depends on an inherited session address.
|
||||
- run: dbus-run-session -- npm run verify:browser
|
||||
env:
|
||||
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
|
||||
REA_BROWSER_NO_SANDBOX: "true"
|
||||
# The preceding verifier builds the runtime used by this request journey.
|
||||
- run: dbus-run-session -- node scripts/verify-browser-request.mjs
|
||||
- run: npm run verify:browser
|
||||
env:
|
||||
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
|
||||
REA_BROWSER_NO_SANDBOX: "true"
|
||||
@@ -1,84 +0,0 @@
|
||||
name: Real upstream Cutter verification
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
repository_dispatch:
|
||||
types: [real-cutter-upstream]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# Writes only on the default branch for push, schedule, manual dispatch,
|
||||
# and workflow_call. Pull requests get an empty token and remote read-only mode.
|
||||
TURBO_TOKEN: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && 'local:rw,remote:rw' || 'local:rw,remote:r' }}"
|
||||
|
||||
jobs:
|
||||
windows-native-artifact:
|
||||
name: Build Windows bridge security boundary
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Install the cross compiler used by the Windows native lane
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends -y g++-mingw-w64-x86-64
|
||||
- run: npm run build:windows-native
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: cutter-windows-native
|
||||
path: native/windows/build/
|
||||
if-no-files-found: error
|
||||
|
||||
cutter-upstream:
|
||||
name: Cutter upstream smoke (${{ matrix.platform }})
|
||||
needs: windows-native-artifact
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux
|
||||
runner: [self-hosted, cutter-upstream, linux]
|
||||
- platform: macOS
|
||||
runner: [self-hosted, cutter-upstream, macOS]
|
||||
- platform: Windows
|
||||
runner: [self-hosted, cutter-upstream, Windows]
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
- run: npm ci
|
||||
- if: matrix.platform == 'Windows'
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: cutter-windows-native
|
||||
path: native/windows/build/
|
||||
- if: matrix.platform == 'Windows'
|
||||
run: npm run verify:windows-native:artifact
|
||||
- if: matrix.platform == 'Windows'
|
||||
run: npm run build:cached
|
||||
- if: matrix.platform == 'Windows'
|
||||
run: npm run verify:windows-native
|
||||
- name: Verify host and real Cutter bridge
|
||||
env:
|
||||
EXPECTED_PLATFORM: ${{ matrix.platform }}
|
||||
run: npm run verify:cutter
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: always()
|
||||
with:
|
||||
name: cutter-upstream-${{ matrix.platform }}
|
||||
path: cutter-smoke.json
|
||||
if-no-files-found: ignore
|
||||
retention-days: 30
|
||||
@@ -9,15 +9,6 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
remote_cache_write:
|
||||
description: Trusted release qualification may publish the remote cache.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
TURBO_TOKEN:
|
||||
description: Remote cache token. Only trusted callers pass it.
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -25,13 +16,6 @@ permissions:
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=768
|
||||
HUSKY: "0"
|
||||
# A called workflow sees the caller event, so workflow_call is not an event
|
||||
# name here. Writes when remote_cache_write is set, or on the default branch
|
||||
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
|
||||
# and use the local cache only.
|
||||
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
|
||||
|
||||
jobs:
|
||||
real-evm-interface:
|
||||
@@ -42,9 +26,7 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
|
||||
@@ -6,11 +6,6 @@ on:
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# Writes only on the default branch for push, schedule, manual dispatch,
|
||||
# and workflow_call. Pull requests get an empty token and remote read-only mode.
|
||||
TURBO_TOKEN: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && 'local:rw,remote:rw' || 'local:rw,remote:r' }}"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -13,8 +13,20 @@ on:
|
||||
- "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
|
||||
@@ -26,11 +38,6 @@ concurrency:
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=768
|
||||
HUSKY: "0"
|
||||
# Writes only on the default branch for push, schedule, manual dispatch,
|
||||
# and workflow_call. Pull requests get an empty token and remote read-only mode.
|
||||
TURBO_TOKEN: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && 'local:rw,remote:rw' || 'local:rw,remote:r' }}"
|
||||
|
||||
jobs:
|
||||
real-go-binary:
|
||||
@@ -41,9 +48,7 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
|
||||
with:
|
||||
go-version: "1.26.1"
|
||||
|
||||
@@ -5,11 +5,6 @@ on:
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# Writes only on the default branch for push, schedule, manual dispatch,
|
||||
# and workflow_call. Pull requests get an empty token and remote read-only mode.
|
||||
TURBO_TOKEN: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && 'local:rw,remote:rw' || 'local:rw,remote:r' }}"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -24,9 +19,7 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Pack CLI for setup verification
|
||||
shell: bash
|
||||
@@ -47,9 +40,7 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Install packed CLI in isolation
|
||||
shell: bash
|
||||
|
||||
@@ -5,11 +5,6 @@ on:
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# Writes only on the default branch for push, schedule, manual dispatch,
|
||||
# and workflow_call. Pull requests get an empty token and remote read-only mode.
|
||||
TURBO_TOKEN: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && 'local:rw,remote:rw' || 'local:rw,remote:r' }}"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -24,9 +19,7 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Install packed CLI in isolation
|
||||
shell: bash
|
||||
|
||||
@@ -8,15 +8,6 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
remote_cache_write:
|
||||
description: Trusted release qualification may publish the remote cache.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
TURBO_TOKEN:
|
||||
description: Remote cache token. Only trusted callers pass it.
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@@ -24,13 +15,6 @@ permissions:
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# A called workflow sees the caller event, so workflow_call is not an event
|
||||
# name here. Writes when remote_cache_write is set, or on the default branch
|
||||
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
|
||||
# and use the local cache only.
|
||||
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
|
||||
NODE_OPTIONS: "--max-old-space-size=768"
|
||||
RAYON_NUM_THREADS: "1"
|
||||
|
||||
@@ -43,9 +27,7 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
|
||||
@@ -1,35 +1,43 @@
|
||||
name: Real packet capture
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
build_cache_artifact:
|
||||
description: Portable build cache artifact from the calling CI run.
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
remote_cache_write:
|
||||
description: Trusted release qualification may publish the remote cache.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
TURBO_TOKEN:
|
||||
description: Remote cache token. Only trusted callers pass it.
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# A called workflow sees the caller event, so workflow_call is not an event
|
||||
# name here. Writes when remote_cache_write is set, or on the default branch
|
||||
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
|
||||
# and use the local cache only.
|
||||
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- "tests/boundary/captures/**"
|
||||
- "tests/boundary/cli/captureCleanup.test.ts"
|
||||
- "tests/boundary/mcp/packetCaptureMcp.test.ts"
|
||||
- "src/captures/**"
|
||||
- "src/pcap/**"
|
||||
- "src/domain/pcap/**"
|
||||
- "src/domain/analysisView/pcapCaptureView.ts"
|
||||
- "src/application/pcap/**"
|
||||
- "src/contracts/pcap/**"
|
||||
- "src/server/registerPcapTools.ts"
|
||||
- "tests/boundary/pcap/**"
|
||||
- "src/domain/captures/**"
|
||||
- "src/application/captures/**"
|
||||
- "src/contracts/captures/**"
|
||||
- "src/composition/captures.ts"
|
||||
- "src/cli/captureCommands.ts"
|
||||
- "src/server/registerCaptureTools.ts"
|
||||
- "src/server/createServer.ts"
|
||||
- "src/server/sessionAvailabilityPolicy.ts"
|
||||
- "src/application/CapabilityInventory.ts"
|
||||
- "src/contracts/toolContracts.ts"
|
||||
- "src/contracts/toolEffects.ts"
|
||||
- "src/cli.ts"
|
||||
- "src/cliCommandNames.ts"
|
||||
- "src/process/**"
|
||||
- "src/artifacts/readStableArtifact.ts"
|
||||
- "scripts/verify-packet-capture.mjs"
|
||||
- ".github/workflows/real-packet-capture.yml"
|
||||
- "package*.json"
|
||||
- ".nvmrc"
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
group: real-packet-capture-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
tshark:
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -39,23 +47,11 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: sudo apt-get update && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y tshark util-linux python3
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
build_cache_artifact: ${{ inputs.build_cache_artifact }}
|
||||
- run: npm run build:cached
|
||||
- name: Run focused tests and require Linux resource and cleanup cases
|
||||
run: |
|
||||
report=".cache/rea-packet-required-tests.json"
|
||||
mkdir -p .cache
|
||||
rm -f "$report"
|
||||
node scripts/run-exact-tests.mjs run --reporter=default --reporter=json --outputFile.json="$report" \
|
||||
tests/boundary/captures/fieldDiscovery.test.ts src/process/LinuxResourceLimits.test.ts tests/boundary/captures/captureProvider.test.ts tests/boundary/captures/captureDescriptorCleanup.test.ts tests/boundary/pcap/pcapProvider.test.ts tests/boundary/pcap/pcapProviderCrossPlatform.test.ts tests/boundary/captures/resourceLimits.test.ts tests/boundary/captures/packetOutput.test.ts tests/boundary/mcp/packetCaptureMcp.test.ts tests/boundary/cli/captureCleanup.test.ts tests/acceptance/pcapVerifier.test.ts
|
||||
node scripts/ci/require-provider-tests.mjs packet "$report"
|
||||
- run: npm run test:focused -- tests/boundary/captures/fieldDiscovery.test.ts src/process/LinuxResourceLimits.test.ts tests/boundary/captures/captureProvider.test.ts tests/boundary/captures/captureDescriptorCleanup.test.ts tests/boundary/captures/resourceLimits.test.ts tests/boundary/captures/packetOutput.test.ts tests/boundary/mcp/packetCaptureMcp.test.ts tests/boundary/cli/captureCleanup.test.ts
|
||||
- run: node scripts/verify-packet-capture.mjs
|
||||
env:
|
||||
REA_TSHARK_COMMAND: /usr/bin/tshark
|
||||
|
||||
@@ -9,15 +9,6 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
remote_cache_write:
|
||||
description: Trusted release qualification may publish the remote cache.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
TURBO_TOKEN:
|
||||
description: Remote cache token. Only trusted callers pass it.
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -25,13 +16,6 @@ permissions:
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=768
|
||||
HUSKY: "0"
|
||||
# A called workflow sees the caller event, so workflow_call is not an event
|
||||
# name here. Writes when remote_cache_write is set, or on the default branch
|
||||
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
|
||||
# and use the local cache only.
|
||||
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
|
||||
UV_CONCURRENT_BUILDS: "1"
|
||||
UV_CONCURRENT_INSTALLS: "1"
|
||||
UV_PYTHON_DOWNLOADS: never
|
||||
@@ -40,30 +24,21 @@ jobs:
|
||||
real-recorded-crash:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: ["ubuntu:24.04", "debian:trixie-slim"]
|
||||
container: ${{ matrix.image }}
|
||||
steps:
|
||||
- name: Install fixture and debugger prerequisites in the disposable container
|
||||
shell: bash
|
||||
run: |
|
||||
apt-get update -qq
|
||||
apt-get install --yes --no-install-recommends \
|
||||
ca-certificates git python3-venv gcc libc6-dev strace gdb
|
||||
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
build_cache_artifact: ${{ inputs.build_cache_artifact }}
|
||||
- name: Install GDB on the disposable CI runner
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install --yes --no-install-recommends gdb
|
||||
- name: Acquire exact unchanged upstreams in isolated runner storage
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -1,35 +1,36 @@
|
||||
name: Real signal recording
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
build_cache_artifact:
|
||||
description: Portable build cache artifact from the calling CI run.
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
remote_cache_write:
|
||||
description: Trusted release qualification may publish the remote cache.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
TURBO_TOKEN:
|
||||
description: Remote cache token. Only trusted callers pass it.
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# A called workflow sees the caller event, so workflow_call is not an event
|
||||
# name here. Writes when remote_cache_write is set, or on the default branch
|
||||
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
|
||||
# and use the local cache only.
|
||||
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- "tests/boundary/signals/**"
|
||||
- "tests/boundary/cli/signalCleanup.test.ts"
|
||||
- "tests/boundary/mcp/signalRecordingMcp.test.ts"
|
||||
- "src/signals/**"
|
||||
- "src/domain/signals/**"
|
||||
- "src/application/signals/**"
|
||||
- "src/contracts/signals/**"
|
||||
- "src/composition/signals.ts"
|
||||
- "src/cli/signalCommands.ts"
|
||||
- "src/server/registerSignalTools.ts"
|
||||
- "src/server/createServer.ts"
|
||||
- "src/server/sessionAvailabilityPolicy.ts"
|
||||
- "src/application/CapabilityInventory.ts"
|
||||
- "src/contracts/toolContracts.ts"
|
||||
- "src/contracts/toolEffects.ts"
|
||||
- "src/cli.ts"
|
||||
- "src/cliCommandNames.ts"
|
||||
- "src/process/**"
|
||||
- "src/artifacts/readStableArtifact.ts"
|
||||
- "scripts/verify-signal-recording.mjs"
|
||||
- ".github/workflows/real-signal-recording.yml"
|
||||
- "package*.json"
|
||||
- ".nvmrc"
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
group: real-signal-recording-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
sigrok:
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -39,23 +40,11 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: sudo apt-get update && sudo apt-get install -y sigrok-cli util-linux python3
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
build_cache_artifact: ${{ inputs.build_cache_artifact }}
|
||||
- run: npm run build:cached
|
||||
- name: Run focused tests and require Linux resource and cleanup cases
|
||||
run: |
|
||||
report=".cache/rea-signal-required-tests.json"
|
||||
mkdir -p .cache
|
||||
rm -f "$report"
|
||||
node scripts/run-exact-tests.mjs run --reporter=default --reporter=json --outputFile.json="$report" \
|
||||
tests/boundary/signals/decoderDiscovery.test.ts src/process/LinuxResourceLimits.test.ts src/domain/signals/annotationTextWindow.test.ts tests/boundary/signals/recordingProvider.test.ts tests/boundary/signals/recordingDescriptorCleanup.test.ts tests/boundary/signals/resourceLimits.test.ts tests/boundary/mcp/signalRecordingMcp.test.ts tests/boundary/cli/signalCleanup.test.ts
|
||||
node scripts/ci/require-provider-tests.mjs signal "$report"
|
||||
- run: npm run test:focused -- tests/boundary/signals/decoderDiscovery.test.ts src/process/LinuxResourceLimits.test.ts src/domain/signals/annotationTextWindow.test.ts tests/boundary/signals/recordingProvider.test.ts tests/boundary/signals/recordingDescriptorCleanup.test.ts tests/boundary/signals/resourceLimits.test.ts tests/boundary/mcp/signalRecordingMcp.test.ts tests/boundary/cli/signalCleanup.test.ts
|
||||
- run: node scripts/verify-signal-recording.mjs
|
||||
env:
|
||||
REA_SIGROK_COMMAND: /usr/bin/sigrok-cli
|
||||
@@ -12,11 +12,6 @@ permissions:
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# Writes only on the default branch for push, schedule, manual dispatch,
|
||||
# and workflow_call. Pull requests get an empty token and remote read-only mode.
|
||||
TURBO_TOKEN: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && 'local:rw,remote:rw' || 'local:rw,remote:r' }}"
|
||||
|
||||
jobs:
|
||||
real-termux:
|
||||
|
||||
@@ -9,28 +9,12 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
remote_cache_write:
|
||||
description: Trusted release qualification may publish the remote cache.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
TURBO_TOKEN:
|
||||
description: Remote cache token. Only trusted callers pass it.
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# A called workflow sees the caller event, so workflow_call is not an event
|
||||
# name here. Writes when remote_cache_write is set, or on the default branch
|
||||
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
|
||||
# and use the local cache only.
|
||||
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
|
||||
NODE_OPTIONS: --max-old-space-size=768
|
||||
|
||||
jobs:
|
||||
@@ -42,9 +26,7 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- name: Use pinned package manager
|
||||
run: npm install --global [email protected]
|
||||
- run: npm ci
|
||||
|
||||
@@ -7,27 +7,11 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
remote_cache_write:
|
||||
description: Trusted release qualification may publish the remote cache.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
TURBO_TOKEN:
|
||||
description: Remote cache token. Only trusted callers pass it.
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# A called workflow sees the caller event, so workflow_call is not an event
|
||||
# name here. Writes when remote_cache_write is set, or on the default branch
|
||||
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
|
||||
# and use the local cache only.
|
||||
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
|
||||
jobs:
|
||||
real-web-runtime:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -37,15 +21,12 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
build_cache_artifact: ${{ inputs.build_cache_artifact }}
|
||||
# Own a fresh bus so Chromium never depends on an inherited session address.
|
||||
- run: dbus-run-session -- npm run verify:browser:runtime
|
||||
- run: npm run verify:browser:runtime
|
||||
env:
|
||||
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
|
||||
REA_BROWSER_NO_SANDBOX: "true"
|
||||
@@ -7,27 +7,11 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
remote_cache_write:
|
||||
description: Trusted release qualification may publish the remote cache.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
TURBO_TOKEN:
|
||||
description: Remote cache token. Only trusted callers pass it.
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# A called workflow sees the caller event, so workflow_call is not an event
|
||||
# name here. Writes when remote_cache_write is set, or on the default branch
|
||||
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
|
||||
# and use the local cache only.
|
||||
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
|
||||
jobs:
|
||||
real-web-source-map:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -37,21 +21,15 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
build_cache_artifact: ${{ inputs.build_cache_artifact }}
|
||||
- name: Install the pinned compiler into an isolated fixture directory
|
||||
run: npm install --prefix "$RUNNER_TEMP/rea-web-source-map-compiler" --no-audit --no-fund --package-lock=false [email protected]
|
||||
# Own a fresh bus so Chromium never depends on an inherited session address.
|
||||
- run: dbus-run-session -- npm run verify:browser:source-maps
|
||||
- run: npm run verify:browser:source-maps
|
||||
env:
|
||||
# Retain browser launch argv/PID/stderr in the verifier's captured
|
||||
# CLI failure diagnostics without changing its deadline or retrying.
|
||||
DEBUG: pw:browser
|
||||
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
|
||||
REA_BROWSER_NO_SANDBOX: "true"
|
||||
REA_WEB_SOURCE_MAP_COMPILER: ${{ runner.temp }}/rea-web-source-map-compiler/node_modules/esbuild/lib/main.js
|
||||
@@ -1,109 +0,0 @@
|
||||
name: Recover published release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Existing npm and GitHub release version
|
||||
required: true
|
||||
type: string
|
||||
release_run_id:
|
||||
description: Original Release run with successful exact-source qualification and npm publication
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: release-recovery-${{ inputs.version }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
name: Verify existing published package
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
outputs:
|
||||
source_sha: ${{ steps.release.outputs.source_sha }}
|
||||
version: ${{ steps.release.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
package-manager-cache: false
|
||||
- run: npm ci
|
||||
- name: Validate original release authority
|
||||
id: release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RECOVERY_VERSION: ${{ inputs.version }}
|
||||
RECOVERY_RUN_ID: ${{ inputs.release_run_id }}
|
||||
run: node scripts/ci/release-recovery.mjs resolve
|
||||
- name: Check out released metadata
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
with:
|
||||
ref: ${{ steps.release.outputs.source_sha }}
|
||||
path: .cache/release-source
|
||||
persist-credentials: false
|
||||
- name: Download original qualified package
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: verified-release-package
|
||||
run-id: ${{ steps.release.outputs.run_id }}
|
||||
github-token: ${{ github.token }}
|
||||
path: .cache/verified-package
|
||||
- name: Restore original verifier runtime
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: rea-test-runtime
|
||||
run-id: ${{ steps.release.outputs.run_id }}
|
||||
github-token: ${{ github.token }}
|
||||
path: .
|
||||
- name: Match published npm bytes to the qualified package
|
||||
env:
|
||||
RECOVERY_VERSION: ${{ steps.release.outputs.version }}
|
||||
RECOVERY_RUN_ID: ${{ steps.release.outputs.run_id }}
|
||||
RECOVERY_RUN_ATTEMPT: ${{ steps.release.outputs.run_attempt }}
|
||||
RECOVERY_SOURCE_SHA: ${{ steps.release.outputs.source_sha }}
|
||||
RECOVERY_SOURCE_DIRECTORY: .cache/release-source
|
||||
RECOVERY_ARTIFACT_DIRECTORY: .cache/verified-package
|
||||
run: node scripts/ci/release-recovery.mjs verify
|
||||
- name: Rehearse the original qualified tarball
|
||||
env:
|
||||
RECOVERY_VERSION: ${{ steps.release.outputs.version }}
|
||||
run: node scripts/verify-release-artifact.mjs .cache/verified-package/package.tgz "$RECOVERY_VERSION"
|
||||
- name: Verify published CLI, MCP and update
|
||||
env:
|
||||
RECOVERY_VERSION: ${{ steps.release.outputs.version }}
|
||||
run: node scripts/verify-published-package.mjs "$RECOVERY_VERSION"
|
||||
- name: Validate released registry metadata
|
||||
working-directory: .cache/release-source
|
||||
run: npm run mcp-registry:check
|
||||
|
||||
publish-mcp:
|
||||
name: Finish MCP Registry publication
|
||||
needs: verify
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check out verified released metadata
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
with:
|
||||
ref: ${{ needs.verify.outputs.source_sha }}
|
||||
path: .cache/release-source
|
||||
persist-credentials: false
|
||||
- uses: ./.github/actions/publish-mcp
|
||||
with:
|
||||
directory: .cache/release-source
|
||||
+88
-163
@@ -21,12 +21,6 @@ on:
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
# Remote cache stays local when either value is empty.
|
||||
# Writes only on the default branch for push, schedule, manual dispatch,
|
||||
# and workflow_call. Pull requests get an empty token and remote read-only mode.
|
||||
TURBO_TOKEN: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && secrets.TURBO_TOKEN || '' }}"
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_CACHE: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && 'local:rw,remote:rw' || 'local:rw,remote:r' }}"
|
||||
|
||||
concurrency:
|
||||
group: release-${{ inputs.release_branch || github.event.pull_request.merge_commit_sha || github.ref_name }}
|
||||
@@ -47,63 +41,19 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
run: |
|
||||
# Filter at the repository connection, without a search-index dependency.
|
||||
pages=$(gh api graphql --paginate --slurp \
|
||||
-f owner="${GH_REPO%/*}" -f name="${GH_REPO#*/}" \
|
||||
-f query='query($owner:String!,$name:String!,$endCursor:String) {
|
||||
repository(owner:$owner,name:$name) {
|
||||
nameWithOwner
|
||||
pullRequests(first:100,after:$endCursor,states:OPEN,baseRefName:"main",labels:["rea:release-notes-finalized"]) {
|
||||
nodes { number headRefName headRepository { nameWithOwner } }
|
||||
pageInfo { hasNextPage endCursor }
|
||||
}
|
||||
}
|
||||
}')
|
||||
finalized=$(jq -er '
|
||||
if type != "array" or length == 0 then error("Missing GitHub pages") else . end |
|
||||
if .[-1].data.repository.pullRequests.pageInfo.hasNextPage != false
|
||||
then error("Incomplete GitHub pagination") else . end |
|
||||
map(
|
||||
if (.errors // [] | length) != 0 then error("GitHub query failed") else . end |
|
||||
.data.repository |
|
||||
if (.nameWithOwner | type) != "string" or (.pullRequests.nodes | type) != "array" or
|
||||
(.pullRequests.pageInfo.hasNextPage | type) != "boolean"
|
||||
then error("Invalid GitHub repository connection") else . end |
|
||||
.nameWithOwner as $repo | .pullRequests.nodes[] |
|
||||
if (.number | type) != "number" or (.headRefName | type) != "string" or (has("headRepository") | not) or
|
||||
(.headRepository != null and (.headRepository.nameWithOwner | type) != "string")
|
||||
then error("Invalid GitHub pull request") else . end |
|
||||
select(.headRepository.nameWithOwner == $repo) |
|
||||
select(.headRefName | startswith("release-please--branches--main--")) | .number
|
||||
) | map(tostring) | join(" ")' <<< "$pages")
|
||||
finalized=$(gh api --paginate "repos/${GH_REPO}/pulls?state=open&base=main&per_page=100" \
|
||||
--jq '.[] | select(.head.repo.full_name == .base.repo.full_name) | select(.head.ref | startswith("release-please--branches--main--")) | select(any(.labels[]; .name == "rea:release-notes-finalized")) | .number')
|
||||
if [[ -n "${finalized}" ]]; then
|
||||
echo "Finalized release notes on PR ${finalized}; automatic proposal refresh is paused." >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "frozen=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Check out proposal controller and complete Git history
|
||||
if: steps.editorial.outputs.frozen != 'true'
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||
- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
|
||||
if: steps.editorial.outputs.frozen != 'true'
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Install proposal controller dependencies
|
||||
if: steps.editorial.outputs.frozen != 'true'
|
||||
run: npm ci
|
||||
|
||||
- name: Update release proposal
|
||||
if: steps.editorial.outputs.frozen != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
SOURCE_SHA: ${{ github.sha }}
|
||||
run: node scripts/release-proposal.mjs --target-branch main --source-sha "$SOURCE_SHA"
|
||||
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
target-branch: main
|
||||
skip-github-release: true
|
||||
|
||||
merged-release:
|
||||
name: Release reviewed main merge
|
||||
@@ -276,25 +226,15 @@ jobs:
|
||||
node scripts/release-notes.mjs publication --repository "$GH_REPO" \
|
||||
--source "$SOURCE_SHA"
|
||||
|
||||
- name: Prepare checkpoint release proposal
|
||||
if: inputs.phase == 'prepare'
|
||||
id: proposal
|
||||
env:
|
||||
# A dedicated credential starts PR CI automatically; the built-in
|
||||
# token creates approval-required PR runs for maintainers to approve.
|
||||
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
RELEASE_BRANCH: ${{ inputs.release_branch }}
|
||||
SOURCE_SHA: ${{ steps.branch.outputs.sha }}
|
||||
run: node scripts/release-proposal.mjs --target-branch "$RELEASE_BRANCH" --source-sha "$SOURCE_SHA"
|
||||
|
||||
- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
|
||||
if: inputs.phase == 'publish'
|
||||
id: release
|
||||
with:
|
||||
# A dedicated credential starts PR CI automatically; the built-in
|
||||
# token creates approval-required PR runs for maintainers to approve.
|
||||
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
target-branch: ${{ inputs.release_branch }}
|
||||
skip-github-release: false
|
||||
skip-github-pull-request: true
|
||||
skip-github-release: ${{ inputs.phase == 'prepare' }}
|
||||
skip-github-pull-request: ${{ inputs.phase == 'publish' }}
|
||||
|
||||
- name: Require a merged release PR for publication
|
||||
if: inputs.phase == 'publish' && steps.release.outputs.release_created != 'true'
|
||||
@@ -314,26 +254,26 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Check out release pull request
|
||||
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
|
||||
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
with:
|
||||
ref: ${{ fromJSON(steps.proposal.outputs.pr).headBranchName }}
|
||||
ref: ${{ fromJSON(steps.release.outputs.pr).headBranchName }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js for generated documentation
|
||||
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
|
||||
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Install dependencies
|
||||
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
|
||||
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
|
||||
run: npm ci
|
||||
|
||||
- name: Validate prepared release version and migration notes
|
||||
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
|
||||
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
|
||||
env:
|
||||
RELEASE_BRANCH: ${{ inputs.release_branch }}
|
||||
SOURCE_SHA: ${{ steps.branch.outputs.sha }}
|
||||
@@ -343,11 +283,11 @@ jobs:
|
||||
--source-sha "$SOURCE_SHA"
|
||||
|
||||
- name: Regenerate release documentation
|
||||
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
|
||||
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
|
||||
run: npm run docs:generate
|
||||
|
||||
- name: Validate generated release documentation
|
||||
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
|
||||
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
|
||||
run: npm run docs:check && git diff --exit-code
|
||||
|
||||
qualification:
|
||||
@@ -361,53 +301,15 @@ jobs:
|
||||
contents: read
|
||||
actions: read
|
||||
uses: ./.github/workflows/ci.yml
|
||||
secrets:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
with:
|
||||
source_sha: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
|
||||
remote_cache_write: true
|
||||
|
||||
rehearsal:
|
||||
name: Rehearse qualified release on a clean runner
|
||||
env:
|
||||
TURBO_TOKEN: ""
|
||||
TURBO_CACHE: local:rw
|
||||
publish:
|
||||
name: Publish to npm
|
||||
needs: [merged-release, release-please, qualification]
|
||||
if: >-
|
||||
always() && !cancelled() && needs.qualification.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
with:
|
||||
ref: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
package-manager-cache: false
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/prepare-release-package
|
||||
id: package
|
||||
- name: Exercise the exact qualified tarball
|
||||
env:
|
||||
VERIFIED_PACKAGE_TARBALL: ${{ steps.package.outputs.tarball }}
|
||||
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
|
||||
run: node scripts/verify-release-artifact.mjs "$VERIFIED_PACKAGE_TARBALL" "$VERIFIED_PACKAGE_VERSION"
|
||||
|
||||
publish:
|
||||
name: Publish to npm
|
||||
needs: [merged-release, release-please, qualification, rehearsal]
|
||||
if: >-
|
||||
always() && !cancelled() &&
|
||||
needs.qualification.result == 'success' && needs.rehearsal.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TURBO_TOKEN: ""
|
||||
TURBO_CACHE: local:rw
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
@@ -416,69 +318,73 @@ jobs:
|
||||
with:
|
||||
ref: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
package-manager-cache: false
|
||||
registry-url: https://registry.npmjs.org
|
||||
- name: Download the original qualified package
|
||||
|
||||
- run: npm ci
|
||||
|
||||
- name: Download the exact qualified package
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: verified-release-package
|
||||
path: .cache/verified-package/
|
||||
- name: Bind publication to qualified intent
|
||||
id: package
|
||||
run: node scripts/ci/prepare-npm-publication.mjs
|
||||
- name: Publish
|
||||
if: steps.package.outputs.published == 'false'
|
||||
env:
|
||||
NPM_RELEASE_TAG: ${{ steps.package.outputs.tag }}
|
||||
run: >-
|
||||
npm publish .cache/verified-package/package.tgz --ignore-scripts
|
||||
--registry=https://registry.npmjs.org --access=public --tag="$NPM_RELEASE_TAG"
|
||||
|
||||
verify-npm:
|
||||
name: Verify published npm package
|
||||
needs: [merged-release, release-please, publish]
|
||||
if: >-
|
||||
always() && !cancelled() && needs.publish.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TURBO_TOKEN: ""
|
||||
TURBO_CACHE: local:rw
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
with:
|
||||
ref: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
package-manager-cache: false
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/prepare-release-package
|
||||
- name: Verify qualified package provenance and digest
|
||||
id: package
|
||||
- name: Verify published npm bytes and dist-tag
|
||||
run: node scripts/ci/verified-package.mjs validate .cache/verified-package
|
||||
|
||||
- name: Publish
|
||||
shell: bash
|
||||
env:
|
||||
VERIFIED_PACKAGE_TARBALL: ${{ steps.package.outputs.tarball }}
|
||||
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
|
||||
run: node scripts/ci/npm-release.mjs verify "$VERIFIED_PACKAGE_TARBALL" "$VERIFIED_PACKAGE_VERSION"
|
||||
run: |
|
||||
version="$(node --input-type=module -e 'import packageJson from "./package.json" with { type: "json" }; process.stdout.write(packageJson.version)')"
|
||||
published_version="$(npm view "rea-agents@${version}" version --json 2>/dev/null || true)"
|
||||
if [ "${published_version}" = "\"${version}\"" ]; then
|
||||
echo "rea-agents@${version} is already published; verifying the existing package."
|
||||
else
|
||||
tag="$(node scripts/release-npm-tag.mjs "${version}")"
|
||||
npm publish "${VERIFIED_PACKAGE_TARBALL}" --access public --tag "${tag}"
|
||||
fi
|
||||
|
||||
- name: Verify published CLI from npm
|
||||
env:
|
||||
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
|
||||
run: node scripts/verify-published-package.mjs "$VERIFIED_PACKAGE_VERSION"
|
||||
shell: bash
|
||||
run: |
|
||||
version="$(node --input-type=module -e 'import packageJson from "./package.json" with { type: "json" }; process.stdout.write(packageJson.version)')"
|
||||
verification_root="$(mktemp -d)"
|
||||
trap 'rm -rf -- "${verification_root}"' EXIT
|
||||
cd "${verification_root}"
|
||||
max_attempts=120
|
||||
sleep_seconds=15
|
||||
total_window_minutes=$((max_attempts * sleep_seconds / 60))
|
||||
for attempt in $(seq 1 "${max_attempts}"); do
|
||||
published_version="$(npm view --prefer-online "rea-agents@${version}" version --json 2>/dev/null || true)"
|
||||
if [ "${published_version}" = "\"${version}\"" ]; then
|
||||
echo "rea-agents@${version} is visible on npm after attempt ${attempt}/${max_attempts}; verifying the published CLI."
|
||||
npm exec --yes --package="rea-agents@${version}" -- rea --help
|
||||
npm exec --yes --package="rea-agents@${version}" -- rea --llms
|
||||
node "${GITHUB_WORKSPACE}/scripts/verify-published-package.mjs" "${version}"
|
||||
exit 0
|
||||
else
|
||||
echo "rea-agents@${version} is not visible on npm yet (attempt ${attempt}/${max_attempts}; up to ${total_window_minutes} minutes)" >&2
|
||||
fi
|
||||
if [ "${attempt}" -lt "${max_attempts}" ]; then
|
||||
sleep "${sleep_seconds}"
|
||||
fi
|
||||
done
|
||||
echo "rea-agents@${version} did not become visible on npm within ${total_window_minutes} minutes (${max_attempts} attempts × ${sleep_seconds}s)" >&2
|
||||
echo "This indicates npm registry propagation delay, not a CLI verification failure." >&2
|
||||
exit 1
|
||||
|
||||
publish-mcp:
|
||||
name: Publish to MCP Registry
|
||||
env:
|
||||
TURBO_TOKEN: ""
|
||||
TURBO_CACHE: local:rw
|
||||
needs: [merged-release, release-please, verify-npm]
|
||||
needs: [merged-release, release-please, publish]
|
||||
if: >-
|
||||
always() && !cancelled() && needs.verify-npm.result == 'success' &&
|
||||
always() && !cancelled() && needs.publish.result == 'success' &&
|
||||
((needs.merged-release.result == 'success' && needs.merged-release.outputs.release_created == 'true') ||
|
||||
(needs.release-please.result == 'success' && inputs.phase == 'publish' && needs.release-please.outputs.release_created == 'true'))
|
||||
runs-on: ubuntu-latest
|
||||
@@ -494,4 +400,23 @@ jobs:
|
||||
- name: Verify package and registry metadata versions
|
||||
run: npm run mcp-registry:check
|
||||
|
||||
- uses: ./.github/actions/publish-mcp
|
||||
- name: Install MCP Registry publisher
|
||||
env:
|
||||
MCP_PUBLISHER_VERSION: v1.8.0
|
||||
MCP_PUBLISHER_SHA256_LINUX_AMD64: 1370446bbe74d562608e8005a6ccce02d146a661fbd78674e11cc70b9618d6cf
|
||||
run: |
|
||||
archive="mcp-publisher_linux_amd64.tar.gz"
|
||||
curl -fsSLo "${archive}" \
|
||||
"https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/${archive}"
|
||||
echo "${MCP_PUBLISHER_SHA256_LINUX_AMD64} ${archive}" | sha256sum --check --strict
|
||||
tar xzf "${archive}" mcp-publisher
|
||||
chmod +x mcp-publisher
|
||||
|
||||
- name: Validate MCP Registry metadata
|
||||
run: ./mcp-publisher validate
|
||||
|
||||
- name: Authenticate to MCP Registry
|
||||
run: ./mcp-publisher login github-oidc
|
||||
|
||||
- name: Publish MCP Registry metadata
|
||||
run: ./mcp-publisher publish
|
||||
@@ -17,19 +17,17 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Exercise unavailable-runtime diagnostics and rolling supported releases.
|
||||
# Security floors prevent fallback to an older hosted-toolcache patch.
|
||||
# Exercise unavailable-runtime diagnostics, the first supported release,
|
||||
# and new patches on both supported lines without hand-editing versions.
|
||||
# The repository-pinned Node 24 version also runs the complete test suite.
|
||||
node: ["^22.23.4", "24.22.1", "^24.22.1", "^26.12.1"]
|
||||
node: ["22.x", "24.15.0", "24.x", "26.x"]
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
check-latest: true
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
|
||||
@@ -21,10 +21,6 @@ jobs:
|
||||
python-version: "3.12"
|
||||
cache: pip
|
||||
cache-dependency-path: website/requirements.txt
|
||||
- name: Set up Node.js for the website Worker checks
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Install sharing-image dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
|
||||
@@ -54,10 +54,6 @@ jobs:
|
||||
python-version: "3.12"
|
||||
cache: pip
|
||||
cache-dependency-path: website/requirements.txt
|
||||
- name: Set up Node.js for the website Worker checks
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Install sharing-image dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -79,21 +75,10 @@ jobs:
|
||||
PY
|
||||
- name: Configure Pages
|
||||
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
|
||||
# upload-pages-artifact excludes dot directories, including .well-known.
|
||||
- name: Package the website including agent discovery
|
||||
run: |
|
||||
tar --dereference --hard-dereference --directory website/_site \
|
||||
--exclude=.git --exclude=.github -cf "$RUNNER_TEMP/artifact.tar" .
|
||||
tar -tf "$RUNNER_TEMP/artifact.tar" \
|
||||
./.well-known/ard.json ./.well-known/ai-catalog.json \
|
||||
./.well-known/agent-skills/index.json > /dev/null
|
||||
- name: Upload the website
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
|
||||
with:
|
||||
name: github-pages
|
||||
path: ${{ runner.temp }}/artifact.tar
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
path: website/_site
|
||||
|
||||
deploy:
|
||||
needs: prepare
|
||||
@@ -145,24 +130,14 @@ jobs:
|
||||
PUBLISH_TARGET: ${{ inputs.target }}
|
||||
run: |
|
||||
python3 -u - <<'PY'
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from urllib.error import URLError
|
||||
from urllib.parse import urljoin, urlsplit
|
||||
from urllib.parse import urljoin
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
def open_public(url, accept=None):
|
||||
headers = {
|
||||
"Cache-Control": "no-cache",
|
||||
"User-Agent": "REA-Website-Verifier/1.0 (+https://github.com/morluto/rea)",
|
||||
}
|
||||
if accept:
|
||||
headers["Accept"] = accept
|
||||
return urlopen(Request(url, headers=headers), timeout=10)
|
||||
|
||||
expected = os.environ["GITHUB_SHA"]
|
||||
pages_url = os.environ["PAGES_URL"]
|
||||
if not pages_url:
|
||||
@@ -178,7 +153,11 @@ jobs:
|
||||
for name, base in list(pending.items()):
|
||||
url = urljoin(base, "deployment.json") + "?commit=" + expected
|
||||
try:
|
||||
with open_public(url) as response:
|
||||
request = Request(url, headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"User-Agent": "REA-Website-Verifier/1.0 (+https://github.com/morluto/rea)",
|
||||
})
|
||||
with urlopen(request, timeout=10) as response:
|
||||
actual = json.load(response).get("commit")
|
||||
if actual == expected:
|
||||
print(f"{name} serves {expected}: {base}")
|
||||
@@ -193,27 +172,6 @@ jobs:
|
||||
time.sleep(5)
|
||||
if pending:
|
||||
sys.exit("Could not verify published revision on: " + ", ".join(pending))
|
||||
for name, base in hosts.items():
|
||||
with open_public(urljoin(base, ".well-known/agent-skills/index.json")) as response:
|
||||
skill, = json.load(response)["skills"]
|
||||
# Fetch from each selected host, including the /rea/ mirror.
|
||||
artifact_url = urljoin(base, urlsplit(skill["url"]).path.lstrip("/"))
|
||||
with open_public(artifact_url) as response:
|
||||
digest = "sha256:" + hashlib.sha256(response.read()).hexdigest()
|
||||
if digest != skill["digest"]:
|
||||
sys.exit(name + " serves a skill archive with the wrong digest")
|
||||
for manifest in ("ard.json", "ai-catalog.json"):
|
||||
with open_public(urljoin(base, ".well-known/" + manifest)) as response:
|
||||
if not json.load(response).get("entries"):
|
||||
sys.exit(name + " serves an empty discovery manifest")
|
||||
if name == "Cloudflare":
|
||||
with open_public(base, accept="text/markdown") as response:
|
||||
vary = {value.strip().lower() for value in response.headers.get("Vary", "").split(",")}
|
||||
if response.headers.get_content_type() != "text/markdown" or "accept" not in vary:
|
||||
sys.exit("Cloudflare did not negotiate Markdown with Vary: Accept")
|
||||
if 'rel="ard"' not in response.headers.get("Link", ""):
|
||||
sys.exit("Cloudflare is missing its discovery Link header")
|
||||
print(name + " serves the discovery manifests and verified skill archive")
|
||||
with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as summary:
|
||||
links = " and ".join(f"[{name}]({url})" for name, url in hosts.items())
|
||||
summary.write(f"Published commit `{expected}` to {links}.\n")
|
||||
|
||||
+1
-3
@@ -33,8 +33,6 @@ codedb.snapshot
|
||||
htmlcov/
|
||||
.coverage
|
||||
coverage/
|
||||
.stryker-tmp/
|
||||
reports/mutation/
|
||||
.vitest/
|
||||
.vitest-reports/
|
||||
test-results.xml
|
||||
@@ -50,7 +48,7 @@ Thumbs.db
|
||||
|
||||
# Local reference sources and analysis artifacts
|
||||
/_reference/
|
||||
/experiments/analysis-efficiency/
|
||||
/experiments/analysis-efficiency/results/
|
||||
|
||||
# Generated website download
|
||||
/website/public/examples/notes-example.zip
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"threshold": 4,
|
||||
"threshold": 3.5,
|
||||
"reporters": ["consoleFull"],
|
||||
"ignore": [
|
||||
"node_modules/**",
|
||||
|
||||
+1
-4
@@ -10,10 +10,7 @@
|
||||
"node_modules/**",
|
||||
"CHANGELOG.md",
|
||||
"package-lock.json",
|
||||
".claude-plugin/plugin.json",
|
||||
".codex-plugin/plugin.json",
|
||||
"_reference/**",
|
||||
"third_party/**",
|
||||
".codex-plugin/mcp.json"
|
||||
"third_party/**"
|
||||
]
|
||||
}
|
||||
+3
-95
@@ -1,13 +1,7 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript", "import", "unicorn", "oxc", "vitest"],
|
||||
"plugins": ["typescript", "import", "unicorn", "oxc"],
|
||||
"rules": {
|
||||
"rea/no-buffer-deep-equality": "error",
|
||||
"rea/cancellation-cause": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"no-unreachable": "error",
|
||||
"no-unsafe-optional-chaining": "error",
|
||||
"no-useless-escape": ["warn", { "allowRegexCharacters": ["-"] }],
|
||||
"complexity": ["warn", 20],
|
||||
"max-depth": ["warn", 4],
|
||||
"max-lines": [
|
||||
@@ -29,38 +23,7 @@
|
||||
"max-params": ["warn", 4],
|
||||
"no-var": "warn",
|
||||
"no-var-requires": "error",
|
||||
"typescript/no-import-type-side-effects": "error",
|
||||
"vitest/no-standalone-expect": "off",
|
||||
"vitest/no-conditional-expect": "off",
|
||||
"vitest/expect-expect": "off",
|
||||
"vitest/no-disabled-tests": "off",
|
||||
"vitest/require-mock-type-parameters": "off",
|
||||
"vitest/require-to-throw-message": "off",
|
||||
"import/no-duplicates": [
|
||||
"warn",
|
||||
{
|
||||
"preferInline": false
|
||||
}
|
||||
],
|
||||
"vitest/valid-expect": [
|
||||
"warn",
|
||||
{
|
||||
"maxArgs": 2
|
||||
}
|
||||
],
|
||||
"rea/no-evidence-json-preadmission": "error",
|
||||
"rea/no-positional-tool-contract": "error",
|
||||
"rea/no-fixture-root-cleanup": "error",
|
||||
"typescript/no-floating-promises": "error",
|
||||
"typescript/no-misused-promises": "warn",
|
||||
"typescript/return-await": ["error", "error-handling-correctness-only"],
|
||||
"typescript/switch-exhaustiveness-check": [
|
||||
"error",
|
||||
{
|
||||
"considerDefaultExhaustiveForUnions": true
|
||||
}
|
||||
],
|
||||
"rea/no-shared-module-mock": "error"
|
||||
"typescript/no-import-type-side-effects": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
@@ -73,12 +36,6 @@
|
||||
"no-var-requires": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["src/**/*.ts"],
|
||||
"rules": {
|
||||
"typescript/no-misused-promises": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["tests/**/*.ts", "src/**/*.test.ts", "src/**/*.fixture.ts"],
|
||||
"rules": {
|
||||
@@ -90,8 +47,7 @@
|
||||
"skipBlankLines": true,
|
||||
"skipComments": true
|
||||
}
|
||||
],
|
||||
"typescript/no-misused-promises": "warn"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -135,45 +91,6 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["tests/**/*.test.ts", "src/**/*.test.ts"],
|
||||
"rules": {
|
||||
"vitest/valid-expect": [
|
||||
"warn",
|
||||
{
|
||||
"maxArgs": 2
|
||||
}
|
||||
],
|
||||
"vitest/valid-expect-in-promise": "error",
|
||||
"vitest/no-focused-tests": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"scripts/lib/http3-capture-assertions.test.mjs",
|
||||
"scripts/lib/http3-verifier-lifecycle.test.mjs"
|
||||
],
|
||||
"rules": {
|
||||
"typescript/no-floating-promises": [
|
||||
"error",
|
||||
{
|
||||
"allowForKnownSafeCalls": [
|
||||
{
|
||||
"from": "package",
|
||||
"package": "@types/node",
|
||||
"name": "test"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["scripts/fixtures/javascript-recovery/code-split/entry.mjs"],
|
||||
"rules": {
|
||||
"typescript/no-floating-promises": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
@@ -192,14 +109,5 @@
|
||||
".devin",
|
||||
".factory",
|
||||
"third_party"
|
||||
],
|
||||
"options": {
|
||||
"reportUnusedDisableDirectives": "error"
|
||||
},
|
||||
"jsPlugins": [
|
||||
{
|
||||
"name": "rea",
|
||||
"specifier": "./scripts/lint/index.mjs"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "6.8.0"
|
||||
".": "6.4.0"
|
||||
}
|
||||
@@ -20,18 +20,10 @@ Give each interpretation and resource a clear owner. Consumers must use that own
|
||||
|
||||
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.
|
||||
|
||||
For lifecycle changes, follow [ownership and completion invariants](docs/tool-design.md#own-the-lifecycle) and select the applicable [lifecycle regressions](docs/testing.md#lifecycle-regressions). Include late handshakes, cancellation during final cleanup, mixed child outcomes, and cleanup retry where those stages exist; active-execution cancellation alone does not cover finalization.
|
||||
|
||||
Before adding complexity for a speculative or vanishingly unlikely race or security edge case, explain the concrete failure mode, likelihood, and complexity cost to the user and get their buy-in. Do not silently expand scope for theoretical robustness.
|
||||
|
||||
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.
|
||||
|
||||
After confirming a bug, search sibling adapters, consumers and similar workflows
|
||||
for the same failure. Verify suspected matches, record working comparisons, and
|
||||
group findings by root cause instead of filing duplicate reports.
|
||||
|
||||
When addressing a review thread, inspect the producing boundary and affected consumers for brittleness. Complete directly related refactoring in the same change. Before resolving the thread, explain the root cause, structural fix, validation, and any remaining cleanup.
|
||||
|
||||
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.
|
||||
@@ -57,18 +49,15 @@ Optimize in this order:
|
||||
3. CPU and memory. Avoid unnecessary work and copies; bound concurrency,
|
||||
retained state and serialization growth.
|
||||
|
||||
Reserve performance measurements for changes that target performance or have a
|
||||
concrete regression concern. Routine cleanup uses correctness checks; changes
|
||||
to helper calls, imports, or representations alone do not require synthetic
|
||||
benchmarks. When measurement is warranted, measure representative completed
|
||||
tasks and compare equivalent workflows before and after where available. Report
|
||||
token usage, elapsed time, CPU time and peak process-family memory. Separate cold
|
||||
For changes affecting these costs, measure representative completed tasks and
|
||||
compare equivalent workflows before and after where available. Report token
|
||||
usage, elapsed time, CPU time and peak process-family memory. Separate cold
|
||||
and warm runs, label estimates, and state unmeasured costs. Fewer bytes alone
|
||||
do not establish fewer tokens or faster analysis.
|
||||
|
||||
## Development
|
||||
|
||||
Use [.nvmrc](.nvmrc) and `package.json#packageManager` for the pinned toolchain. Cursor Cloud may place older Node.js first on `PATH`; environment setup installs the pinned toolchain under `/usr/local/bin`. Prefix `PATH="/usr/local/bin:$PATH"` before `node` and `npm`.
|
||||
Use [.nvmrc](.nvmrc) and `package.json#packageManager` for the pinned toolchain. Cursor Cloud may place older Node.js first on `PATH`; environment setup installs the pinned toolchain under `/usr/local/bin`.
|
||||
|
||||
- `npm ci`: install locked dependencies.
|
||||
- `npm run build:cached`: build the runtime and packaged skill.
|
||||
@@ -79,59 +68,16 @@ Use [.nvmrc](.nvmrc) and `package.json#packageManager` for the pinned toolchain.
|
||||
|
||||
Use relevant checks for the change. The complete local gate, `npm run check:pr`, is optional for broad changes or CI diagnosis. See [CONTRIBUTING.md](CONTRIBUTING.md) for development, generated-file ownership, and release conventions.
|
||||
|
||||
When changing installers or setup recovery, follow
|
||||
[Checking installers and publisher downloads](docs/testing.md#checking-installers-and-publisher-downloads).
|
||||
|
||||
Coordinate expensive checks across agents. If a test unexpectedly consumes heavy CPU, stop its owned process family and profile a bounded representative case before retrying. Inspect production work, fixture generation, subprocesses, and repeated setup; preserve regression coverage when optimizing. Follow the [test resource guidance](docs/testing.md#developer-commands).
|
||||
|
||||
`docs/public/product-catalog.json`, `docs/verification/managed-conformance-*.json`, and `skills/` are ignored outputs. Edit source contracts and `.agents/skills/reverse-engineer-anything/`, then regenerate as needed. `.cache/mcp-tool-catalog.json` is test metadata; runtime builds and source checking do not consume it. Never commit binaries, provider project documents, credentials, `dist/`, `node_modules/`, or local planning artifacts.
|
||||
|
||||
## Tool and Test Changes
|
||||
|
||||
For tool design, follow [docs/tool-design.md](docs/tool-design.md): start from the analyst question, reuse existing contracts, preserve caller choices, and return useful evidence inline. Limits must follow real format, protocol, authority, or resource constraints. Account for representation expansion before allocation; derive completeness from examined coverage.
|
||||
|
||||
For user- or agent-facing tool problems, prioritize fixing the owning behavior,
|
||||
contract, and actionable result or recovery at the point of use. Document the
|
||||
result in product docs when explanation is needed. Add skill guidance only for
|
||||
workflow judgment or context that the tool cannot reasonably convey; do not use
|
||||
a skill as the primary repair for a discoverability or contract defect.
|
||||
|
||||
For test changes or provider claims, use [docs/testing.md](docs/testing.md). Select applicable [boundary regressions](docs/testing.md#boundary-regressions) for parser, inventory, snapshot, and projection fixes. Prefer real public CLI/MCP workflows, then production-boundary integration, then producer goldens. Keep module tests for distinct semantics stronger workflows cannot reliably reproduce. Preserve malformed-input, cancellation, cleanup, permission, capacity, and target-identity coverage when pruning. Report unverified coverage explicitly; simulated providers do not establish real-provider behavior.
|
||||
For test changes or provider claims, use [docs/testing.md](docs/testing.md). Prefer real public CLI/MCP workflows, then production-boundary integration, then producer goldens. Keep module tests for distinct semantics stronger workflows cannot reliably reproduce. Preserve malformed-input, cancellation, cleanup, permission, capacity, and target-identity coverage when pruning. Report unverified coverage explicitly; simulated providers do not establish real-provider behavior.
|
||||
|
||||
Use Conventional Commit subjects and PR titles. Mark breaking changes with `!` or a `BREAKING CHANGE:` footer. Describe behavior changes, relevant validation, and real-provider verification scope.
|
||||
|
||||
## Issue Reports
|
||||
|
||||
Follow [issue reporting guidance](CONTRIBUTING.md#reporting-issues) and the matching
|
||||
issue form. Keep reports concise and cite the smallest useful evidence.
|
||||
Distinguish observed results, source-derived reasoning, hypotheses, and proposed
|
||||
checks. Never claim runtime reproduction or measurements you did not perform.
|
||||
Use `NOT_ENOUGH_INFO` with an explanation for missing narrative answers or evidence.
|
||||
Incomplete reports, source-only findings, and early feature or design ideas are
|
||||
welcome when their limitations are explicit; a completed diagnosis or fix is not
|
||||
required to open an issue.
|
||||
|
||||
## Pull Request Readiness
|
||||
|
||||
Follow [human ownership and readiness](CONTRIBUTING.md#pull-request-ownership-and-readiness)
|
||||
when preparing a PR. AI-assisted contributions are welcome; a human must review
|
||||
and own the final diff, check the validation evidence and its limitations, and
|
||||
explicitly declare the PR ready for initial maintainer review.
|
||||
|
||||
Keep the PR body concise and current, with the concrete problem, observable
|
||||
outcome, approach, and evidence. Report only checks that actually ran as executed;
|
||||
distinguish source-derived conclusions, proposed checks, and unavailable coverage.
|
||||
Address actionable automated review findings and explain disagreements.
|
||||
|
||||
Leave the template's **Author readiness** declarations unchecked for the human
|
||||
owner to complete. Never attest to human review on their behalf. Draft status
|
||||
is optional; preserve the PR's current draft status unless the user asks to change
|
||||
it. Later pushes do not invalidate the readiness declaration or require another
|
||||
human sign-off. Keep the PR body and affected validation current as work changes.
|
||||
The readiness check validates the filled declaration or the author exemption
|
||||
listed in CONTRIBUTING.md; CI and automated review results do not establish that
|
||||
human review occurred, maintainer scope approval, or merge permission.
|
||||
|
||||
<!-- BEGIN:turborepo-agent-rules -->
|
||||
|
||||
# This is NOT the Turborepo you know
|
||||
|
||||
+2
-305
@@ -1,308 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [6.8.0](https://github.com/morluto/rea/compare/rea-agents-6.7.0...rea-agents-6.8.0) (2026-10-25)
|
||||
|
||||
### Highlights
|
||||
|
||||
- Build local agent environments from network captures, browser scenarios, or a prompt, then serve derived reads and evidence-gated writes and compare them with fresh application captures.
|
||||
- Inspect more shipped formats, including GPU code objects, BEAM, Python bytecode, Unity IL2CPP metadata, Godot PCK, PyInstaller, Node.js SEA, devicetree, and additional PE/ELF tables.
|
||||
- Keep large investigations focused with retained summaries, selected fact queries, configurable recovery limits, and row-oriented CLI exports. Complete Evidence remains available.
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
- **Function analysis:** `analyze_function` now defaults to a retained summary. Set `detail: "complete"` for the previous inline dossier; the CLI retains its complete default and requires `--evidence_output` when selecting a summary. [#5660](https://github.com/morluto/rea/pull/5660). Thanks @CryptoJones, @gensokyo-dev, @RishTheFish21.
|
||||
- **Accessibility:** Accept JSON numbers and booleans in node `value`, alongside strings and null. Update consumers that previously assumed text-only values. [cc90771](https://github.com/morluto/rea/commit/cc90771af14f045538651615147d8bf86b576549), [#6759](https://github.com/morluto/rea/pull/6759), [#6467](https://github.com/morluto/rea/issues/6467). Thanks @coygeek, @gensokyo-dev, @mvanhorn.
|
||||
- **JSON Lines:** `--format jsonl` now emits object-collection rows followed by a `$rea: "jsonl/v1"`, `type: "metadata"` record; scalar selections and unsupported reports are refused. Parse control records separately, select a single supported collection when necessary, and use `--format json` for the previous whole-document output. See [JSON Lines framing](https://github.com/morluto/rea/blob/rea-agents-6.8.0/docs/cli.md#json-lines-record-output). [#5629](https://github.com/morluto/rea/pull/5629), [#5487](https://github.com/morluto/rea/issues/5487). Thanks @gensokyo-dev, @jayykernel, @vijaykiran06.
|
||||
- **Interface Builder:** Keyed-archive graph identities use decimal table UIDs instead of authored `objectID`/`object-id`. Treat authored IDs as attributes and update persisted graph lookups; ibtool and native NIBArchive identities keep their source semantics. [6fbe67d](https://github.com/morluto/rea/commit/6fbe67d66e3d716db69929503a3a232a7ada0860).
|
||||
- **Frida:** `instrument-with-frida` JSON returns the canonical Evidence directly. Read `normalized_result.cleanup_error` instead of the former top-level wrapper field. [48f700e](https://github.com/morluto/rea/commit/48f700ee35e0988ed973e5486dc9db33f2a5d944).
|
||||
- **Reconstruction proof:** ID-array proof records no longer establish verified or ready ledgers. Supply implementation/revision bindings and individual claim outcomes; handle inapplicable, unavailable, and failed proof diagnostics. [f8b65e7](https://github.com/morluto/rea/commit/f8b65e78723b1e2169f94dd1226198ea3139707e).
|
||||
- **Mach-O imports:** Handle a null import `name` when identity is ambiguous; use `raw_name` and `name_status` to distinguish observed expressions from exact symbols. [5d0d243](https://github.com/morluto/rea/commit/5d0d243a45c9132e1aff75d454ab53d86a74cc31), [169512a](https://github.com/morluto/rea/commit/169512a35bb015ff209168fb8bc1c9b31a2c678b).
|
||||
- **Node.js:** Use a runtime satisfying `^22.23.4 || ^24.22.1 || >=26.12.1`; Node 23 and 25 are unsupported. Upgrade Node before installing or updating REA; the installer does not upgrade it. [#6315](https://github.com/morluto/rea/pull/6315), [#6283](https://github.com/morluto/rea/issues/6283). Thanks @GamerDubz, @gensokyo-dev, @ShikamaruNaaraa.
|
||||
- **Browser storage:** Historical hashed storage remains readable, but comparisons stay unknown until both captures are recaptured in the new representation. [#6341](https://github.com/morluto/rea/pull/6341), [#6289](https://github.com/morluto/rea/issues/6289). Thanks @coygeek, @gongfpp.
|
||||
- **Environment serving:** `environment-serve` serves derived state by default, including write tools whose effects are confirmed by recorded evidence. Use `--replay-only` for stateless replay of manifest tools. Read stop-summary `state` instead of `state_evidence_id`. [#5351](https://github.com/morluto/rea/pull/5351), [#6158](https://github.com/morluto/rea/pull/6158). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **MCP schemas:** Input advertisements default to compact. Set `REA_MCP_INPUT_SCHEMA_PROFILE=full` for complete advertised guidance and nested constraints. [#5975](https://github.com/morluto/rea/pull/5975). Thanks @gensokyo-dev.
|
||||
- **Browser console arguments:** Accept null `argument_types` and `text_capture.status: "unavailable"` when the protocol argument inventory is omitted or malformed; preserve that uncertainty. [#6016](https://github.com/morluto/rea/pull/6016), [#5934](https://github.com/morluto/rea/issues/5934). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Reconstruction summaries:** `summary.by_evidence_authority` is keyed by Evidence authority; read the former source-state counts from required `summary.by_source_state`. Regenerate ledgers whose closure digests depended on the old projection. [#5875](https://github.com/morluto/rea/pull/5875), [#5838](https://github.com/morluto/rea/issues/5838). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Swift bindings:** Conformance `type_name` and `protocol_name` may be null; consume typed `type_binding`/`protocol_binding` evidence. Replace the former Objective-C-specific import-binding type/schema with `MachoImportBinding`. [d0067ba](https://github.com/morluto/rea/commit/d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f).
|
||||
- **WebSocket frames:** Accept null opcodes and preserve unknown frame facts; numeric opcodes are restricted to the four-bit protocol range. [0bc2b42](https://github.com/morluto/rea/commit/0bc2b42001f684beffcf47b70e507443b1a5d320).
|
||||
- **Resource-only inspection:** Evidence subjects and executable digests may be null. Use `resource_root` and each resource's reported provenance instead of requiring an executable target. [6a6adf7](https://github.com/morluto/rea/commit/6a6adf703fa44100643947d20fbc670f86260b1b).
|
||||
- **Browser timestamps:** Console and session-timeline timestamps may be null when the protocol supplies no finite/comparable time. Keep unknown time explicit in sorting and comparisons. [690fc51](https://github.com/morluto/rea/commit/690fc518540bae982038a74f47ef7781201785aa), [2a0410f](https://github.com/morluto/rea/commit/2a0410f1717b3126e8a95063d77dea66a597010a).
|
||||
- **Reference sources:** Read failed root enumeration from `rootFailure` rather than a synthetic failed child at `"."`; preserve the top-level source-graph limitation. [6296fdf](https://github.com/morluto/rea/commit/6296fdf560f1ac4aa55985558edaa98300aacdbc).
|
||||
- **Filesystem capture:** Accept `entry_unavailable` and `identity_changed` enumeration omission reasons in addition to the previous reason literals. [c85b44e](https://github.com/morluto/rea/commit/c85b44e1b14851c54e65327a4807f79ad5cd5a61). Thanks @rudrakshagarwal4200-ai.
|
||||
- **APK locales:** Replace numeric legacy selectors such as `es-r419` with `es-419` or `b+es+419`. Two-letter regions remain supported. [29910d1](https://github.com/morluto/rea/commit/29910d16b98fe4b607a7aa15928789a73614b0b9). Thanks @mertkayacs.
|
||||
- **XML plist scalars:** Recognize `$plist_type: "unknown"` and read `element`, `literal`, and `reason`; unresolved XML scalars use this wrapper instead of the former real/null unknown marker or malformed scalar prefixes. Supported extended UTC years preserve their timestamp. [3537e58](https://github.com/morluto/rea/commit/3537e58c1ab29309f06eabf50bc52adba9817a0c).
|
||||
|
||||
### Changes
|
||||
|
||||
- **Binary layouts:** Inspect ELF32 i386, little-endian ARM, and little- or big-endian MIPS `EXEC`/`DYN`/`REL` layouts on Linux x64 using a caller-supplied pwntools environment. [#7052](https://github.com/morluto/rea/pull/7052), [#6934](https://github.com/morluto/rea/issues/6934). Thanks @roblennon, @salmanalmutairi1.
|
||||
- **Agent environments:** Project network captures and Evidence files into manifests, preserve template path pairs and positions, export AgentEnv bundles, and serve manifests as replay environments. [#4954](https://github.com/morluto/rea/pull/4954), [#5293](https://github.com/morluto/rea/pull/5293), [#5664](https://github.com/morluto/rea/pull/5664), [#4999](https://github.com/morluto/rea/pull/4999), [#4975](https://github.com/morluto/rea/pull/4975). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **Capture to environment:** Build environments from browser scenarios or a prompt, extract the initial observed state, and compare the environment with application probe captures. [#5182](https://github.com/morluto/rea/pull/5182), [#6946](https://github.com/morluto/rea/pull/6946), [#5319](https://github.com/morluto/rea/pull/5319), [#5699](https://github.com/morluto/rea/pull/5699). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **State-backed tools:** Render read filters, search, sorting and paging from state; apply confirmed write effects with broader state targets and recording-based write admission. [#5352](https://github.com/morluto/rea/pull/5352), [293832b](https://github.com/morluto/rea/commit/293832bafc9a054914ec514fe906dd4936c7a372), [#5800](https://github.com/morluto/rea/pull/5800), [#5787](https://github.com/morluto/rea/pull/5787), [#5600](https://github.com/morluto/rea/pull/5600), [5375ff0](https://github.com/morluto/rea/commit/5375ff0f9aadb4fd0d46333815aa0431e3df1010), [#6169](https://github.com/morluto/rea/pull/6169), [#6385](https://github.com/morluto/rea/pull/6385), [#6897](https://github.com/morluto/rea/pull/6897). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **State learning:** Model application-assigned values, preserve view-specific clock spellings, learn write clocks from scenario timing, derive fields, and learn creates across sorted arrays and referenced entities. [7ec9b6e](https://github.com/morluto/rea/commit/7ec9b6e56e1b3762285dca881db6d900a389ab9c), [#6136](https://github.com/morluto/rea/pull/6136), [957bcfa](https://github.com/morluto/rea/commit/957bcfa149c1a0593bb0aa2466cc56d1d4f62e7d), [#6138](https://github.com/morluto/rea/pull/6138), [#6967](https://github.com/morluto/rea/pull/6967), [#7025](https://github.com/morluto/rea/pull/7025), [#7034](https://github.com/morluto/rea/pull/7034). Thanks @earakely-scale.
|
||||
- **Recorded writes:** Serve creates whose compatible read-backs add omitted fields or express timestamps at a different precision, and whole-object updates that regenerate observed slugs or modification times. Conflicting observations still withhold writes. State views and bundles carrying the new read-back data need REA 6.8.0 or later to serve. [#7080](https://github.com/morluto/rea/pull/7080). Thanks @earakely-scale.
|
||||
- **Native investigations:** Page native inventory summaries, match functions across builds, export pseudocode to resumable directories, and select objdump disassembly by symbol. [#5378](https://github.com/morluto/rea/pull/5378), [#5390](https://github.com/morluto/rea/pull/5390), [#6170](https://github.com/morluto/rea/pull/6170), [#5171](https://github.com/morluto/rea/pull/5171), [#4249](https://github.com/morluto/rea/issues/4249), [#5343](https://github.com/morluto/rea/issues/5343), [#5159](https://github.com/morluto/rea/issues/5159). Thanks @coygeek, @CryptoJones, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **Engine workflows:** Bound a single Ghidra decompilation without closing its session, analyze GameCube/Wii DOL executables, and inspect static CUDA/HIP code objects. [#5662](https://github.com/morluto/rea/pull/5662), [#6240](https://github.com/morluto/rea/pull/6240), [#5813](https://github.com/morluto/rea/pull/5813), [#5340](https://github.com/morluto/rea/issues/5340), [#5345](https://github.com/morluto/rea/issues/5345). Thanks @arielfikru, @coygeek, @gensokyo-dev, @RishTheFish21.
|
||||
- **PE/ELF:** Inspect export tables without an engine, PE imports/delay-loads, Authenticode certificates, TLS callbacks/load configuration, ELF notes, Linux kernel module metadata, and Zstandard section-name tables. [#6278](https://github.com/morluto/rea/pull/6278), [#6509](https://github.com/morluto/rea/pull/6509), [#4830](https://github.com/morluto/rea/pull/4830), [#7008](https://github.com/morluto/rea/pull/7008), [#4837](https://github.com/morluto/rea/pull/4837), [#7010](https://github.com/morluto/rea/pull/7010), [#6987](https://github.com/morluto/rea/pull/6987), [#4057](https://github.com/morluto/rea/issues/4057). Thanks @gensokyo-dev, @kaoru0822-kitauji, @mvanhorn, @Rinkia, @ShuaixinHuang.
|
||||
- **Apple/native metadata:** Inspect IPS crash reports, decode Swift nominal declarations, and select universal Mach-O slices in Go/Rust metadata readers. [#6511](https://github.com/morluto/rea/pull/6511), [#6257](https://github.com/morluto/rea/pull/6257), [#6219](https://github.com/morluto/rea/pull/6219), [#5341](https://github.com/morluto/rea/issues/5341), [#4102](https://github.com/morluto/rea/issues/4102). Thanks @coygeek, @gensokyo-dev, @kaoru0822-kitauji, @mvanhorn, @Rinkia.
|
||||
- **Packaged applications:** Triage desktop installation directories and inspect/extract Godot PCK, PyInstaller archives, and Node.js single-executable application blobs. [#5207](https://github.com/morluto/rea/pull/5207), [#3634](https://github.com/morluto/rea/pull/3634), [afa5da7](https://github.com/morluto/rea/commit/afa5da745e0b139e68029a96b776ae30e48ea2b2), [#2433](https://github.com/morluto/rea/pull/2433), [#7000](https://github.com/morluto/rea/pull/7000), [#4399](https://github.com/morluto/rea/issues/4399). Thanks @0xthearchitect, @coygeek, @gensokyo-dev, @mvanhorn.
|
||||
- **Unity and managed code:** Inventory IL2CPP images/types/methods and metadata versions 24/27; read member names and literals, link native methods and generic instances, and export Ghidra seeds. Project managed call/field-access edges and page managed member summaries. [#3807](https://github.com/morluto/rea/pull/3807), [#5326](https://github.com/morluto/rea/pull/5326), [52d1a72](https://github.com/morluto/rea/commit/52d1a72af047a73b787b8935b032786a34966036), [#5327](https://github.com/morluto/rea/pull/5327), [#5785](https://github.com/morluto/rea/pull/5785), [#6260](https://github.com/morluto/rea/pull/6260), [#5167](https://github.com/morluto/rea/pull/5167), [#3784](https://github.com/morluto/rea/issues/3784), [#5112](https://github.com/morluto/rea/issues/5112), [#4056](https://github.com/morluto/rea/issues/4056), [#5148](https://github.com/morluto/rea/issues/5148). Thanks @8rulerstar, @alexlanghart, @elricfd, @gensokyo-dev, @jaelliot, @kaoru0822-kitauji, @Rinkia.
|
||||
- **Android:** Inspect APK signing declarations and structured manifest components; report static-final DEX field values. [e93e3b1](https://github.com/morluto/rea/commit/e93e3b1e7d6e7e4f49c2de24b92452ec29e853f8), [#4158](https://github.com/morluto/rea/pull/4158), [#5025](https://github.com/morluto/rea/pull/5025), [#6981](https://github.com/morluto/rea/pull/6981), [#4537](https://github.com/morluto/rea/issues/4537), [#6397](https://github.com/morluto/rea/issues/6397). Thanks @Abelo9996, @BaaRai, @FerroLx, @gensokyo-dev, @sserrgo12.
|
||||
- **Bytecode and firmware:** Inspect and query CPython pyc and BEAM facts, inspect flattened devicetrees, and attribute MMIO peripheral regions in native disassembly. [#4826](https://github.com/morluto/rea/pull/4826), [#6928](https://github.com/morluto/rea/pull/6928), [#4833](https://github.com/morluto/rea/pull/4833), [#6943](https://github.com/morluto/rea/pull/6943), [#7009](https://github.com/morluto/rea/pull/7009), [#5348](https://github.com/morluto/rea/pull/5348), [#6922](https://github.com/morluto/rea/issues/6922), [#6938](https://github.com/morluto/rea/issues/6938). Thanks @gensokyo-dev, @liziye627-design, @mvanhorn.
|
||||
- **JavaScript:** Retain queryable static route inventories, link assignment values to writes, and accept source selections as a JSON array or file. [#6019](https://github.com/morluto/rea/pull/6019), [#6206](https://github.com/morluto/rea/pull/6206), [#5029](https://github.com/morluto/rea/pull/5029), [#4772](https://github.com/morluto/rea/issues/4772), [#6193](https://github.com/morluto/rea/issues/6193). Thanks @gensokyo-dev, @IvenKooLab, @jacobmalibiran, @JoshOsullivan-au, @kaoru0822-kitauji, @KJLavender.
|
||||
- **Data inspection:** Decode selected protobuf/gRPC bodies, inspect GGUF/safetensors declarations, and produce byte-entropy profiles. [#5031](https://github.com/morluto/rea/pull/5031), [#5603](https://github.com/morluto/rea/pull/5603), [#2331](https://github.com/morluto/rea/pull/2331), [#4986](https://github.com/morluto/rea/issues/4986), [#3323](https://github.com/morluto/rea/issues/3323). Thanks @AayushJB03, @gensokyo-dev, @kaoru0822-kitauji, @KJLavender, @michaelkillgta, @mvanhorn.
|
||||
- **CLI input/output:** Accept structured request documents on stdin and expose version/runtime output formats. Row-oriented JSONL exports use the migration above. [#7040](https://github.com/morluto/rea/pull/7040), [#5486](https://github.com/morluto/rea/issues/5486), [#6530](https://github.com/morluto/rea/pull/6530), [169512a](https://github.com/morluto/rea/commit/169512a35bb015ff209168fb8bc1c9b31a2c678b), [#5483](https://github.com/morluto/rea/issues/5483). Thanks @Abelo9996, @ahmed-husssain, @CassieFox23, @DeryFerd, @eco-null, @ezrabrilliant, @GamerDubz, @gensokyo-dev, @gongfpp, @hifzabuildsai, @jayykernel, @mhamzasakhi, @pablodiazjorge, @Plastic-time, @raidnouredden-ux, @SandstromPL, @ShuaixinHuang, @Sriram-PR, @XiaoBinGan.
|
||||
- **MCP and setup:** Add opt-in Streamable HTTP and FreeBuff setup registration. FreeBuff support first appears in 6.8.0; installed native FreeBuff loading and skill discovery remain unverified. [#4955](https://github.com/morluto/rea/pull/4955), [#2736](https://github.com/morluto/rea/pull/2736), [#3243](https://github.com/morluto/rea/issues/3243). Thanks @0xLLVEL, @codebuff-team, @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **Recovery controls:** Adjust per-call WASM/SQLite/firmware deadlines, MCP startup budgets, regex matching deadlines/worker heap, capture decoder heap, ASAR header admission, Apple dispatch input, and the bridge request ceiling. MCP runtime limit recovery reports actionable settings. [#5798](https://github.com/morluto/rea/pull/5798), [#5818](https://github.com/morluto/rea/pull/5818), [aa53c06](https://github.com/morluto/rea/commit/aa53c065387c58b4189d82c47f1f3445fc505e30), [#5863](https://github.com/morluto/rea/pull/5863), [#6304](https://github.com/morluto/rea/pull/6304), [#6267](https://github.com/morluto/rea/pull/6267), [#6301](https://github.com/morluto/rea/pull/6301), [#6299](https://github.com/morluto/rea/pull/6299), [#6209](https://github.com/morluto/rea/pull/6209), [#6233](https://github.com/morluto/rea/pull/6233), [#6376](https://github.com/morluto/rea/pull/6376), [#7049](https://github.com/morluto/rea/pull/7049), [#5797](https://github.com/morluto/rea/issues/5797), [#5812](https://github.com/morluto/rea/issues/5812), [#5851](https://github.com/morluto/rea/issues/5851), [#6176](https://github.com/morluto/rea/issues/6176), [#3917](https://github.com/morluto/rea/issues/3917). Thanks @gensokyo-dev, @jaychou813, @kaoru0822-kitauji.
|
||||
- **APK output:** Set `string_tables_only` or CLI `--string-tables-only` to omit duplicate primary strings and avoid their output-capacity charge. [7c458f0](https://github.com/morluto/rea/commit/7c458f04b9155707c8ba406f8b455b10016f1b99), [#4990](https://github.com/morluto/rea/pull/4990), [#4982](https://github.com/morluto/rea/issues/4982). Thanks @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Retained analysis:** Reuse successful validation of authenticated immutable native/imported-JavaScript parents and captures. Query retained Go build identity, Rust dependencies, JavaScript source-to-bundle mappings, and native export facts without repeating full output. [8f7d12e](https://github.com/morluto/rea/commit/8f7d12e30d63f3d3b4cfe2a90336362841438f56), [#4962](https://github.com/morluto/rea/pull/4962), [#5349](https://github.com/morluto/rea/pull/5349), [#5524](https://github.com/morluto/rea/pull/5524), [#5815](https://github.com/morluto/rea/pull/5815), [#6128](https://github.com/morluto/rea/pull/6128), [#6907](https://github.com/morluto/rea/pull/6907), [#4810](https://github.com/morluto/rea/issues/4810), [#5499](https://github.com/morluto/rea/issues/5499), [#5802](https://github.com/morluto/rea/issues/5802), [#6112](https://github.com/morluto/rea/issues/6112), [#6900](https://github.com/morluto/rea/issues/6900). Thanks @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Compact results:** Use retained Frida/GDB/objdump summaries, omit redundant Ghidra raw results, and defer the analysis runtime during MCP catalog discovery. [#5661](https://github.com/morluto/rea/pull/5661), [#5614](https://github.com/morluto/rea/pull/5614), [#5355](https://github.com/morluto/rea/pull/5355), [#4424](https://github.com/morluto/rea/pull/4424), [#5640](https://github.com/morluto/rea/issues/5640), [#5584](https://github.com/morluto/rea/issues/5584), [#5337](https://github.com/morluto/rea/issues/5337). Thanks @coygeek, @gensokyo-dev, @kaoru0822-kitauji, @leeguooooo.
|
||||
- **Analysis work:** Reuse Hopper literal-string records, JavaScript workers and captured prefix selections, Interface Builder validation and plist scalar schemas; bound stable reads to one snapshot buffer and avoid decoding omitted GGUF strings. [#5017](https://github.com/morluto/rea/pull/5017), [#5819](https://github.com/morluto/rea/pull/5819), [31de177](https://github.com/morluto/rea/commit/31de177404461d122761d8243abe4fd24564f4da), [#5885](https://github.com/morluto/rea/pull/5885), [85e38ac](https://github.com/morluto/rea/commit/85e38ac199dff15e8c43343d9170db7e0e46d284), [#6966](https://github.com/morluto/rea/pull/6966), [#5781](https://github.com/morluto/rea/pull/5781), [#5010](https://github.com/morluto/rea/issues/5010), [#5855](https://github.com/morluto/rea/issues/5855), [#6954](https://github.com/morluto/rea/issues/6954), [#5774](https://github.com/morluto/rea/issues/5774). Thanks @gensokyo-dev, @kaoru0822-kitauji.
|
||||
- **Native scheduling:** Batch modern IDA callee lookups, schedule decompilation reads on demand, index NativeAOT type closure, and retain call-path ancestry once per visited node. [0c6fed9](https://github.com/morluto/rea/commit/0c6fed90b74c75a9fd23d1ec1b510a7274f85887), [06fe7ee](https://github.com/morluto/rea/commit/06fe7eecbc8c387414b4c1aa0d88ad6314f54f0c), [ce61680](https://github.com/morluto/rea/commit/ce61680c04c95a9cdfad5335632a99542af26d37), [5e4ec0f](https://github.com/morluto/rea/commit/5e4ec0fa1b678aa3bf20704ec9110224ba8c1676).
|
||||
- **Selected processing:** Prepare CLI positional schemas only for the selected command, separate entropy reads from report resolution, prune typed-array reference escapes, index state-write cutoffs, decode only selected replay query values, and skip payload text during WAT framing. [315b234](https://github.com/morluto/rea/commit/315b23403f0819967878cee418c3f9cb4b1a69d9), [#6950](https://github.com/morluto/rea/pull/6950), [#5139](https://github.com/morluto/rea/pull/5139), [#5350](https://github.com/morluto/rea/pull/5350), [96ea32b](https://github.com/morluto/rea/commit/96ea32b7e4e28d3c9cd79a1de4b8e1ba3b4244e9), [70835d3](https://github.com/morluto/rea/commit/70835d3d5f030deadf606e1c79c70a60883e50c9), [#5766](https://github.com/morluto/rea/pull/5766), [#6947](https://github.com/morluto/rea/issues/6947), [#5760](https://github.com/morluto/rea/issues/5760). Thanks @galleonli, @gensokyo-dev, @kaoru0822-kitauji.
|
||||
|
||||
### Fixes
|
||||
|
||||
- **IPS report fields:** Preserve the recorded terminating process and PID, termination flags, parent PID, and build/platform metadata. [#7079](https://github.com/morluto/rea/pull/7079), [#7078](https://github.com/morluto/rea/issues/7078). Thanks @rodricksz4h5.
|
||||
- **Ghidra language selection:** Accept stock language IDs containing `LEBE` or slash-separated processor names, and report conflicting DOL language overrides as configuration errors naming `REA_GHIDRA_LANGUAGE_ID`. [#7070](https://github.com/morluto/rea/pull/7070), [#7072](https://github.com/morluto/rea/issues/7072), [#7081](https://github.com/morluto/rea/pull/7081), [#7082](https://github.com/morluto/rea/issues/7082). Thanks @Adolanium.
|
||||
- **Setup and Hopper:** Preserve the Linux embedding runtime, supported x86_64 host restriction, readiness-based provider choices, orderly demo-helper shutdown, and installed apt dependency versions. Keep cleanup and skill publication ownership through retries. [#5041](https://github.com/morluto/rea/pull/5041), [#3422](https://github.com/morluto/rea/pull/3422), [f5d1012](https://github.com/morluto/rea/commit/f5d1012fabfe99af82523c3078ce9d93eb3780c1), [#4966](https://github.com/morluto/rea/pull/4966), [#6974](https://github.com/morluto/rea/pull/6974), [211e353](https://github.com/morluto/rea/commit/211e3533b34f81ae0910ad4f53f2a3d3c8587d3e), [#6920](https://github.com/morluto/rea/pull/6920), [#5015](https://github.com/morluto/rea/issues/5015), [#3419](https://github.com/morluto/rea/issues/3419), [#4921](https://github.com/morluto/rea/issues/4921), [#6975](https://github.com/morluto/rea/issues/6975). Thanks @Abel-I-Akhigbe, @Adolanium, @DavidFB-creator, @DeryFerd, @gensokyo-dev, @kaoru0822-kitauji, @sx4im.
|
||||
- **Client setup:** Treat empty APPDATA as unset, distinguish MSIX Claude Desktop process views, preserve aliased Hermes server tables, reuse client dialect/alias bindings, and materialize the pinned package-runner cache before the first start. [#5126](https://github.com/morluto/rea/pull/5126), [#4903](https://github.com/morluto/rea/pull/4903), [#3785](https://github.com/morluto/rea/pull/3785), [b7eeded](https://github.com/morluto/rea/commit/b7eededf5af5061a834f93f57f2c888acf870344), [#7032](https://github.com/morluto/rea/pull/7032), [#5104](https://github.com/morluto/rea/issues/5104), [#4278](https://github.com/morluto/rea/issues/4278), [#3320](https://github.com/morluto/rea/issues/3320), [#7027](https://github.com/morluto/rea/issues/7027). Thanks @CmsChase, @coygeek, @myso-kr, @pablodiazjorge, @vaidehi-snk, @wxj006007.
|
||||
- **Target admission:** Preserve thin arm64_32 Mach-O support, distinguish JVM identity before FAT admission, validate FAT format/CPU identity, preserve original DOS header padding, and reject NULs in local objdump/rizin paths. [#5788](https://github.com/morluto/rea/pull/5788), [6843dbe](https://github.com/morluto/rea/commit/6843dbea9bce4709ee2e8bf46c38f0bbf640716b), [497037e](https://github.com/morluto/rea/commit/497037e6e3975cbb37cfd89ea0ad300faa2a4769), [#7013](https://github.com/morluto/rea/pull/7013), [#3600](https://github.com/morluto/rea/pull/3600), [#5713](https://github.com/morluto/rea/issues/5713), [#6510](https://github.com/morluto/rea/issues/6510). Thanks @coygeek, @dhruvac29, @kaoru0822-kitauji, @Lew1sWong, @qorvr.
|
||||
- **Mach-O and dylibs:** Preserve container framing, facts when dyld rejects a missing UUID, verified-signature snapshot identity, unsigned diagnostics, absolute paths inside the selected root, legacy platform rules, lazy-load payloads, and reasons for rejecting present dylibs. [cf3288d](https://github.com/morluto/rea/commit/cf3288d8cf7a09685acf89103b36c32359be56fa), [2b263a7](https://github.com/morluto/rea/commit/2b263a7fe9e9d4c185c7d6af6b4e67b21a73af30), [eb7acc7](https://github.com/morluto/rea/commit/eb7acc70bf06f119e8639e30a78081d5ba82b0bf), [1502cfb](https://github.com/morluto/rea/commit/1502cfbc8ca0d1d1d86eebf334018dfb418bbac0), [90062c0](https://github.com/morluto/rea/commit/90062c092228dea9d33fadc0d7e1b968105b8b2c), [#5650](https://github.com/morluto/rea/pull/5650), [3b67b36](https://github.com/morluto/rea/commit/3b67b362aa2cd79eac5af57d0965e81a7d0b8c2b), [#5551](https://github.com/morluto/rea/issues/5551). Thanks @coygeek.
|
||||
- **Native facts:** Admit direct dispatch-metadata providers; preserve record-local Objective-C omissions, snapshot profiles, class-import binding identity, stripped class labels, version-like names, and qualified Swift protocol identities. [50b6975](https://github.com/morluto/rea/commit/50b6975d861069e2c6cfa202780621449f863802), [9eec57b](https://github.com/morluto/rea/commit/9eec57b469087fe9bff3d29dcc02f47074159e91), [0ba4d6f](https://github.com/morluto/rea/commit/0ba4d6f43ad610dacc861a1548be1a5c4712cd61), [4d7de00](https://github.com/morluto/rea/commit/4d7de00d4cc9a95ec2b7a3123b04e01db2daafa0), [22c84ce](https://github.com/morluto/rea/commit/22c84ce64aa6c7601aa81af86d4117b5a4c2feac), [4852c83](https://github.com/morluto/rea/commit/4852c830af1e3b9df437235b7a8fa8970a6d1c59), [aa9043f](https://github.com/morluto/rea/commit/aa9043f6b61c453e4735795cfe6a559bf6b6cc16).
|
||||
- **Apple resources:** Preserve independent plist scalars, processing instructions, nonstandard table widths, unsupported NIB connector coverage, and strict IPS JSON surrogate validation; classify Interface Builder objects by their declared superclass chains. [fd728f2](https://github.com/morluto/rea/commit/fd728f271e7de7bcfa63890b573715720d5f948c), [#5806](https://github.com/morluto/rea/pull/5806), [4526862](https://github.com/morluto/rea/commit/4526862728af01f1135714079c0d8a8c754eb364), [14cd78c](https://github.com/morluto/rea/commit/14cd78c3224edcf79fca152915077112f547b9ed), [#6990](https://github.com/morluto/rea/pull/6990), [#5805](https://github.com/morluto/rea/issues/5805), [#5228](https://github.com/morluto/rea/pull/5228), [#5218](https://github.com/morluto/rea/issues/5218). Thanks @coygeek, @DeryFerd, @kaoru0822-kitauji, @Mangolycheematcha, @mhamzasakhi, @Shreyash021104.
|
||||
- **Archives and files:** Complete positional ZIP reads, preserve bounded ASAR signatures, report stable symlink loops as resolution failures, retain target applicability, and report unknown traversal coverage. Retain reported integrity contradictions for members present in only one compared artifact. [#4414](https://github.com/morluto/rea/pull/4414), [e8da7e1](https://github.com/morluto/rea/commit/e8da7e1edd075feec0e6beedec93d5645fed8125), [#5571](https://github.com/morluto/rea/pull/5571), [#5763](https://github.com/morluto/rea/pull/5763), [#5867](https://github.com/morluto/rea/pull/5867), [#5521](https://github.com/morluto/rea/issues/5521), [#5731](https://github.com/morluto/rea/issues/5731), [#5837](https://github.com/morluto/rea/issues/5837), [#7076](https://github.com/morluto/rea/pull/7076). Thanks @Afloat16, @coygeek, @jdotc1, @kaoru0822-kitauji, @WenyuChiou.
|
||||
- **Extraction and file ownership:** Restrict destination-case recovery to owned collisions, retain physical-read failures through late cancellation/cleanup, preserve cleanup capabilities for retry, and retain independent firmware extraction failures. [932fbb7](https://github.com/morluto/rea/commit/932fbb7d84a9a995b9aee3c1be7da05a8c4d7bbb), [9499667](https://github.com/morluto/rea/commit/949966712e68d8857a50a165ecddf04c2d7e6d3b), [56679dd](https://github.com/morluto/rea/commit/56679dd2ae31dda58e181f8bf99775014f7bb82b), [b3c912a](https://github.com/morluto/rea/commit/b3c912ab79ad147a37b6cfbe1552a1a5eecec629), [7298bd0](https://github.com/morluto/rea/commit/7298bd08d134860b84b4073f6703021916a5a4c5).
|
||||
- **Processes and cancellation:** Retain partial samples and shutdown gaps, unresolved descendants and caller abort reasons; bind cleanup errors to their owners, propagate comparison contradictions, and report no normal exit code for signalled targets. [ba866a2](https://github.com/morluto/rea/commit/ba866a26ecbe179a717e746cbc0d1605ae616ad5), [#5270](https://github.com/morluto/rea/pull/5270), [#6144](https://github.com/morluto/rea/pull/6144), [a721c42](https://github.com/morluto/rea/commit/a721c42c07dc674c32ab939627355b8a1bee97a5), [#6298](https://github.com/morluto/rea/pull/6298), [#6486](https://github.com/morluto/rea/pull/6486), [#5935](https://github.com/morluto/rea/issues/5935), [#5944](https://github.com/morluto/rea/issues/5944), [#6100](https://github.com/morluto/rea/issues/6100), [#6101](https://github.com/morluto/rea/issues/6101), [#6110](https://github.com/morluto/rea/issues/6110), [#6113](https://github.com/morluto/rea/issues/6113), [#6121](https://github.com/morluto/rea/issues/6121), [#6131](https://github.com/morluto/rea/issues/6131), [#6132](https://github.com/morluto/rea/issues/6132), [#6218](https://github.com/morluto/rea/issues/6218), [#6445](https://github.com/morluto/rea/issues/6445). Thanks @agorevski, @coygeek, @gongfpp, @kwakhyun, @mhamzasakhi.
|
||||
- **Provider cleanup:** Preserve Windows native handles until close is confirmed, native/Inspector/Electron cleanup ownership, Kuna probe identity and cleanup, IDA registration configuration, and Hopper preparation ownership. [#5772](https://github.com/morluto/rea/pull/5772), [87ab054](https://github.com/morluto/rea/commit/87ab054ea384b3fcbae2998b1f94685edcd5f624), [ff78165](https://github.com/morluto/rea/commit/ff78165978de9dbcf9fa16c2acf74bf85a69dc44), [04ae07a](https://github.com/morluto/rea/commit/04ae07ab8c9ae1a6697b6ad15489bbde515da8b5), [dd0015a](https://github.com/morluto/rea/commit/dd0015aaf156c5701ef9598bcc6c885e6b8da803), [b231c28](https://github.com/morluto/rea/commit/b231c28cf64c2f799ada99343f8f21e0e6acb15e), [#4505](https://github.com/morluto/rea/issues/4505), [#5616](https://github.com/morluto/rea/issues/5616).
|
||||
- **Provider diagnostics:** Give Ghidra restart recovery, stable Homebrew aliases, unsupported-host results for Linux-only integrations, correct IDA regex/readiness handling, and clear diagnostics when Windows refuses a verified native addon. [#5356](https://github.com/morluto/rea/pull/5356), [7c6142e](https://github.com/morluto/rea/commit/7c6142efdc19a42c148d0e8c96b69d18e63c7f87), [#5793](https://github.com/morluto/rea/pull/5793), [#4862](https://github.com/morluto/rea/pull/4862), [99ccfb6](https://github.com/morluto/rea/commit/99ccfb6e709ac4bc0afe176315280abde9ebcb9f), [#6832](https://github.com/morluto/rea/pull/6832), [#5730](https://github.com/morluto/rea/issues/5730), [#4860](https://github.com/morluto/rea/issues/4860), [#5723](https://github.com/morluto/rea/issues/5723). Thanks @coygeek, @dark13b, @gensokyo-dev, @jgt87, @jpmorris41488, @Lew1sWong.
|
||||
- **MCP results:** Preserve protocol envelopes on oversized errors, enforce and advertise absolute Evidence paths, retain typed output-filter errors and selected JSON paths, and give client/provider timeout recovery. Compact advertisements now normalize reported enum and union shapes for strict gateway translation. [562dab0](https://github.com/morluto/rea/commit/562dab00c5cbf76a9b2695bad93792eccdc9a49d), [3cf6206](https://github.com/morluto/rea/commit/3cf6206614e65b5ebb6284a5709090dfb8f9aaee), [0e83d92](https://github.com/morluto/rea/commit/0e83d92c583ae36afe00b2c23dd2a0f1d025cbca), [4714c7e](https://github.com/morluto/rea/commit/4714c7eaa3d1d8b6a5df0aff586e844b9d9ab06d), [1bfa60a](https://github.com/morluto/rea/commit/1bfa60ac20e1a344bcf3132c08d15b6860b05c61), [#5608](https://github.com/morluto/rea/pull/5608), [#3806](https://github.com/morluto/rea/pull/3806), [#5598](https://github.com/morluto/rea/issues/5598), [#3800](https://github.com/morluto/rea/issues/3800). Thanks @gensokyo-dev, @Hahaa909, @wang1xiang.
|
||||
- **Browser lifecycles:** Retain historical outcomes through cleanup, stop late navigation before session release, stop expired work, observe aborted fetch settlement, and mark later scenario actions skipped after failure or cancellation. [2e5171e](https://github.com/morluto/rea/commit/2e5171e391b83f8a467d0861fd452c1f78621fd8), [599fdac](https://github.com/morluto/rea/commit/599fdac029912f96c511f859660483405d2de26f), [b2fad36](https://github.com/morluto/rea/commit/b2fad362f2b37f81dc9a6f784f4521e4ca24d41e), [13a8640](https://github.com/morluto/rea/commit/13a864020414577006dce58d7fdaa877ddd0cb52), [#6023](https://github.com/morluto/rea/pull/6023), [#5943](https://github.com/morluto/rea/issues/5943). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Browser comparisons:** Preserve frame identity, default origin scope, unknown body/shape coverage, correspondence and uncertainty, and independent retained-observation budgets. [#4040](https://github.com/morluto/rea/pull/4040), [#4413](https://github.com/morluto/rea/pull/4413), [#4476](https://github.com/morluto/rea/pull/4476), [a0c22ff](https://github.com/morluto/rea/commit/a0c22ff0125b8af7612db54d3708413e2bfecb25), [a0fcc60](https://github.com/morluto/rea/commit/a0fcc60d89eb0cc09b08181eb9f961acfd07d8e9), [#3878](https://github.com/morluto/rea/issues/3878), [#3910](https://github.com/morluto/rea/issues/3910), [#3941](https://github.com/morluto/rea/issues/3941), [#4385](https://github.com/morluto/rea/issues/4385), [#4477](https://github.com/morluto/rea/issues/4477). Thanks @Adolanium, @coygeek, @eco-null, @gensokyo-dev, @kaoru0822-kitauji, @XY-code1.
|
||||
- **Electron observations:** Preserve listener callback ownership and count registrations by source callsite; block trash calls in the active hook, retain window handles through cancelled selection, and preserve cancellation during finalization. [dfe8378](https://github.com/morluto/rea/commit/dfe8378cf5a727e3d93d47a5986233175c6ef898), [5064007](https://github.com/morluto/rea/commit/506400748eb1d6c674f0cf51330073929c45f939), [816a9dc](https://github.com/morluto/rea/commit/816a9dc98886e6d824cd8ae88b68731b161d5dda), [1707d17](https://github.com/morluto/rea/commit/1707d171d750955bf6387e028e378183b360ffe3), [4e2a566](https://github.com/morluto/rea/commit/4e2a566d6c7e053ecbbee1b684311ed31ea163bf).
|
||||
- **JavaScript source maps:** Keep unmapped and missing mappings unknown, preserve partial indexed-map facts and section paths, propagate preparation capability errors, and retain observed worker failures through cancellation. [a4dbb36](https://github.com/morluto/rea/commit/a4dbb36d8ea83e9f94dd289ac1a2d220d2c72c9b), [2b2e362](https://github.com/morluto/rea/commit/2b2e362a00fc5ba2f93fa6c62e2d8780072d1b4b), [498a667](https://github.com/morluto/rea/commit/498a667234b0140c1cfa43c819b35454ab11d14e), [#6149](https://github.com/morluto/rea/pull/6149), [#6214](https://github.com/morluto/rea/pull/6214), [f01dcca](https://github.com/morluto/rea/commit/f01dccab77f712fb4ac936d0a6e92a70d12f0789), [#6082](https://github.com/morluto/rea/issues/6082), [#6061](https://github.com/morluto/rea/issues/6061). Thanks @coygeek, @diyasharma05, @kaoru0822-kitauji.
|
||||
- **JavaScript semantics:** Preserve overflowing timer uncertainty, unavailable-scope comparisons, directory-form URL/ESM path intent, and artifact confinement. Replaced globals lose built-in effects; erased imports no longer become runtime bindings. Treat a known truthy `fork()` `silent` option as piped stdio unless a string/array `stdio` override takes precedence; unresolved options stay dynamic. [#3456](https://github.com/morluto/rea/pull/3456), [8d198d6](https://github.com/morluto/rea/commit/8d198d6ec707402d75276f516e0090d71767b7e4), [#4646](https://github.com/morluto/rea/pull/4646), [#2700](https://github.com/morluto/rea/pull/2700), [#4565](https://github.com/morluto/rea/pull/4565), [#6994](https://github.com/morluto/rea/pull/6994), [#3400](https://github.com/morluto/rea/issues/3400), [#3882](https://github.com/morluto/rea/issues/3882), [#3367](https://github.com/morluto/rea/issues/3367), [#7083](https://github.com/morluto/rea/pull/7083), [#7084](https://github.com/morluto/rea/issues/7084). Thanks @Adolanium, @amyanger, @coygeek, @gensokyo-dev, @HawkForge, @rohitg00, @viniciusdsandrade.
|
||||
- **JavaScript routes and imports:** Treat path data as route candidates, preserve route flag and lexical RPC identities, package-main trailing-slash selection, catch aliases, and factory parameter ownership. [#6360](https://github.com/morluto/rea/pull/6360), [38ecc02](https://github.com/morluto/rea/commit/38ecc0248e1993d25a2f7dd350024484cce797bb), [#4219](https://github.com/morluto/rea/pull/4219), [21097b1](https://github.com/morluto/rea/commit/21097b192b1ab94647e3446b1a2a4dd00e924d5e), [f774dc4](https://github.com/morluto/rea/commit/f774dc420b3befe2b8e68971b4116da2d62fc800), [#6349](https://github.com/morluto/rea/issues/6349), [#4189](https://github.com/morluto/rea/issues/4189). Thanks @coygeek, @hemv-857, @kaoru0822-kitauji, @Nikhi00718.
|
||||
- **Managed/IL2CPP:** Preserve extension-only scope uncertainty and type discovery after member limits, nested names, ReadyToRun/declaration facts, selected type scope, valid shared-suffix strings, and truncated IL2CPP metadata prefixes. [#5674](https://github.com/morluto/rea/pull/5674), [#5312](https://github.com/morluto/rea/pull/5312), [87fa1b6](https://github.com/morluto/rea/commit/87fa1b613d2b6bda1722855a00f1390f27731b64), [bc2d494](https://github.com/morluto/rea/commit/bc2d494030efd1ccabaeaa0bd49226aa87c378a2), [1f0eb22](https://github.com/morluto/rea/commit/1f0eb228e50908ee2a2aaa72d329178ae1a2cdbd), [4c5fae5](https://github.com/morluto/rea/commit/4c5fae5179dd54c4a0d427cd2a115b9c887e3841), [b3aadaf](https://github.com/morluto/rea/commit/b3aadaf474e59b33e5ff465236008a67eccefb87), [#5467](https://github.com/morluto/rea/issues/5467), [#5146](https://github.com/morluto/rea/issues/5146). Thanks @coygeek, @eco-null, @elricfd, @umi008.
|
||||
- **Format boundaries:** Preserve independent facts across malformed inputs in model, GPU, WASM, Rust, Android, and SQLite inspection; report optional metadata warnings without discarding core results. [eb41f35](https://github.com/morluto/rea/commit/eb41f358cf83d5626f09de68332f9718f9aa6482), [#5789](https://github.com/morluto/rea/pull/5789), [00a9e95](https://github.com/morluto/rea/commit/00a9e95de91a4bd4d2a41f8aee704a53fa575750), [fc63fe8](https://github.com/morluto/rea/commit/fc63fe8682654086261540dcf3c59afc36c7d58f), [39a91d4](https://github.com/morluto/rea/commit/39a91d4db96ca13224c25936330ea84fe78970e5), [6c6e2ad](https://github.com/morluto/rea/commit/6c6e2ad62b20f810b54f5a567e66fc55241514b2), [929a0a7](https://github.com/morluto/rea/commit/929a0a76c870d9991e015dde58308fd9df0280b8), [d137a4c](https://github.com/morluto/rea/commit/d137a4cdde0049de967f1d5cd429ed9e1f7d0a10), [ef475fc](https://github.com/morluto/rea/commit/ef475fcf8b0601c61052b7091568ba574546417d), [#5749](https://github.com/morluto/rea/issues/5749). Thanks @coygeek, @gensokyo-dev.
|
||||
- **Capture decoding:** Preserve malformed protobuf trailer facts and wire/schema identity, HTTP/2 URI and UTF-8 failure facts, WebMCP property coverage, and offline capture availability without a browser provider. Report invalid record ordinals as selection errors with actual bounds where available, and trim surrounding SP/HTAB from gRPC-web trailer values while preserving internal whitespace. [9cf7a7c](https://github.com/morluto/rea/commit/9cf7a7cb0d3c72a1121fde280cb11bf911af8055), [b3c3b21](https://github.com/morluto/rea/commit/b3c3b21e43cab4e71b460d08829d8c6e4e9b9c21), [4cb1f1d](https://github.com/morluto/rea/commit/4cb1f1d820df4498ee464bcf72a5f47d40cf8dab), [51e5938](https://github.com/morluto/rea/commit/51e593877642ab42fb5bf25812f4a4110ee7ac26), [d23be5c](https://github.com/morluto/rea/commit/d23be5c0898c39c9f0c54e2e0b43ee746d92d316), [#6725](https://github.com/morluto/rea/pull/6725), [#3947](https://github.com/morluto/rea/issues/3947), [#7067](https://github.com/morluto/rea/pull/7067), [#7068](https://github.com/morluto/rea/issues/7068), [#7069](https://github.com/morluto/rea/pull/7069), [#7071](https://github.com/morluto/rea/issues/7071). Thanks @Adolanium, @coygeek, @Sriram-PR.
|
||||
- **Evidence and errors:** Authenticate ledger Evidence before committing; preserve parent-schema diagnostics, canonical correction details and owned cleanup outcomes; identify the rejected function-Evidence side/kind. Fixed allowances now name their actual limits. [0407bbf](https://github.com/morluto/rea/commit/0407bbfbe65e8fa475e8bb0c78c30170e2c2f55e), [#6361](https://github.com/morluto/rea/pull/6361), [1629266](https://github.com/morluto/rea/commit/1629266e3c08c90fed6bba5383528af7986a12b7), [#6969](https://github.com/morluto/rea/pull/6969), [#5860](https://github.com/morluto/rea/pull/5860), [#6034](https://github.com/morluto/rea/issues/6034), [#5932](https://github.com/morluto/rea/issues/5932), [#5847](https://github.com/morluto/rea/issues/5847). Thanks @AyoubElleuch, @coygeek, @dhruvac29, @kaoru0822-kitauji.
|
||||
- **ADB and firmware recovery:** Report an unparsed push checksum as unverified, normalize absent installer-package sentinels, describe extraction-relative Unblob links, and avoid recommending a budget increase at its fixed ceiling. [#6010](https://github.com/morluto/rea/pull/6010), [#4073](https://github.com/morluto/rea/pull/4073), [#4573](https://github.com/morluto/rea/pull/4573), [#6971](https://github.com/morluto/rea/pull/6971), [#5924](https://github.com/morluto/rea/issues/5924), [#4175](https://github.com/morluto/rea/issues/4175). Thanks @coygeek, @Ganesh2325, @kaoru0822-kitauji, @Rinkia, @XiaoBinGan, @yankikucuk.
|
||||
- **CLI formatting:** Share Markdown report formatting, exclude inherited row values, and preserve arrays of empty objects as JSON. Render Markdown table headers and cells literally while keeping intraword underscores readable. [b784912](https://github.com/morluto/rea/commit/b784912811fd10ac9dc425696dc0e9af632afa34), [#6984](https://github.com/morluto/rea/pull/6984), [#7058](https://github.com/morluto/rea/pull/7058), [#7057](https://github.com/morluto/rea/issues/7057), [#7073](https://github.com/morluto/rea/pull/7073). Thanks @Plastic-time, @ShuaixinHuang, @viniciusdsandrade.
|
||||
- **Format identity:** Preserve Python marshal scalar/decode limits, empty optional BEAM chunks, PE resource branch identities and certificate alignment, unsupported compressed ELF notes, and codec-framed GPU bundle extents. [0603223](https://github.com/morluto/rea/commit/06032237c6755e3dd5f9a8db732c89958b238cc7), [c46064c](https://github.com/morluto/rea/commit/c46064c3f6eaba121c5de7b17390bfdecdd132ef), [66671e8](https://github.com/morluto/rea/commit/66671e874888e98f7e2b27e68a7c6fb320bbb827), [03077d5](https://github.com/morluto/rea/commit/03077d54afc49613db4e38f70c506bc60f5223aa), [#6721](https://github.com/morluto/rea/pull/6721), [9fb8c11](https://github.com/morluto/rea/commit/9fb8c114a03ad60e57c11586967c6df845a3f97e), [f6a1e2c](https://github.com/morluto/rea/commit/f6a1e2c892dc23c7583c3d03af9edb01632ff5a5), [#6694](https://github.com/morluto/rea/issues/6694). Thanks @coygeek, @Deepak-Kambala.
|
||||
- **WASM and APK metadata:** Preserve ambiguous WASM identity and partial facts, glue URL query/fragment text across line terminators, APK signer identity, and raw issuer identity when comparison rules are unknown. [af8f0ba](https://github.com/morluto/rea/commit/af8f0badf64fd3546392b6aa4625777904b2f051), [#6802](https://github.com/morluto/rea/pull/6802), [f783f0f](https://github.com/morluto/rea/commit/f783f0f1e82472cdddd4de2156d974f729e332a6), [115a78f](https://github.com/morluto/rea/commit/115a78fe241276c34917b496621954c5f29bfc15), [#6639](https://github.com/morluto/rea/issues/6639). Thanks @coygeek, @gensokyo-dev, @gongfpp.
|
||||
- **Source identity:** Preserve Next route spelling, module grammar and loader request identity, directory package-main behavior, accessor effects, exact source targets, and resolver-discovery versus module-loading distinctions. [ddd7ac7](https://github.com/morluto/rea/commit/ddd7ac7b846b48191ead1f7dcd0e3bd735468fa8), [9974512](https://github.com/morluto/rea/commit/99745126009b15060d71c38809418858a07d5afa), [f2e5fd1](https://github.com/morluto/rea/commit/f2e5fd148a85fa737302dd194a33172c4561093e), [baf1678](https://github.com/morluto/rea/commit/baf16784af3592534687052431c145daef55cfd1), [6e324b2](https://github.com/morluto/rea/commit/6e324b224405f2befd953234f6dc210cf72ad50a), [75daf0c](https://github.com/morluto/rea/commit/75daf0c7df108f04ab41f34aec1a31d4a7ceb322), [f91b7f8](https://github.com/morluto/rea/commit/f91b7f8009c9af27a5e0148e3b07d20bfe3b7a2a).
|
||||
- **Graphs and selected facts:** Preserve JavaScript graph omissions/version facts, Electron native-loader facts, web JSON identity/coverage, module-path selection, feature-trace qualifications, selected native paths, endpoint context, and mixed Swift demangler outcomes. [30db499](https://github.com/morluto/rea/commit/30db499db310f073d9bf3d3acfd77985e1cb7e2e), [5a7bddb](https://github.com/morluto/rea/commit/5a7bddb3aeb0345854e76b79d529aed1c40e2054), [79805f2](https://github.com/morluto/rea/commit/79805f244a1ec0850f3109929168a9c8ebb1b908), [f719dc4](https://github.com/morluto/rea/commit/f719dc46c51a55b3926eec9659d06e7dfb95132b), [7784fdb](https://github.com/morluto/rea/commit/7784fdbad78f8b384d513f0e7dae8e983cd48ef8), [76500f1](https://github.com/morluto/rea/commit/76500f12b54e07b1ea700a403b2593890d63b42a), [676d29f](https://github.com/morluto/rea/commit/676d29f9eef794cea668f5f09f0ff510c848a513), [edf7b3d](https://github.com/morluto/rea/commit/edf7b3d32c3248f221e27f7ce5a3d34e8590285b), [93de7bc](https://github.com/morluto/rea/commit/93de7bc7e324a761162a31625b838ba0d4e3824d).
|
||||
- **Managed boundaries:** Honor native platform symbol lookup, consume resolved IL2CPP ELF declarations, and attribute malformed comparison inputs to the supplied argument. [64fa1db](https://github.com/morluto/rea/commit/64fa1db1309218283df19f3ee4dba61f5fbd2a67), [87745af](https://github.com/morluto/rea/commit/87745af52ab0ccd3a172c2534c516d41b0095035), [96ad94f](https://github.com/morluto/rea/commit/96ad94f69eda5d68c7335ff775b6927b1376fd2b).
|
||||
- **Provider and worker recovery:** Preserve output loss after successful command exit, physical refusal diagnostics, bounded worker control framing, JavaScript worker-phase/budget attribution, admitted ambiguity choices, and selected EVM limiter availability. Scope objdump debug-link limitations to DWARF requests and keep process-capture preparation nonexecuting. [e639edf](https://github.com/morluto/rea/commit/e639edf6204c05285d96840478926592daf95876), [171b33b](https://github.com/morluto/rea/commit/171b33b2c4defc6cbea49545bedf4272b72d1ea2), [34563fc](https://github.com/morluto/rea/commit/34563fcc9bd92af30668820dcac5635e27181094), [3f9357b](https://github.com/morluto/rea/commit/3f9357b5990d166a38440f56f439452266650093), [cec8f1e](https://github.com/morluto/rea/commit/cec8f1e8f305d6332272457a6ee419d3c5fb6359), [a74d859](https://github.com/morluto/rea/commit/a74d859a19122fb8f1282b3632d320997b1caae0), [c284168](https://github.com/morluto/rea/commit/c284168c4b1cd214ce7f504d43da28528d82d603), [4fb936f](https://github.com/morluto/rea/commit/4fb936fe86bb7a07f94f819ae8510301f2ef69c9).
|
||||
- **Setup and limits:** Retain unknown environment-budget facts, selected-skill/maintenance uncertainty, Hopper URL admission and bookmark verification, and listener failure recovery that matches the system cause. [a718037](https://github.com/morluto/rea/commit/a7180370332350bc0f998fbd643888221759570e), [d1fbebb](https://github.com/morluto/rea/commit/d1fbebb578538dc94fd5e376e6e1959218efa835), [60accad](https://github.com/morluto/rea/commit/60accad2de8f2862fe91901502fe26cc5390fb36), [fd7da77](https://github.com/morluto/rea/commit/fd7da77fc02dcfaf7d1bc9f2fb240d71cdd27c59), [0c440bf](https://github.com/morluto/rea/commit/0c440bfc07f3cb343e2e836d27cccaed539cfe9c).
|
||||
- **State and Evidence:** Retain export tarball ownership and SQLite primary outcomes through cleanup retry, count emitted SQLite schema names across encodings, scope display refresh to matching snapshot bindings, and report retained-result validation failures consistently. Reconstruction uses one effective completion scope and kind-based labels when names are blank. [ab58edc](https://github.com/morluto/rea/commit/ab58edcdf50b4bd687900ee2dc362ecc5c3dce15), [1006714](https://github.com/morluto/rea/commit/100671427105791a5c5913a918b63a394f6bc83e), [a47788a](https://github.com/morluto/rea/commit/a47788a6a4abb067273f954275a9d0a269adf1e7), [8aa40e9](https://github.com/morluto/rea/commit/8aa40e9a61155cff8b6f3c138c601bf805b20653), [b8c1b45](https://github.com/morluto/rea/commit/b8c1b45316882beeab4a9de49381d94e131fab0f), [a35b933](https://github.com/morluto/rea/commit/a35b9335a0124674ee5e3abe8c075f1fff6b4fa6), [#6137](https://github.com/morluto/rea/pull/6137), [#6126](https://github.com/morluto/rea/issues/6126). Thanks @carlogiovannineri-rgb, @mohithhhh.
|
||||
- **Capture coverage:** Name browser bundle budgets and deadline recovery, separate domain readiness from worker attribution, attribute rendered-frame omissions to their producing stage, share offline-capture host admission with discovery, preserve selected signal-file semantics, and retain short Flutter GNU build IDs. [#6036](https://github.com/morluto/rea/pull/6036), [ba78133](https://github.com/morluto/rea/commit/ba781330d6d28b22d15498e63c144fb40e086156), [5934387](https://github.com/morluto/rea/commit/5934387f4185129efe9dc8b7338d8bc3fa5b25bd), [ad629b2](https://github.com/morluto/rea/commit/ad629b2e31c3a9495b27179360b508fa021fe86a), [928ba76](https://github.com/morluto/rea/commit/928ba76882bfd216dde8ed79118789b2c3aad4f9), [678c4cf](https://github.com/morluto/rea/commit/678c4cfa68e2fee12f620075e12dc87d9b1195b0).
|
||||
- **Additional boundary facts:** Retain parser format/query occurrence identity and independent malformed-input facts, unresolved nested Swift identifiers, and completed signing-capture coverage; describe crash documents without assuming a provider. [5f702dd](https://github.com/morluto/rea/commit/5f702dd6ccd9e8e505e224854b91678439c3b41f), [99d2d7a](https://github.com/morluto/rea/commit/99d2d7ad22bcbd780919760008aba6a88e7d1077), [90e433d](https://github.com/morluto/rea/commit/90e433df06b3f83417176b8db1b0a905f9df1d3a), [#5548](https://github.com/morluto/rea/pull/5548), [ee12d51](https://github.com/morluto/rea/commit/ee12d517a25d34853d40e31fc039c7d1576c60e1), [#5538](https://github.com/morluto/rea/issues/5538). Thanks @coygeek, @ShuaixinHuang.
|
||||
- **Documentation:** Add the Romanian README. [#7026](https://github.com/morluto/rea/pull/7026). Thanks @An3treiu.
|
||||
|
||||
Full contribution record: [1740 commits and 497 merged PRs](https://github.com/morluto/rea/blob/rea-agents-6.8.0/docs/releases/6.8.0.contributions.json).
|
||||
|
||||
## [6.7.0](https://github.com/morluto/rea/compare/rea-agents-6.6.0...rea-agents-6.7.0) (2026-10-18)
|
||||
|
||||
### Highlights
|
||||
|
||||
- Client configuration edits follow the duplicate-member semantics used by MCP clients.
|
||||
|
||||
### Changes
|
||||
|
||||
- Includes the [6.6.0 changes and migration notes](https://github.com/morluto/rea/releases/tag/rea-agents-6.6.0). That version was tagged but was not published to npm.
|
||||
|
||||
### Fixes
|
||||
|
||||
- **Client configuration:** Setup edits the last repeated JSON/JSONC member, matching the value clients actually read. Setup and uninstall refuse a removal that would reactivate a shadowed duplicate, preserving the original configuration. ([#4957](https://github.com/morluto/rea/pull/4957), [morluto/rea#4952](https://github.com/morluto/rea/issues/4952)). Thanks @kaoru0822-kitauji, @xu91102.
|
||||
|
||||
Full contribution record: [4 commits and 1 merged PR](https://github.com/morluto/rea/blob/rea-agents-6.7.0/docs/releases/6.7.0.contributions.json).
|
||||
|
||||
## [6.6.0](https://github.com/morluto/rea/compare/rea-agents-6.5.0...rea-agents-6.6.0) (2026-10-18)
|
||||
|
||||
### Highlights
|
||||
|
||||
- Add Kuna as a caller-supplied static-analysis provider.
|
||||
- Query selected JavaScript facts and multiple APK locale tables without repeating the underlying analysis.
|
||||
- Extend Ghidra analysis to Windows ARM PE targets and explicit raw m68k images.
|
||||
- Preserve completed Evidence, cancellation reasons and retryable cleanup across provider and CLI finalization.
|
||||
|
||||
### Changes
|
||||
|
||||
- **Kuna analysis:** Use a caller-installed Kuna for procedure listings, pseudocode, assembly, call relationships, exact-address xrefs and cached string searches. Project exports retain source spans and digests; repeated requests reuse completed work. Recorded real-provider coverage uses Kuna 1.833 on Linux; Windows, PE and universal Mach-O are unsupported. See [the provider guide](docs/kuna-provider.md). ([#3832](https://github.com/morluto/rea/pull/3832), [#3854](https://github.com/morluto/rea/pull/3854), [#4071](https://github.com/morluto/rea/pull/4071), [#4095](https://github.com/morluto/rea/pull/4095)). Thanks @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Retained JavaScript facts:** Query exact application paths, selected properties and filtered module references through `inspect_analysis_view`, with paging and complete row Evidence. These views reuse completed analysis and preserve source selection and unknown coverage. ([#4265](https://github.com/morluto/rea/pull/4265), [morluto/rea#4190](https://github.com/morluto/rea/issues/4190), [#4097](https://github.com/morluto/rea/pull/4097), [morluto/rea#4092](https://github.com/morluto/rea/issues/4092)). Thanks @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **APK locale tables:** Request several resource locales in one decode with repeatable CLI `--locales` or MCP `locales`. Each table reports its status and coverage; existing single-locale fields remain available. ([#4804](https://github.com/morluto/rea/pull/4804), [morluto/rea#4777](https://github.com/morluto/rea/issues/4777)). Thanks @abhay-2108, @N0zoM1z0.
|
||||
- **Windows ARM binaries:** Ghidra admits ARMNT/Thumb-2 and ARM64 PE applications and DLLs on the Windows x64 host lane. Recorded Ghidra 12.1.4 verification uses `WindowsResourceReference` disabled and raised startup budgets; default-analyzer completion, ARM64EC/ARM64X and Windows-on-Arm hosts remain unverified or unsupported. See [the exact scope](docs/windows-ghidra-p0.md#native-authority-and-target-identity). ([#3965](https://github.com/morluto/rea/pull/3965), [morluto/rea#2186](https://github.com/morluto/rea/issues/2186)). Thanks @N0zoM1z0, @WestLabsAI.
|
||||
- **Raw m68k images:** Analyze explicitly selected big-endian m68k raw binaries using Ghidra’s fixed 68000 profile and a caller-supplied aligned entry. Real CLI/MCP verification used Ghidra 12.1.4 on macOS arm64; Linux is unverified and Windows P0 does not admit m68k. See [mapping requirements](docs/raw-binary.md). ([#4018](https://github.com/morluto/rea/pull/4018)). Thanks @icedac.
|
||||
- **JADX deadlines:** Set `REA_JADX_OPERATION_TIMEOUT_MS` to extend APK loading and upstream operation deadlines together. Doctor reports the effective value and setup forwards an explicit selection. ([#4466](https://github.com/morluto/rea/pull/4466), [morluto/rea#3918](https://github.com/morluto/rea/issues/3918)). Thanks @jaychou813, @SandstromPL.
|
||||
- **Electron window options:** Resolve stable local constant `BrowserWindow` options while retaining source provenance. Relevant writes and escapes keep the result unknown. ([#4400](https://github.com/morluto/rea/pull/4400), [morluto/rea#4366](https://github.com/morluto/rea/issues/4366)). Thanks @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Language editions:** Add the Greek website edition and Malayalam documentation, with Korean typography and translation corrections across the site. ([#3520](https://github.com/morluto/rea/pull/3520), [#4792](https://github.com/morluto/rea/pull/4792), [#3821](https://github.com/morluto/rea/pull/3821), [morluto/rea#3820](https://github.com/morluto/rea/issues/3820), [#3799](https://github.com/morluto/rea/pull/3799), [morluto/rea#3798](https://github.com/morluto/rea/issues/3798), [#4344](https://github.com/morluto/rea/pull/4344), [#3856](https://github.com/morluto/rea/pull/3856), [#4166](https://github.com/morluto/rea/pull/4166)). Thanks @gensokyo-dev, @hi2242, @kalayciburak, @kaoru0822-kitauji, @kraken-503, @kukinghan, @N0zoM1z0, @yeseong0412.
|
||||
|
||||
### Fixes
|
||||
|
||||
- **Cancellation and cleanup:** Keep completed observations, exact cancellation reasons and the original physical failure through finalization. Failed cleanup retains its process or file owner for retry; recovered resources disappear from pending-cleanup reports without replacing the original error. ([#4238](https://github.com/morluto/rea/pull/4238), [morluto/rea#4232](https://github.com/morluto/rea/issues/4232), [#4274](https://github.com/morluto/rea/pull/4274), [morluto/rea#3923](https://github.com/morluto/rea/issues/3923), [3e82366](https://github.com/morluto/rea/commit/3e8236639abffa39deb5d5e939ec8baf6aaf3ca3), [16924e2](https://github.com/morluto/rea/commit/16924e275e939d0543ba01493324959a9e1d731c), [bbfe6ed](https://github.com/morluto/rea/commit/bbfe6edd60290f9bfbec68c8a0bcf605d8bbf5b3), [1f23e97](https://github.com/morluto/rea/commit/1f23e977b50301e91f02a4a17c9f8d48bb7b60f3), [08fbeb8](https://github.com/morluto/rea/commit/08fbeb8bdab27118b6c57039aef7c0fa8b6463bc)). Thanks @coygeek.
|
||||
- **Provider startup:** Join prerequisite probes and in-flight readiness work before shutdown, preserve typed timeout and JVM-memory failures, and retain startup diagnostics through cleanup. Ghidra recognizes keg-only Homebrew JDKs and names the setting responsible for a startup timeout. ([376eeca](https://github.com/morluto/rea/commit/376eeca770820b31f7f8d4c5fcc397315621643e), [ceeff5a](https://github.com/morluto/rea/commit/ceeff5a41788e47e5aee96a4baf191a6ce7fde4b), [3ad1032](https://github.com/morluto/rea/commit/3ad1032b11edea2dcb054e78f951cc43159ae83d), [964decd](https://github.com/morluto/rea/commit/964decd989acfa84c4bfc3295362c4c46877fc20), [3fa972a](https://github.com/morluto/rea/commit/3fa972af7c7cb6cf014ffc426bdf7130dc6f2fd2), [#4557](https://github.com/morluto/rea/pull/4557), [morluto/rea#4556](https://github.com/morluto/rea/issues/4556), [#4590](https://github.com/morluto/rea/pull/4590), [morluto/rea#4241](https://github.com/morluto/rea/issues/4241)). Thanks @DanielCvz1, @J0UH, @Rinkia, @RishTheFish21, @vadlachary2002.
|
||||
- **Client configuration:** Preserve the selected launcher, destination file, caller timeout choices and unresolved configuration evidence. Setup/uninstall recovery stays bound to the exact published file, keeps current verified backups, and transfers failed cleanup ownership for retry. ([d0253c9](https://github.com/morluto/rea/commit/d0253c99d65afbbd12732a2fdaf711640574e17f), [7e4eed9](https://github.com/morluto/rea/commit/7e4eed9dd48485b6a7100300aa98401811c2deb9), [2fb5e20](https://github.com/morluto/rea/commit/2fb5e204a35723f6f843d56ca47b0db94d8fcc44), [8235210](https://github.com/morluto/rea/commit/823521085a28f825e7fee1929d852d420042d1f7), [d818c3d](https://github.com/morluto/rea/commit/d818c3d2639d3115edb8a5269a754101aea6c3bd), [19329e4](https://github.com/morluto/rea/commit/19329e43bb38ca9d64512d801b25abcb188d2512), [dadf909](https://github.com/morluto/rea/commit/dadf909dce2e8b3d055541de69b4118d21276316), [333e0ab](https://github.com/morluto/rea/commit/333e0ab0dfc126a9915907d5079ed6195190e38e), [c15c206](https://github.com/morluto/rea/commit/c15c2062ad815cc9a3222ccfaeb7ef53cacd8b72)). Thanks @Adolanium.
|
||||
- **Configuration formats:** Read signed 64-bit TOML integers without rounding, handle large TOML documents without argument-spread failures, preserve JSONC trivia and Hermes YAML scalar text, and reject NUL bytes in registration environment values. ([#4937](https://github.com/morluto/rea/pull/4937), [morluto/rea#4917](https://github.com/morluto/rea/issues/4917), [c43bc0b](https://github.com/morluto/rea/commit/c43bc0bf66a4e245a0cafa40def0d0207bcbd73c), [4fd28fc](https://github.com/morluto/rea/commit/4fd28fc9e0c4f8358147af38c72ab9e814f52d61), [#3568](https://github.com/morluto/rea/pull/3568), [#4335](https://github.com/morluto/rea/pull/4335)). Thanks @alfredo1491, @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0, @ShuaixinHuang, @xu91102.
|
||||
- **Claude Code configuration:** Setup, doctor and uninstall now accept only the strict JSON format Claude Code reads, including a leading UTF-8 BOM. Comments and trailing commas are refused before backup or writing; clients that support JSONC retain their existing behavior. ([#4944](https://github.com/morluto/rea/pull/4944), [morluto/rea#4940](https://github.com/morluto/rea/issues/4940)). Thanks @kaoru0822-kitauji, @xu91102.
|
||||
- **CLI output and dispatch:** Accept `--flag=value` and spaced global output values, keep array positions in filtered JSON, validate integer tokens before coercion, and exit quietly when the output reader closes. Cancellation remains effective through command finalization; app bundles and static native inspections select the intended workflow. ([#3811](https://github.com/morluto/rea/pull/3811), [#4823](https://github.com/morluto/rea/pull/4823), [2ae4e16](https://github.com/morluto/rea/commit/2ae4e1608f290c95ff7cff28c4d16a79b301d03d), [8d82579](https://github.com/morluto/rea/commit/8d8257928bbe8d43fedffc4db5fca26c8108820f), [#3781](https://github.com/morluto/rea/pull/3781), [279c590](https://github.com/morluto/rea/commit/279c590ef6bb737d4a48ea696a0ae5eb30010b3d), [#3511](https://github.com/morluto/rea/pull/3511), [morluto/rea#3321](https://github.com/morluto/rea/issues/3321), [f31b231](https://github.com/morluto/rea/commit/f31b231a92a61be6bd1800b16be0b3210ae82944)). Thanks @arjunprabhulal, @coygeek, @kaoru0822-kitauji, @ShuaixinHuang, @Sriram-PR, @Tapraj-Pathak.
|
||||
- **Updates:** Route `--update` through the verified updater and bound retained package-manager diagnostics. ([4f7b5fb](https://github.com/morluto/rea/commit/4f7b5fb6023ce2ec449561782d09e3c7e5ac7c7b), [01cdbbd](https://github.com/morluto/rea/commit/01cdbbdc556262c47d04a5b3391dd9c17946d3fc)).
|
||||
- **MCP contracts:** Advertise self-contained tool inputs while preserving tuple, array, Boolean and nested-JSON constraints across schema profiles. Schema-reference handling no longer rewrites literal metadata; catalog identity includes the output-schema profile. ([5e30969](https://github.com/morluto/rea/commit/5e30969cc5f1255d44ec7a1f1ca8dbac0af5e7c4), [10aa74d](https://github.com/morluto/rea/commit/10aa74dfedc3ae776f50d1fa1eb83b64ae82d3d6), [204b9f3](https://github.com/morluto/rea/commit/204b9f3ac140a993c3e458bb0e117de31818ab29), [b3389a5](https://github.com/morluto/rea/commit/b3389a5b0834265fe7a40391be315d810a53a97e), [a2e3016](https://github.com/morluto/rea/commit/a2e30168a046f3b79319eb7fbaf807b802104199), [ba98130](https://github.com/morluto/rea/commit/ba98130049fa40ade66b03611ff70c7ddc011a1d), [c5fb86d](https://github.com/morluto/rea/commit/c5fb86d1948af0a287a581b29b2b1d554743c607)). Thanks @akushonkamen, @HarsH-1877, @kaoru0822-kitauji, @xingzihai.
|
||||
- **Session ordering:** Keep snapshot admission ahead of later calls on the same connection, retain shutdown signals until cleanup succeeds, and preserve inspection authority through investigation publication. ([#3661](https://github.com/morluto/rea/pull/3661), [morluto/rea#3334](https://github.com/morluto/rea/issues/3334), [8873b39](https://github.com/morluto/rea/commit/8873b39770e89eae22b7693bc492d7e423b22bea), [663d07f](https://github.com/morluto/rea/commit/663d07f02cd62c99d81c59391d2c6d745c875335)). Thanks @coygeek, @rudycelekli, @Swatantra-66.
|
||||
- **JavaScript exports and resolution:** Retain CommonJS exports created with `Object.defineProperty` and track export receiver lifetimes. Resolve scoped packages, package `main`, JSON requires and `.mts`/`.cts` imports using the owning loader; overwritten `require` bindings and erased type-only imports no longer create false runtime links. ([#4725](https://github.com/morluto/rea/pull/4725), [morluto/rea#4724](https://github.com/morluto/rea/issues/4724), [#3567](https://github.com/morluto/rea/pull/3567), [morluto/rea#3541](https://github.com/morluto/rea/issues/3541), [#4838](https://github.com/morluto/rea/pull/4838), [morluto/rea#4632](https://github.com/morluto/rea/issues/4632), [#3844](https://github.com/morluto/rea/pull/3844), [morluto/rea#3383](https://github.com/morluto/rea/issues/3383), [#4164](https://github.com/morluto/rea/pull/4164), [morluto/rea#3915](https://github.com/morluto/rea/issues/3915), [#4223](https://github.com/morluto/rea/pull/4223), [#3801](https://github.com/morluto/rea/pull/3801), [morluto/rea#3393](https://github.com/morluto/rea/issues/3393), [#4165](https://github.com/morluto/rea/pull/4165), [morluto/rea#3932](https://github.com/morluto/rea/issues/3932)). Thanks @Abishek0070, @coygeek, @JNTMTMTM, @kaoru0822-kitauji, @prasad2512, @rodricksz4h5, @xu91102.
|
||||
- **JavaScript value flow:** Preserve values captured before later assignments, guarded factory fallthrough and nested destructuring. Track exact property keys and setter writes; direct `eval`, compound assignments and incoming callbacks preserve unresolved effects instead of inventing certainty. ([#4196](https://github.com/morluto/rea/pull/4196), [morluto/rea#3881](https://github.com/morluto/rea/issues/3881), [#4341](https://github.com/morluto/rea/pull/4341), [morluto/rea#3963](https://github.com/morluto/rea/issues/3963), [bfb7ed0](https://github.com/morluto/rea/commit/bfb7ed0bd43edc605e00fc52c6e21e735b285904), [#3480](https://github.com/morluto/rea/pull/3480), [#3831](https://github.com/morluto/rea/pull/3831), [morluto/rea#3312](https://github.com/morluto/rea/issues/3312), [9ac76d3](https://github.com/morluto/rea/commit/9ac76d3d9486856d25395b413d7ac5ffa744cad0), [#4342](https://github.com/morluto/rea/pull/4342), [morluto/rea#4206](https://github.com/morluto/rea/issues/4206), [#4243](https://github.com/morluto/rea/pull/4243), [morluto/rea#3880](https://github.com/morluto/rea/issues/3880)). Thanks @coygeek, @kaoru0822-kitauji, @maxazure, @N0zoM1z0, @ShuaixinHuang.
|
||||
- **JavaScript relationships:** Distinguish `Map` reads from network endpoints, associate response endpoints only with writes that reach the read, and preserve child-process receiver ownership and effective stdio choices. Version comparisons keep relocation and unknown relationships distinct from application changes. ([fbfdb6a](https://github.com/morluto/rea/commit/fbfdb6abe9d663be0be146a15a489e33ccba0f68), [#4759](https://github.com/morluto/rea/pull/4759), [morluto/rea#4606](https://github.com/morluto/rea/issues/4606), [#3819](https://github.com/morluto/rea/pull/3819), [morluto/rea#3362](https://github.com/morluto/rea/issues/3362), [#4336](https://github.com/morluto/rea/pull/4336), [morluto/rea#3355](https://github.com/morluto/rea/issues/3355), [556504e](https://github.com/morluto/rea/commit/556504edcbb0f8cd9eaf81a91b18ff891b8f5457), [4d980d0](https://github.com/morluto/rea/commit/4d980d017622d3175d86aaf3b0dcfbdf4ca3d091)). Thanks @Abdooo2235, @coygeek, @kaoru0822-kitauji, @Xinglan233.
|
||||
- **JavaScript source admission:** Parse JavaScript with its own grammar, refuse FIFO recovery inputs without blocking, and report oversized or resource-skipped files with their actual capacity and coverage state. Malformed runtime events stay excluded rather than becoming policy-filtered facts. ([#3782](https://github.com/morluto/rea/pull/3782), [morluto/rea#3780](https://github.com/morluto/rea/issues/3780), [#3788](https://github.com/morluto/rea/pull/3788), [morluto/rea#3332](https://github.com/morluto/rea/issues/3332), [#4757](https://github.com/morluto/rea/pull/4757), [morluto/rea#4629](https://github.com/morluto/rea/issues/4629), [#3852](https://github.com/morluto/rea/pull/3852), [morluto/rea#3670](https://github.com/morluto/rea/issues/3670), [#4755](https://github.com/morluto/rea/pull/4755), [morluto/rea#4685](https://github.com/morluto/rea/issues/4685)). Thanks @andresaragon, @coygeek, @kaoru0822-kitauji, @sx4im.
|
||||
- **Browser capture:** Bound CDP handshakes and command responses, preserve malformed inventory uncertainty and partial cleanup diagnostics, and report cancellation after content reads and final cleanup. Accessibility comparisons require observed coverage; attached emulation teardown limits remain explicit. ([#3555](https://github.com/morluto/rea/pull/3555), [morluto/rea#3553](https://github.com/morluto/rea/issues/3553), [#3901](https://github.com/morluto/rea/pull/3901), [morluto/rea#3379](https://github.com/morluto/rea/issues/3379), [4cc044b](https://github.com/morluto/rea/commit/4cc044be68f813e96907e6cedcf422d0a3170de3), [#4406](https://github.com/morluto/rea/pull/4406), [morluto/rea#4403](https://github.com/morluto/rea/issues/4403), [#2893](https://github.com/morluto/rea/pull/2893), [0123d59](https://github.com/morluto/rea/commit/0123d5976828bfe621bd51daa4cf39fee48a1349), [2ff0867](https://github.com/morluto/rea/commit/2ff086796ad7b8f60397956750584e170dbe2910)). Thanks @coygeek, @dakjdakd, @kaoru0822-kitauji, @maxazure, @N0zoM1z0, @YunzhouLi-hub.
|
||||
- **Browser page facts:** Preserve URL path bytes when removing credentials, repeated document `Link` headers and complete COOP/COEP structured items. WebMCP discovery reads the actual schema properties and exact receiver path. ([#4731](https://github.com/morluto/rea/pull/4731), [df2fc25](https://github.com/morluto/rea/commit/df2fc251e318a1255309dba232f9f73c700777b6), [c360cfc](https://github.com/morluto/rea/commit/c360cfcd6d7901514b16dd6113eba2e0b0fdf422), [ebd5a82](https://github.com/morluto/rea/commit/ebd5a82c439f6deb3a06062225ce009eb03257cf), [38f92d1](https://github.com/morluto/rea/commit/38f92d108e3148cdf47e4ffbfe3de8a76e795d51)). Thanks @Abdooo2235, @geekwolf, @ShuaixinHuang.
|
||||
- **Electron runtime capture:** Keep native IPC listener and one-shot dispatch behavior, block synchronous shell effects during capture, refuse active scenarios when the inspect fuse is disabled, and preserve fuse/transport cleanup ownership. ([#4389](https://github.com/morluto/rea/pull/4389), [morluto/rea#4390](https://github.com/morluto/rea/issues/4390), [#4605](https://github.com/morluto/rea/pull/4605), [morluto/rea#4593](https://github.com/morluto/rea/issues/4593), [#4562](https://github.com/morluto/rea/pull/4562), [morluto/rea#4393](https://github.com/morluto/rea/issues/4393), [#3605](https://github.com/morluto/rea/pull/3605), [morluto/rea#3453](https://github.com/morluto/rea/issues/3453), [f9de413](https://github.com/morluto/rea/commit/f9de41371f7536e2e22ce6e43c9314fa2f238a30), [e7df00e](https://github.com/morluto/rea/commit/e7df00e80777757b329bbe8aba6d421d8892b9d5)). Thanks @Adolanium, @gensokyo-dev, @gkobayashi0, @HarsH-1877, @kaoru0822-kitauji, @keeponlight, @N0zoM1z0, @Nitin13-git, @xu91102.
|
||||
- **APK resource accuracy:** Retain completed tables and decode facts after interrupted or failed reads. Derive locale limits from resource configuration fields, disclose unselectable qualified tables, distinguish document failures from omitted entries, and bind Evidence to the decoded snapshot. ([34a88d6](https://github.com/morluto/rea/commit/34a88d6c1674710953e2c94bd0a17332cd69ca6f), [f5fcf52](https://github.com/morluto/rea/commit/f5fcf5216ffb3a266b8c00f6d9a0943051084ab5), [6385ef4](https://github.com/morluto/rea/commit/6385ef4355d7a794b3154ee38529cbddee583336), [98886ad](https://github.com/morluto/rea/commit/98886adddfcbd7b4f4236b3a9825f91878c3cb11), [dc2e811](https://github.com/morluto/rea/commit/dc2e8117b60c4481372ed89b077e6c1cf81ee653), [#4771](https://github.com/morluto/rea/pull/4771), [morluto/rea#4768](https://github.com/morluto/rea/issues/4768)). Thanks @abhay-2108, @gensokyo-dev, @Iyanuoluwa007, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Apktool guidance:** Retain the observed version and disclose the matching platform/release advisory windows without treating a version string as proof of decoder safety. Missing prerequisites and decode refusals identify the selected APK or launcher. ([d38f099](https://github.com/morluto/rea/commit/d38f099692394ec90c582f9f0a14b036b421b160), [63696d2](https://github.com/morluto/rea/commit/63696d277328f556c971d1fc9f81ae83128d468b), [cc038da](https://github.com/morluto/rea/commit/cc038da218cada68c87700477aba3db4739791ff)). Thanks @GovinduSasanka, @peter-research.
|
||||
- **Android method bodies:** Recover original bytecode bodies when Java source is omitted while preserving genuine native/abstract body absence and primary failures during retryable cleanup. ([63c65a2](https://github.com/morluto/rea/commit/63c65a22430a4bbc0c1807a0298edb867775001f), [742b6f5](https://github.com/morluto/rea/commit/742b6f587f3c4ef1b8212e7376d6f3a305b194dc), [9fa6d2b](https://github.com/morluto/rea/commit/9fa6d2b254cc390665974efb52463386a029e9fc)). Thanks @balkan16, @Chessing234.
|
||||
- **ADB observations and transfers:** Preserve display-scoped focus, distinguish missing declarations from absent focus, retain numeric physical density and mark unknown permission coverage partial. Transfers use consumed snapshot identity and refuse unknown destination existence; uninstall requests explicitly select user zero. ([7086768](https://github.com/morluto/rea/commit/7086768a27242a15daa93486aefbaacc3c891e78), [3d028db](https://github.com/morluto/rea/commit/3d028dbc74a465f6f8df1813a931fbb5362e7dc3), [#4074](https://github.com/morluto/rea/pull/4074), [morluto/rea#3942](https://github.com/morluto/rea/issues/3942), [#4663](https://github.com/morluto/rea/pull/4663), [#4665](https://github.com/morluto/rea/pull/4665), [1342127](https://github.com/morluto/rea/commit/1342127e91d4db8b4113a7a4e85be909bc96e1db), [61e6880](https://github.com/morluto/rea/commit/61e688070f48016b6d178cd04c02fcc2f33e19ad), [be0b2d1](https://github.com/morluto/rea/commit/be0b2d138c46bb951fc3c8ee57ef7b0d57fed312)). Thanks @Copilot, @coygeek, @gongfpp, @Tapraj-Pathak, @yankikucuk.
|
||||
- **Apple metadata:** Preserve exact plist/keyed-archive integers, negative zero, non-finite real identity and dictionary keys. Reject malformed XML before value decoding, retain undecodable dates as unknown and report unsupported OpenStep entries explicitly. ([#4324](https://github.com/morluto/rea/pull/4324), [morluto/rea#4292](https://github.com/morluto/rea/issues/4292), [2cf9c28](https://github.com/morluto/rea/commit/2cf9c28f27261b7e64af2ba7ecb4e23ea7714aee), [#4839](https://github.com/morluto/rea/pull/4839), [morluto/rea#4834](https://github.com/morluto/rea/issues/4834), [f2b7bbb](https://github.com/morluto/rea/commit/f2b7bbb31279a820f4cf32d439570f8e5381d010), [6c98708](https://github.com/morluto/rea/commit/6c98708793c23615de8053b2cd14d9f164b960c5), [78cf610](https://github.com/morluto/rea/commit/78cf6100f33ad2412c0a3d60b83f8a44a43fbbb5), [c7e9e09](https://github.com/morluto/rea/commit/c7e9e096d3e2444e4c251123b8e914e9c0941f4e), [#3793](https://github.com/morluto/rea/pull/3793), [#4105](https://github.com/morluto/rea/pull/4105), [morluto/rea#3892](https://github.com/morluto/rea/issues/3892)). Thanks @coygeek, @kaoru0822-kitauji, @mertkayacs, @pablodiazjorge, @pollychen-lab, @xu91102.
|
||||
- **Apple application structure:** Derive bundle components and launchd domains from their owning directories, retain unavailable components, and select only exact `Assets.car` names as asset catalogs. Interface Builder keeps unresolved classes and counts hierarchy records beyond the returned object page. ([149a0fc](https://github.com/morluto/rea/commit/149a0fc06c3c67ee45624e6da14dfb0754473758), [5d6029e](https://github.com/morluto/rea/commit/5d6029ea62a65ec34fd739ee40223c77c6fdb63b), [c192c58](https://github.com/morluto/rea/commit/c192c58815ddeaf5cf30159e7082c742c0bc36b7), [#4094](https://github.com/morluto/rea/pull/4094), [morluto/rea#3914](https://github.com/morluto/rea/issues/3914), [#4118](https://github.com/morluto/rea/pull/4118), [morluto/rea#3958](https://github.com/morluto/rea/issues/3958), [#4162](https://github.com/morluto/rea/pull/4162), [morluto/rea#3950](https://github.com/morluto/rea/issues/3950)). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Native names and layout:** Keep valid Objective-C records beside malformed neighbors, preserve leading U+FEFF and repeated load-command fields, and select exact arm64e.x1 slices. PE resource ownership and NativeAOT reads stay within declared image/header ranges. ([#3833](https://github.com/morluto/rea/pull/3833), [morluto/rea#3336](https://github.com/morluto/rea/issues/3336), [#4549](https://github.com/morluto/rea/pull/4549), [morluto/rea#4540](https://github.com/morluto/rea/issues/4540), [morluto/rea#4541](https://github.com/morluto/rea/issues/4541), [#4730](https://github.com/morluto/rea/pull/4730), [#4048](https://github.com/morluto/rea/pull/4048), [morluto/rea#4008](https://github.com/morluto/rea/issues/4008), [#3500](https://github.com/morluto/rea/pull/3500), [morluto/rea#3389](https://github.com/morluto/rea/issues/3389), [#4463](https://github.com/morluto/rea/pull/4463), [morluto/rea#4373](https://github.com/morluto/rea/issues/4373), [morluto/rea#4455](https://github.com/morluto/rea/issues/4455), [3fc0075](https://github.com/morluto/rea/commit/3fc007534bb2104d662b43cc712281f9d1d6a7d3), [4a076f3](https://github.com/morluto/rea/commit/4a076f368c62bdfb19415accad342e03a38d1005)). Thanks @coygeek, @Iyanuoluwa007, @jayinvers, @kaoru0822-kitauji, @kevinjosh10, @maxazure, @ShuaixinHuang.
|
||||
- **Native analysis providers:** Keep Ghidra byte matches within examined coverage, retain PE PDB/function-boundary limitations and preserve configured MIPS choices. Attached IDA refuses universal Mach-O rather than silently choosing a slice; request timeout identity and captured output survive adapter handling. ([#4531](https://github.com/morluto/rea/pull/4531), [#2933](https://github.com/morluto/rea/pull/2933), [morluto/rea#2259](https://github.com/morluto/rea/issues/2259), [01a1b3f](https://github.com/morluto/rea/commit/01a1b3fda6a7dacd24a0b10619d41d12153080d4), [50dd9fc](https://github.com/morluto/rea/commit/50dd9fcad1efb47cb404192c30bdf190dcdaca58), [#4765](https://github.com/morluto/rea/pull/4765), [morluto/rea#3871](https://github.com/morluto/rea/issues/3871), [c0cfccb](https://github.com/morluto/rea/commit/c0cfccb45fe43e7eeff18e56c64caa5b121988f9)). Thanks @BeLit-Development-2, @coygeek, @kaoru0822-kitauji, @N0zoM1z0, @ShuaixinHuang.
|
||||
- **Recorded crashes:** Prevent GDB from discovering an executable named by an untrusted core, reject core mapping paths that escape the isolated sysroot, and preserve pwntools worker ownership across launcher exec. Completed decode facts and cancellation reasons remain available through failed final cleanup. ([5bf4fff](https://github.com/morluto/rea/commit/5bf4fffc1b835130ddb16f96db9ce13b639591db), [b33c0df](https://github.com/morluto/rea/commit/b33c0df02e93fabca00a44f2de625b1a63637ee9), [#4435](https://github.com/morluto/rea/pull/4435), [morluto/rea#4011](https://github.com/morluto/rea/issues/4011)). Thanks @coygeek, @N0zoM1z0.
|
||||
- **Cutter and Rizin:** Read Cutter’s active document from core configuration and attribute its host version correctly. Keep interrupted Rizin frames and output whitespace before completion markers, with exact command and cleanup failure attribution. ([#4507](https://github.com/morluto/rea/pull/4507), [morluto/rea#4497](https://github.com/morluto/rea/issues/4497), [#4514](https://github.com/morluto/rea/pull/4514), [morluto/rea#4498](https://github.com/morluto/rea/issues/4498), [#4459](https://github.com/morluto/rea/pull/4459), [morluto/rea#4015](https://github.com/morluto/rea/issues/4015), [d5fc7e6](https://github.com/morluto/rea/commit/d5fc7e6ebf9af050081fc4c6e18b9079cfaa5cdc), [199396f](https://github.com/morluto/rea/commit/199396f61c8f61da326d059606a992304a760147)). Thanks @coygeek, @N0zoM1z0, @Xander-AJ.
|
||||
- **Windows cleanup:** Retain unpublished runtimes after failed removal, handle read-only attributes and sharing conflicts, preserve primary snapshot failures and honor caller environment overrides. ([f78d137](https://github.com/morluto/rea/commit/f78d137b42741a0ed5fb8ac161286f9a50220c4d), [d69acc3](https://github.com/morluto/rea/commit/d69acc3455c33f90a2fc4381db43d38b6af50622), [0679a79](https://github.com/morluto/rea/commit/0679a7972b4f2b02eee6a86a44f50f4c2967d3cb), [5f9ac2c](https://github.com/morluto/rea/commit/5f9ac2c98a6fe11988c41afb737df9b6d5c3a1b3), [1bbf4bd](https://github.com/morluto/rea/commit/1bbf4bd16ca134cdb2187eaf29291cae90d14a57)). Thanks @DeryFerd.
|
||||
- **Managed metadata:** Compare nominal signatures and declaring identities across builds, preserve empty names without display substitutions, verify ABI-prefixed Mach-O exports exactly, and charge cached metadata once. Metadata errors report artifact file offsets; mixed-mode assemblies retain their actual boundary classification. ([#3059](https://github.com/morluto/rea/pull/3059), [#3079](https://github.com/morluto/rea/pull/3079), [#3853](https://github.com/morluto/rea/pull/3853), [morluto/rea#3352](https://github.com/morluto/rea/issues/3352), [#3974](https://github.com/morluto/rea/pull/3974), [morluto/rea#3961](https://github.com/morluto/rea/issues/3961), [#3491](https://github.com/morluto/rea/pull/3491), [morluto/rea#3449](https://github.com/morluto/rea/issues/3449), [#4418](https://github.com/morluto/rea/pull/4418), [morluto/rea#4372](https://github.com/morluto/rea/issues/4372), [#3516](https://github.com/morluto/rea/pull/3516), [morluto/rea#3366](https://github.com/morluto/rea/issues/3366)). Thanks @8rulerstar, @coygeek, @duckhunt113, @Hahaa909, @JayOfTheKeyboard, @kaoru0822-kitauji, @keeponlight, @N0zoM1z0, @tomyrms.
|
||||
- **Archive and filesystem identity:** Reject colliding extraction paths before writing, classify ZIP offsets beyond EOF as malformed, normalize ZIP current-directory prefixes once, and preserve symlink and unreadable-path provenance. A directory named `*.map` is no longer mistaken for a source-map file. ([862a0b2](https://github.com/morluto/rea/commit/862a0b2631785492f8dc20c8acd396646c545fd1), [c209dcb](https://github.com/morluto/rea/commit/c209dcb209118069f123e789949eeba1ef78853e), [6b1c36e](https://github.com/morluto/rea/commit/6b1c36e336eeacbb7159b211e687b4b84f99b9f5), [25c2222](https://github.com/morluto/rea/commit/25c22222abe7a681491bdb4d7316250fdcf32973), [d6e4fe4](https://github.com/morluto/rea/commit/d6e4fe4a4dd8914ec3db50d2a7fc3d0802649262), [#4131](https://github.com/morluto/rea/pull/4131), [morluto/rea#3909](https://github.com/morluto/rea/issues/3909)). Thanks @Adkid-Zephyr, @coygeek, @jackie-cqz, @kaoru0822-kitauji, @maxazure, @XY-code1.
|
||||
- **Evidence and reconstruction:** Authenticate all retained inventory substeps, preserve source-attributed caveats in composed workflows, and require comparable cited Evidence before declaring reconstruction coverage or readiness. Missing Evidence and residual replay uncertainty stay explicit. ([9ac91e0](https://github.com/morluto/rea/commit/9ac91e0c1e13e91cd21341635ad5be89b0e8642d), [c4608a2](https://github.com/morluto/rea/commit/c4608a29620ea3e7bb8259b47357b942fe4e4709), [#4075](https://github.com/morluto/rea/pull/4075), [morluto/rea#3357](https://github.com/morluto/rea/issues/3357), [#3526](https://github.com/morluto/rea/pull/3526), [morluto/rea#3363](https://github.com/morluto/rea/issues/3363), [153017d](https://github.com/morluto/rea/commit/153017d750322188c1a112b2284c1b8065c67320)). Thanks @coygeek, @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0, @rudycelekli.
|
||||
- **Offline data:** Preserve exact UTC packet-capture instants, SQLite column identity and snapshot admission failures, and cooked WASM directive literals. Cleanup cancellation retains completed EVM/WASM observations rather than losing them. ([796faa0](https://github.com/morluto/rea/commit/796faa0e4e6bf745bcdc33acef65af8ccfc3622d), [748ac7f](https://github.com/morluto/rea/commit/748ac7f0df340f0d0569eca2e803a59cd51ffdbd), [bc51d0e](https://github.com/morluto/rea/commit/bc51d0ea5df7dcee1fcc42dfae3ab0c62c9cca5e), [4f87f02](https://github.com/morluto/rea/commit/4f87f02bb19bf77349a683a5bcb2da2d1da427d0), [d694aef](https://github.com/morluto/rea/commit/d694aef227e90164e6654ea9e5ce6a854d099404), [321cacb](https://github.com/morluto/rea/commit/321cacb84a020f447e1110df5c58fa71d99b7bf1)). Thanks @himamaheswarreddy, @mertkayacs, @rollroyces, @viniciusdsandrade.
|
||||
- **Kuna recovery:** Share concurrent single-function requests and cache repeats, validate warm exports once, stream string inventories beyond the former fixed buffer ceiling, and keep unproven processes in their run slots until close confirms release. Failure messages distinguish final buffer refusals from configurable deadlines. ([#4045](https://github.com/morluto/rea/pull/4045), [#4169](https://github.com/morluto/rea/pull/4169), [#4049](https://github.com/morluto/rea/pull/4049), [#4318](https://github.com/morluto/rea/pull/4318), [#4052](https://github.com/morluto/rea/pull/4052), [#4098](https://github.com/morluto/rea/pull/4098), [#4343](https://github.com/morluto/rea/pull/4343)). Thanks @kaoru0822-kitauji.
|
||||
- **Flutter snapshots:** Check symbol-table ranges against loadable segments and retain completed inspection facts when cancellation occurs during snapshot cleanup. ([#3859](https://github.com/morluto/rea/pull/3859), [morluto/rea#3346](https://github.com/morluto/rea/issues/3346), [#4766](https://github.com/morluto/rea/pull/4766), [morluto/rea#4717](https://github.com/morluto/rea/issues/4717), [#4762](https://github.com/morluto/rea/pull/4762), [morluto/rea#4640](https://github.com/morluto/rea/issues/4640)). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Native metadata recovery:** Keep unknown Objective-C category classes, empty protocol names, undecoded Swift aliases and unfinished property types explicit instead of discarding neighboring valid records. Preserve unnamed segment identity and exclude a dylib’s own install name from dependencies. ([#4093](https://github.com/morluto/rea/pull/4093), [morluto/rea#4012](https://github.com/morluto/rea/issues/4012), [#4078](https://github.com/morluto/rea/pull/4078), [morluto/rea#3873](https://github.com/morluto/rea/issues/3873), [#4085](https://github.com/morluto/rea/pull/4085), [morluto/rea#3934](https://github.com/morluto/rea/issues/3934), [#3825](https://github.com/morluto/rea/pull/3825), [morluto/rea#3345](https://github.com/morluto/rea/issues/3345), [#4051](https://github.com/morluto/rea/pull/4051), [morluto/rea#3875](https://github.com/morluto/rea/issues/3875), [#4050](https://github.com/morluto/rea/pull/4050), [morluto/rea#3872](https://github.com/morluto/rea/issues/3872)). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Document selection:** Preserve explicitly selected plist paths and bundle defaults, and only use document fallback after confirming its applicability. Finalization keeps completed selection facts and cancellation outcomes. ([e6c4877](https://github.com/morluto/rea/commit/e6c487799bb64f559e66b69be1863e6ec5187548), [#3774](https://github.com/morluto/rea/pull/3774), [9df559b](https://github.com/morluto/rea/commit/9df559b7e712298a761ff26e9caae8d659b7e54b)). Thanks @kaoru0822-kitauji.
|
||||
- **Capture and comparison accuracy:** Admit schema-valid HAR numeric values while retaining exact-publication limits, preserve empty URL path segments, and keep unrelated process traces and unnamed reference endpoints distinct or unknown. ([#3159](https://github.com/morluto/rea/pull/3159), [morluto/rea#3145](https://github.com/morluto/rea/issues/3145), [#3792](https://github.com/morluto/rea/pull/3792), [morluto/rea#3307](https://github.com/morluto/rea/issues/3307), [#4178](https://github.com/morluto/rea/pull/4178), [morluto/rea#3931](https://github.com/morluto/rea/issues/3931), [#3809](https://github.com/morluto/rea/pull/3809), [morluto/rea#3388](https://github.com/morluto/rea/issues/3388)). Thanks @coygeek, @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0, @pablodiazjorge, @xu91102.
|
||||
- **Evidence publication:** Recheck staged output identity after its final write, classify missing review Evidence and duplicate ledger entries as caller input, and preserve the selected freshness requirement in comparison parameters. ([#4764](https://github.com/morluto/rea/pull/4764), [morluto/rea#4613](https://github.com/morluto/rea/issues/4613), [#4452](https://github.com/morluto/rea/pull/4452), [morluto/rea#4444](https://github.com/morluto/rea/issues/4444), [#4065](https://github.com/morluto/rea/pull/4065), [morluto/rea#4000](https://github.com/morluto/rea/issues/4000)). Thanks @amyanger, @coygeek, @kaoru0822-kitauji, @Rinkia, @Swatantra-66.
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
- **Saved Evidence:** Evidence and analysis snapshots now require explicit nullable `relationship_coverage` in their identity commitments. Earlier records whose digest omitted null coverage are incompatible even when the serialized field is explicitly null. Artifact inventories require scoped coverage and explicit `integrity_contradictions`; obsolete `coverage_status` and prose-based coverage inference are removed. Regenerate old Evidence/snapshots and inventory observations with this release: saved files are not migrated and omitted facts are not filled with guessed defaults. ([db8128d](https://github.com/morluto/rea/commit/db8128dc0d1c4f731f3d19877fa00d25cf17f657), [0405c9d](https://github.com/morluto/rea/commit/0405c9d361de6d904dfa16cc0b6d8350407f2c9b), [a9588be](https://github.com/morluto/rea/commit/a9588be4ebdd20eeda975cb63ffcdb1cd868b340)).
|
||||
- **Browser capture results:** Consumers must retain resolved capture limits, event families and omission facts. Set desired budgets through the supported capture options; omitted caller inputs still select defaults, but stored observations must report the resolved values. Admitted content reads queue behind bounded concurrency. ([57a23f0](https://github.com/morluto/rea/commit/57a23f0a2da0fdc3835e203718ded8acea5a34e6)).
|
||||
- **Android version codes:** `inspect_adb_package.version_code` is a decimal string rather than a JSON number. Read it as a string, or use `BigInt` for numeric comparison. ([#3485](https://github.com/morluto/rea/pull/3485), [morluto/rea#3368](https://github.com/morluto/rea/issues/3368), [7666a48](https://github.com/morluto/rea/commit/7666a482b69dc7cabdbac3bbaf89a43e32300192)). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Native UI failures:** Failed or cancelled native UI scenarios now return typed errors with the collected report in `partial_observation`. Handle the error path and read that report instead of expecting a successful result containing failed steps. ([d909919](https://github.com/morluto/rea/commit/d9099190ab3a819bd3a9fd62a8714cef398016d5)).
|
||||
- **Provider status:** `binary_session` candidate availability can be unknown during owner finalization. Preserve that state rather than treating it as unavailable; internal consumers must await the asynchronous session status API. ([150e996](https://github.com/morluto/rea/commit/150e996a86f2bec4982ba87f37bf502a8d5e8214)).
|
||||
- **Swift symbol locations:** `swift_symbols` entries may have a null `address` when the provider supplies no location. Handle unknown addresses; optional `evidence.reported_address` retains the original provider value. ([8116dc0](https://github.com/morluto/rea/commit/8116dc06512b4609d8db63cebc9f2ff8cb964151)).
|
||||
- **Linux Hopper admission:** Older builds previously admitted by a fixed allowlist need retained publisher-signed package evidence or an approved update to the current public demo. Update explicit `HOPPER_LAUNCHER_PATH` selections when migrating; the live feed offers only its current release. The official RPM metadata mismatch remains unresolved and is refused. ([#3590](https://github.com/morluto/rea/pull/3590), [morluto/rea#3577](https://github.com/morluto/rea/issues/3577), [70cd4cb](https://github.com/morluto/rea/commit/70cd4cbfb849b61f05d97d2690372a9782831426)). Thanks @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
|
||||
Full contribution record: [571 commits and 236 merged PRs](https://github.com/morluto/rea/blob/rea-agents-6.6.0/docs/releases/6.6.0.contributions.json).
|
||||
|
||||
## [6.5.0](https://github.com/morluto/rea/compare/rea-agents-6.4.0...rea-agents-6.5.0) (2026-10-13)
|
||||
|
||||
Provider prerequisites and recorded verification scope are listed in [the testing guide](docs/testing.md).
|
||||
|
||||
### Highlights
|
||||
|
||||
- Analyze selected JavaScript files and recover cross-bundle relationships without expanding the selected source scope.
|
||||
- Read compact summaries and page retained Evidence across binary, managed, Android, Dart, WASM and network workflows.
|
||||
- Inspect SQLite snapshots, packet and signal recordings, WebAssembly, Rust dependency metadata and Electron fuses offline.
|
||||
- Keep completed Evidence and partial observations available when analysis or cleanup fails.
|
||||
|
||||
### Changes
|
||||
|
||||
- **JavaScript source selection:** analyze exact application paths without expanding imports into the selected scope. Unselected files retain identity and explicit unknown coverage; summary delivery remains separate from source selection. ([#1944](https://github.com/morluto/rea/pull/1944), [issue morluto/rea#1848](https://github.com/morluto/rea/issues/1848)). Thanks @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Code-split recovery:** recover related bundles from multiple selected inputs, retaining per-file provenance and downstream cross-chunk imports. Recorded CLI/MCP coverage is Linux x64 with Wakaru 1.14.0 and source-owned esbuild/webpack fixtures; other hosts, releases and arbitrary application equivalence remain unverified. ([#1686](https://github.com/morluto/rea/pull/1686), [issue morluto/rea#1605](https://github.com/morluto/rea/issues/1605)). Thanks @JingHao-Leon, @pionxzh.
|
||||
- **Package imports:** resolve exact package import aliases using the selected loader and package ancestry, rather than guessing sibling files. ([#1862](https://github.com/morluto/rea/pull/1862), [issue morluto/rea#1838](https://github.com/morluto/rea/issues/1838)). Thanks @AWUQAQ, @mvanhorn.
|
||||
- **Retained analysis views:** opt into compact inventory/WASM, managed metadata, Android manifest, Dart AOT, Mach-O and EVM summaries, then page retained Evidence without rerunning analysis. Summary delivery preserves the complete recorded result and its original coverage. ([#2122](https://github.com/morluto/rea/pull/2122), [#2193](https://github.com/morluto/rea/pull/2193), [#2413](https://github.com/morluto/rea/pull/2413), [#2438](https://github.com/morluto/rea/pull/2438), [#2455](https://github.com/morluto/rea/pull/2455), [#2634](https://github.com/morluto/rea/pull/2634), [#2308](https://github.com/morluto/rea/pull/2308), [9a18251](https://github.com/morluto/rea/commit/9a18251426f891e6a24856b715ef8002ab8b9610)). Thanks @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Managed assemblies:** Select declaring types when inspecting members, decode inspected `ldstr` literals with independent coverage, and page retained member summaries. Call and field targets expose `target_declaring_type` and `field_declaring_type`, including supported generic method targets; unresolved metadata remains unknown. ([#1706](https://github.com/morluto/rea/pull/1706), [#2125](https://github.com/morluto/rea/pull/2125), [issue morluto/rea#2121](https://github.com/morluto/rea/issues/2121), [#2162](https://github.com/morluto/rea/pull/2162), [issue morluto/rea#2158](https://github.com/morluto/rea/issues/2158), [#3061](https://github.com/morluto/rea/pull/3061), [issue morluto/rea#3010](https://github.com/morluto/rea/issues/3010), [#2163](https://github.com/morluto/rea/pull/2163), [issue morluto/rea#2160](https://github.com/morluto/rea/issues/2160)). Thanks @kaoru0822-kitauji, @N0zoM1z0, @Nelglor, @skoodster.
|
||||
- **Packet captures:** inspect saved packets with bounded TShark output, compact capture summaries and retained pages; discover installed protocol/field definitions. Recorded real-engine coverage uses Linux and Wireshark 4.6.6; native Windows/macOS and Bun behavior remain unverified. ([#2318](https://github.com/morluto/rea/pull/2318), [#2340](https://github.com/morluto/rea/pull/2340), [#2496](https://github.com/morluto/rea/pull/2496)). Thanks @3QL, @N0zoM1z0, @Olorinm.
|
||||
- **Recorded signals:** decode existing recordings and discover installed decoders/options through caller-supplied sigrok-cli 0.7.2. This is Linux-only and requires util-linux `prlimit`; it acquires no hardware samples. ([#2339](https://github.com/morluto/rea/pull/2339), [#2487](https://github.com/morluto/rea/pull/2487)). Thanks @N0zoM1z0, @Olorinm.
|
||||
- **Web captures:** derive an inferred method/origin/path-template API surface from retained HAR data including observed query-parameter names while retaining unknowns; query values, headers and bodies remain excluded. ([#1891](https://github.com/morluto/rea/pull/1891), [#2982](https://github.com/morluto/rea/pull/2982), [issue morluto/rea#2913](https://github.com/morluto/rea/issues/2913)). Thanks @mvanhorn, @wunianze666-netizen, @zhangguiping-xydt.
|
||||
- **QUIC captures:** inspect retained aioquic 1.5.0 JSON qlog 0.3 objects and page traces/streams/observations. Other qlog layouts remain unsupported; overlap and loss declarations do not prove retransmission or network loss. ([#2277](https://github.com/morluto/rea/pull/2277)). Thanks @LuisTaffelli, @N0zoM1z0.
|
||||
- **Fiddler captures:** inspect native SAZ archives with retained HTTP provenance and bounded decoding. Recorded producer coverage is the pinned public encoding corpus; current Fiddler Everywhere variants and Bun-native execution remain unverified. ([#2409](https://github.com/morluto/rea/pull/2409)). Thanks @N0zoM1z0, @zahhak.
|
||||
- **SQLite snapshots:** inspect an explicitly selected offline database with table/column selection and bounded Evidence. This operation requires Node 24.x >=24.15 or 26+ with `DatabaseSync.limits`; supported Node 22 and earlier 24 runtimes cannot use it. A live database/WAL copy does not establish an atomic snapshot. ([#2271](https://github.com/morluto/rea/pull/2271)). Thanks @N0zoM1z0, @Y0UW1SH.
|
||||
- **WASM artifacts:** Inspect local modules with bring-your-own WABT 1.0.42 without executing them. Compare imports/exports from two complete core-module inspection Evidence records without requiring an active WABT installation; unsupported signatures and duplicate identities remain unknown, and comparisons do not establish runtime compatibility. Recorded real-tool inspection coverage is macOS arm64. ([#1533](https://github.com/morluto/rea/pull/1533), [issue morluto/rea#884](https://github.com/morluto/rea/issues/884), [#2464](https://github.com/morluto/rea/pull/2464), [issue morluto/rea#2356](https://github.com/morluto/rea/issues/2356)). Thanks @matbrgz, @N0zoM1z0, @thangldw.
|
||||
- **Go binaries:** inspect embedded build metadata, including file-backed unmapped Mach-O debug segments. ([#1363](https://github.com/morluto/rea/pull/1363), [7fb097d](https://github.com/morluto/rea/commit/7fb097d72c9931dd5dfae481ad2b9d3bb6029f15)). Thanks @Y0UW1SH.
|
||||
- **Flutter APKs:** identify snapshot/engine builds and inspect AOT structure without an external engine. Recorded real APK coverage uses the documented Flutter Gallery fixture; macOS/Windows and complete pool semantics remain unverified, and pool coverage stays partial. ([#1582](https://github.com/morluto/rea/pull/1582)). Thanks @3QL.
|
||||
- **HarmonyOS packages:** inventory HAP/HSP/App Pack structures without executing code. The recorded real workflow covers one pinned HAP through repository-built CLI/MCP; installed-package and real HSP/App Pack coverage remain unverified. ([#1447](https://github.com/morluto/rea/pull/1447)). Thanks @BaaRai.
|
||||
- **Ghidra raw inputs and references:** compatible Linux/macOS Ghidra profiles accept caller-selected raw architecture/entry information and expose typed incoming references at exact addresses. Windows Ghidra P0 admits the raw NMOS 6502 profile described below; other raw targets and the incoming-reference facet remain outside Windows P0. ([#1868](https://github.com/morluto/rea/pull/1868), [issue morluto/rea#1836](https://github.com/morluto/rea/issues/1836), [#1715](https://github.com/morluto/rea/pull/1715), [issue morluto/rea#1245](https://github.com/morluto/rea/issues/1245)). Thanks @bugstan, @casesolved-co-uk, @mvanhorn, @N0zoM1z0.
|
||||
- **Ghidra MIPS:** Add bounded MIPS32r2/o32 relocatable objects, soft-float integer-register storage, admitted MIPS16 shared ELF, and a narrowly admitted recordless legacy MIPS32 executable profile. The latter pins an endian-matched 32-bit language and records inferred o32 as an analysis assumption. Admission follows each documented ABI/flag/relocation boundary. ([#1949](https://github.com/morluto/rea/pull/1949), [issue morluto/rea#1937](https://github.com/morluto/rea/issues/1937), [#1950](https://github.com/morluto/rea/pull/1950), [issue morluto/rea#1938](https://github.com/morluto/rea/issues/1938), [#1991](https://github.com/morluto/rea/pull/1991), [issue morluto/rea#1851](https://github.com/morluto/rea/issues/1851), [#1898](https://github.com/morluto/rea/pull/1898), [issue morluto/rea#1620](https://github.com/morluto/rea/issues/1620)). Thanks @AurelioB, @BaaRai, @bqw371, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Ghidra RISC-V:** admit little-endian RV32/RV64 `ET_EXEC` ELF declaring the supported double-float ABI. Relocatable/shared, big-endian and other ABI profiles remain refused; see [the profile and recorded lane](docs/ghidra-riscv.md). ([#2151](https://github.com/morluto/rea/pull/2151)). Thanks @NIU-123370.
|
||||
- **PSP static ELF profile:** the Ghidra profile is Linux x64, bounded little-endian ELF32 `ET_EXEC` with the documented PSP/ABI declarations and a compatible caller-installed Allegrex extension. PRX, shared/relocatable images, PBP/ISO/CSO and runtime emulation are outside scope. Historical extension experiments are separate from public-provider verification; real public Ghidra PSP coverage remains unverified. ([#1599](https://github.com/morluto/rea/pull/1599)). Thanks @jaelliot.
|
||||
- **Ghidra analysis settings:** apply validated analyzer overrides before analysis while retaining the selected analysis profile. ([#1913](https://github.com/morluto/rea/pull/1913)). Thanks @akushonkamen, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Attached native IDA MCP:** add an optional native bridge alongside existing legacy/headless profiles. Recorded native runs use Linux IDA Free 9.4 with a navigation correction and Windows IDA Pro 9.3 with a loopback-listener change; stock native switching, native macOS and Windows IDA Free remain unverified. Attached databases remain read-only and are never saved or closed. ([#1544](https://github.com/morluto/rea/pull/1544)). Thanks @imissapixel, @N0zoM1z0.
|
||||
- **Frida instrumentation:** add explicit local/remote target selection, caller-supplied scripts and retained messages, with script-read/input diagnostics and bounded message eviction. Recorded real coverage is the Linux local Node marker workflow; macOS, Windows and remote devices/servers remain unverified. Instrumentation executes foreign code and is destructive, not read-only. ([#1359](https://github.com/morluto/rea/pull/1359), [#2211](https://github.com/morluto/rea/pull/2211), [issue morluto/rea#2209](https://github.com/morluto/rea/issues/2209), [#2217](https://github.com/morluto/rea/pull/2217), [issue morluto/rea#2213](https://github.com/morluto/rea/issues/2213), [#2628](https://github.com/morluto/rea/pull/2628)). Thanks @dandgabr, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Client setup:** register Qoder, Trae, ZCode, DeepSeek Harness, Kimi Code, Factory Droid and WorkBuddy through the existing scoped setup flow. DeepSeek is included from 6.5.0; the historical npm 6.4 check did not cover it. Live Trae and DeepSeek API integration remain unverified. ([#1564](https://github.com/morluto/rea/pull/1564), [#1572](https://github.com/morluto/rea/pull/1572), [#1573](https://github.com/morluto/rea/pull/1573), [#1929](https://github.com/morluto/rea/pull/1929), [issue morluto/rea#1839](https://github.com/morluto/rea/issues/1839), [#2744](https://github.com/morluto/rea/pull/2744), [#2309](https://github.com/morluto/rea/pull/2309), [#3041](https://github.com/morluto/rea/pull/3041), [issue morluto/rea#3040](https://github.com/morluto/rea/issues/3040)). Thanks @0xRafie, @BaaRai, @cainiao1992, @IvenKooLab, @whoishaha, @ysntony.
|
||||
- **Website:** Add maintained language editions, persistent/system-aware themes, keyboard styling and nested Evidence indentation, plus the reverse-engineering models essay. A simpler home page and beginner journey connect platform prerequisites, client/provider setup and a supplied first investigation. ([#1779](https://github.com/morluto/rea/pull/1779), [issue morluto/rea#1022](https://github.com/morluto/rea/issues/1022), [#1699](https://github.com/morluto/rea/pull/1699), [#1714](https://github.com/morluto/rea/pull/1714), [#1894](https://github.com/morluto/rea/pull/1894), [#2250](https://github.com/morluto/rea/pull/2250), [#2432](https://github.com/morluto/rea/pull/2432), [#2732](https://github.com/morluto/rea/pull/2732), [#3274](https://github.com/morluto/rea/pull/3274)). Thanks @CassieFox23, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
|
||||
- **Rust dependency metadata:** Inspect embedded cargo-auditable package/dependency records in ELF, PE and thin Mach-O without executing the file. Missing metadata and inferred compiler hashes retain explicit limitations; universal Mach-O requires a selected thin slice. ([#2456](https://github.com/morluto/rea/pull/2456)). Thanks @mvanhorn.
|
||||
- **Electron fuses:** Read packaged fuse bytes and per-slice runtime implications without launching or modifying the app. Missing, truncated and unknown wires remain explicit; fuse observations do not prove other Inspector-enablement paths. ([#2399](https://github.com/morluto/rea/pull/2399)). Thanks @kaoru0822-kitauji, @mvanhorn, @N0zoM1z0.
|
||||
- **Ghidra PowerPC:** Admit bounded ELF32 big-endian `ET_EXEC` PowerPC with an explicit language profile on supported Linux/macOS hosts. PPC64, DOL and other variants remain outside this lane; see [the profile and verification prerequisites](docs/ghidra-ppc.md). ([#1747](https://github.com/morluto/rea/pull/1747)). Thanks @arielfikru.
|
||||
|
||||
- **Reference-source summaries:** Use CLI `import-reference-source --detail summary` for compact manifest, count, state and path-outline output. The complete graph remains available and is still the default; hidden outline entries retain separate coverage counts. ([#2768](https://github.com/morluto/rea/pull/2768), [issue morluto/rea#2442](https://github.com/morluto/rea/issues/2442)). Thanks @matbrgz, @To3akaRin.
|
||||
|
||||
- **Recorded AArch64 crashes:** Decode 34 ABI register values per thread from supported Linux ELF64 little-endian core notes using the documented caller-provided Python dependencies. These are recorded facts, not proof of current process or library identity. ([#2505](https://github.com/morluto/rea/pull/2505), [issue morluto/rea#2353](https://github.com/morluto/rea/issues/2353)). Thanks @matbrgz, @NIU-123370.
|
||||
|
||||
- **Ghidra byte search:** Search initialized analyzed memory using wildcards, bit masks, address bounds and paged results. Unloaded, overlay and uninitialized blocks retain skipped coverage; this is not a raw-file search. ([#2341](https://github.com/morluto/rea/pull/2341)). Thanks @mvanhorn.
|
||||
|
||||
- **Raw NMOS 6502 images:** Analyze explicitly selected headerless images with Ghidra, including Windows P0, using a caller-selected entry and source mapping within the 16-bit address space. Cartridge parsing, reset-vector inference and 65C02/65816 are outside this profile. ([#2079](https://github.com/morluto/rea/pull/2079)). Thanks @Zhong-fan.
|
||||
|
||||
- **Swift String observations:** Ghidra function dossiers derive supported AArch64 Swift String literals from constant-register pairs at recognized use sites, retaining instruction/register provenance and explicit omissions. This is a bounded derivation, not a complete string inventory. ([#2049](https://github.com/morluto/rea/pull/2049), [issue morluto/rea#2030](https://github.com/morluto/rea/issues/2030)). Thanks @thefolahan.
|
||||
|
||||
- **Headless IDA procedure lookup:** Resolve addresses to canonical containing-procedure entries and identify the session’s admitted program document in the modern headless profile. Incomplete surveys remain errors; attached profiles do not advertise this containment capability. ([#2374](https://github.com/morluto/rea/pull/2374), [issue morluto/rea#2335](https://github.com/morluto/rea/issues/2335)). Thanks @nikolay-dementiev.
|
||||
|
||||
- **Passive browser attachment:** Six existing passive browser tools accept an explicit-port loopback browser WebSocket endpoint when HTTP target discovery is unavailable. Existing consent and target/origin checks remain required; this does not launch a browser or enable runtime instrumentation. ([#2346](https://github.com/morluto/rea/pull/2346)). Thanks @all-black-493.
|
||||
|
||||
- **Android DEX references:** Retain exact method descriptors and individual direct-invoke observations with source method, DEX entry, opcode and code-unit offset. These are static references; unavailable instruction facts remain separate from method-level usage totals. ([#2439](https://github.com/morluto/rea/pull/2439)). Thanks @LcokHcak.
|
||||
|
||||
- **Offline EVM metadata:** Retain bounded, lossless facts from plausible terminal Solidity CBOR root-map candidates. Candidates do not prove Solidity origin or source/compiler identity, and linked metadata is not fetched. ([#2790](https://github.com/morluto/rea/pull/2790)). Thanks @matbrgz.
|
||||
|
||||
### Fixes
|
||||
|
||||
- **JavaScript loader and export identity:** Respect import/require conditions and unavailable package metadata, keep export links separate from writes, and resolve default identifier exports to the value captured at the export statement. ([7e5cffa](https://github.com/morluto/rea/commit/7e5cffa2ae9dfc9afaeca0715110d4dd41c0dcdd), [322506d](https://github.com/morluto/rea/commit/322506d0c0d39d3cd1e2e1c285c2cc23cfa73ee7), [81f7638](https://github.com/morluto/rea/commit/81f763820319a762f003cf95651ed14ce9326a4c), [dae1e55](https://github.com/morluto/rea/commit/dae1e55381f0d6cfee7f2082d5d27b938e55704c)). Thanks @paoValle, @VarunGore36, @xu91102.
|
||||
- **JavaScript traces:** Retain nested bridge calls and surviving early-return predicates. Honor node-ID match options, match route seeds against source values, and keep ambiguous runtime candidates unknown. ([0351328](https://github.com/morluto/rea/commit/0351328e54ce70ea3f2814482e76b132774aff48), [5031099](https://github.com/morluto/rea/commit/503109995f9dcf8a955585d39a344fc846d2f59e), [44c9d29](https://github.com/morluto/rea/commit/44c9d2924d5b95d7a86595e8d7b7cf4fd4f0f6ac), [5e79d46](https://github.com/morluto/rea/commit/5e79d468e5864204cbe79a7ca2898541cee5e012)). Thanks @jpdefo.
|
||||
- **Browser comparison coverage:** Compare only known redirect response fields and retain network-selection recovery guidance alongside independent event changes. Missing response facts do not prove equality or a change. ([2b60e09](https://github.com/morluto/rea/commit/2b60e097f281f7708c69f1f7ba9263694547bcc0)).
|
||||
- **Evidence comparison and ledger input:** Report display-only bundle differences as unchanged while retaining digests, and classify noncanonical bundle records as invalid caller input. ([030c477](https://github.com/morluto/rea/commit/030c47732833c9cedcc31db1309eb3a1ca2c62e8), [3a34ad3](https://github.com/morluto/rea/commit/3a34ad37bdb7b8debba0f6515ba85b80141f2716)).
|
||||
- **Capture and admission cleanup:** Keep deleted-file observations and original bytes when a parent becomes a file, preserve unrelated I/O failures during cancellation, and require explicit pre-launch proof before releasing an unknown LLDB target owner. LLDB failure controls used simulated boundaries. ([e67c364](https://github.com/morluto/rea/commit/e67c3642c8920a2a9c0e231ee2f16f50b245df31), [2daeff4](https://github.com/morluto/rea/commit/2daeff4b243e1cd137c591ca394e18a1c05c8bbc), [45b6a7f](https://github.com/morluto/rea/commit/45b6a7fe8f51a89951aaa823ffa627f6f5453a33)). Thanks @SII-YDD, @xu91102.
|
||||
- **Client configuration:** Validate MCP output-schema profiles before setup writes, ignore untrusted Gemini workspace settings before parsing them, and identify installed skills independently of marker-only client roots. ([0930e94](https://github.com/morluto/rea/commit/0930e94e077ab7fb4d695f62785919a8a25c2e21), [aade9bb](https://github.com/morluto/rea/commit/aade9bbcb79aa8504ab6921b25f8ab082aba78e7), [ba33c70](https://github.com/morluto/rea/commit/ba33c706d26b63e43308be50c0249c72cff62cc6)). Thanks @CmsChase, @paoValle, @sx4im.
|
||||
- **Host tool discovery:** Share macOS Hopper launcher discovery across setup and runtime, preserve explicit paths, and report validated ADB and Apktool availability. ([7582f0d](https://github.com/morluto/rea/commit/7582f0dff29ae0e80c97b885ddd386ac61a92570), [e5deb83](https://github.com/morluto/rea/commit/e5deb832cb09bb524877d0df40f49d5b5bb7dea4)). Thanks @Chessing234, @kaoru0822-kitauji, @viniciusdsandrade.
|
||||
- **SAZ encoding validation:** Reject invalid XML bytes, unsupported encodings and declarations that contradict the archive bytes, including records outside the selected request. ([1250aae](https://github.com/morluto/rea/commit/1250aaedd8b4d1e471d8a382f76418bde65c05d2)).
|
||||
- **Managed member matching:** Keep malformed TypeDef list ownership unknown, distinguish constants during structural method matching, and accept legacy small exception-section sizes. ([3f6b575](https://github.com/morluto/rea/commit/3f6b5755b3e699bd9373641acffe0f2c5d2ab094), [ee5cfbe](https://github.com/morluto/rea/commit/ee5cfbe05cd309a88003c711721e67a3bee36554)).
|
||||
- **Bounded diagnostics:** Bound SDK validation messages and terminal normalization before allocation while retaining diagnostics and explicit unknown final output sizes. ([7fb0a0c](https://github.com/morluto/rea/commit/7fb0a0cc577f7c9033a2674d877d8242b5864556), [1d826f3](https://github.com/morluto/rea/commit/1d826f304f032acfe69dad3ff824b300aacaeb43)). Thanks @CRYPTONIKAV.
|
||||
|
||||
- **Windows artifact classification:** identify `.exe`, `.com`, `.bat` and `.cmd` artifacts as launchable host executables. ([#1823](https://github.com/morluto/rea/pull/1823), [#1824](https://github.com/morluto/rea/pull/1824), [issue morluto/rea#1819](https://github.com/morluto/rea/issues/1819)). Thanks @dollarsmoney, @JasonBuildAI, @kaoru0822-kitauji.
|
||||
|
||||
- **CLI/MCP delivery:** defer command-specific startup work, bundle runtime entries, retain field/input correction details across output formats and preserve shared optional-provider lifetime. MCP selectors, paging continuation and retained partial-error references remain explicit; session liveness/provider-attribution corrections retain their controlled-provider verification limits. ([#1691](https://github.com/morluto/rea/pull/1691), [issue morluto/rea#1685](https://github.com/morluto/rea/issues/1685), [#1705](https://github.com/morluto/rea/pull/1705), [issue morluto/rea#1695](https://github.com/morluto/rea/issues/1695), [#1755](https://github.com/morluto/rea/pull/1755), [issue morluto/rea#1746](https://github.com/morluto/rea/issues/1746), [#1773](https://github.com/morluto/rea/pull/1773), [issue morluto/rea#1771](https://github.com/morluto/rea/issues/1771), [#1780](https://github.com/morluto/rea/pull/1780), [#1814](https://github.com/morluto/rea/pull/1814), [#1910](https://github.com/morluto/rea/pull/1910), [issue morluto/rea#1849](https://github.com/morluto/rea/issues/1849), [#1996](https://github.com/morluto/rea/pull/1996), [#2140](https://github.com/morluto/rea/pull/2140), [issue morluto/rea#2135](https://github.com/morluto/rea/issues/2135), [#2205](https://github.com/morluto/rea/pull/2205), [issue morluto/rea#2195](https://github.com/morluto/rea/issues/2195), [#2230](https://github.com/morluto/rea/pull/2230), [issue morluto/rea#2180](https://github.com/morluto/rea/issues/2180), [#2294](https://github.com/morluto/rea/pull/2294), [#2482](https://github.com/morluto/rea/pull/2482), [a82d14e](https://github.com/morluto/rea/commit/a82d14e4db4dca989ffa2e7dbe78fd43551da256), [a4dd4e8](https://github.com/morluto/rea/commit/a4dd4e8e8700ce350e1a0c612ebd61b91ca5e585), [48c352b](https://github.com/morluto/rea/commit/48c352b6bfa34c820a530272cd36600158c37b1e), [07b36c1](https://github.com/morluto/rea/commit/07b36c148946f46b99a4ce04e55726e97d0ee03a), [570c40c](https://github.com/morluto/rea/commit/570c40cde55d31e455ef9bc484b7b908658ded58), [8fc2043](https://github.com/morluto/rea/commit/8fc2043eb8bcedbf34dce064f977e455438635d6), [7fc0b5e](https://github.com/morluto/rea/commit/7fc0b5ec9c2532bbf789d7783b7a0cab6fc054d8)). Thanks @369Apps, @chqcjq, @cold-atom, @coygeek, @JackOdegard, @kaoru0822-kitauji, @N0zoM1z0, @sx4im.
|
||||
- **Evidence import/replay:** import bundles across session transitions while authenticating selected target and file identities, preserve literal keys, metadata/mutation intent and relationship coverage, and honor cancellation before session mutation. Reuse authenticated snapshots and comparisons within their owned lifetime; reject malformed/non-object snapshots and unsupported metadata without disguising uncertainty. ([#1694](https://github.com/morluto/rea/pull/1694), [#1716](https://github.com/morluto/rea/pull/1716), [issue morluto/rea#1680](https://github.com/morluto/rea/issues/1680), [#1721](https://github.com/morluto/rea/pull/1721), [issue morluto/rea#1718](https://github.com/morluto/rea/issues/1718), [#1733](https://github.com/morluto/rea/pull/1733), [issue morluto/rea#1723](https://github.com/morluto/rea/issues/1723), [issue morluto/rea#1724](https://github.com/morluto/rea/issues/1724), [#1776](https://github.com/morluto/rea/pull/1776), [#1873](https://github.com/morluto/rea/pull/1873), [#2001](https://github.com/morluto/rea/pull/2001), [#2013](https://github.com/morluto/rea/pull/2013), [issue morluto/rea#2009](https://github.com/morluto/rea/issues/2009), [#2083](https://github.com/morluto/rea/pull/2083), [#2303](https://github.com/morluto/rea/pull/2303), [#2685](https://github.com/morluto/rea/pull/2685), [issue morluto/rea#2675](https://github.com/morluto/rea/issues/2675), [c002b89](https://github.com/morluto/rea/commit/c002b89e2d8130a3c07e25e5fd604876be18c703), [9c85114](https://github.com/morluto/rea/commit/9c8511409c0706f9979c2b68dd56aaaf9f21e191), [aada0ca](https://github.com/morluto/rea/commit/aada0ca8126bf14c60750a4ccd4a79667770a1e4), [4a4d36f](https://github.com/morluto/rea/commit/4a4d36f881861bbab3c734b0dd459f805766caec), [1c64fe3](https://github.com/morluto/rea/commit/1c64fe3eff48679c6880f42d2f0a4af46686ade7), [6fb3a34](https://github.com/morluto/rea/commit/6fb3a345fe952b8e8f59221aec993acc37ccbf31), [3bcc68c](https://github.com/morluto/rea/commit/3bcc68c037e9415c78093cb286069c1fcca6a2f8), [6bae774](https://github.com/morluto/rea/commit/6bae774328724403255590aa2ddad4d7a2aa727f), [e997085](https://github.com/morluto/rea/commit/e9970855ba16ba711816ac30212c5ca17ae085cd), [d1dcebc](https://github.com/morluto/rea/commit/d1dcebce2c0820469ed63e33497a14258971b710), [b123b2e](https://github.com/morluto/rea/commit/b123b2e51cc7d6d2157525664be2df88fb1ba3bd), [049132a](https://github.com/morluto/rea/commit/049132ab43fa962d31774f2b258c15a71217148b), [042dcaa](https://github.com/morluto/rea/commit/042dcaa1ee66f1b8668f08b193f38ab01bceaacb), [acf75e6](https://github.com/morluto/rea/commit/acf75e6700ace28bb1db25e54e56673ac15e0ed3), [1f7f808](https://github.com/morluto/rea/commit/1f7f808c4f3bb5eca6b13bcc71d5cceb2ace6614), [26bffb9](https://github.com/morluto/rea/commit/26bffb94bf80355c35e22a98a8fc629c6210307e)). Thanks @coygeek, @DeryFerd, @jiangjiang248, @kaoru0822-kitauji, @khanukov, @N0zoM1z0, @NIU-123370, @rohitg00, @sx4im.
|
||||
- **Ghidra setup and analysis:** preserve launcher/JVM diagnostics, Java-property and extension identity, stale-client recovery, UTF-8 bridge output and pending-startup progress. File-offset decompilation is Ghidra-specific; recorded coverage is Linux x64 with Ghidra 12.1.4. Startup/progress/compiler failure paths that use simulations remain unverified with a real engine. ([#1698](https://github.com/morluto/rea/pull/1698), [#1877](https://github.com/morluto/rea/pull/1877), [#1931](https://github.com/morluto/rea/pull/1931), [#2011](https://github.com/morluto/rea/pull/2011), [issue morluto/rea#1992](https://github.com/morluto/rea/issues/1992), [#2014](https://github.com/morluto/rea/pull/2014), [#2101](https://github.com/morluto/rea/pull/2101), [issue morluto/rea#2078](https://github.com/morluto/rea/issues/2078), [#2206](https://github.com/morluto/rea/pull/2206), [issue morluto/rea#2204](https://github.com/morluto/rea/issues/2204), [2187e16](https://github.com/morluto/rea/commit/2187e16f149a7a7b512a4cb45d5f468cc04091fe), [b953f96](https://github.com/morluto/rea/commit/b953f96c0aae27e1876c317a8f7db1d6f292b8ff)). Thanks @EDMND-SRC, @Ha1baraA11, @kaoru0822-kitauji, @lovecontroing, @lyly-bonbon, @N0zoM1z0, @nosmile99, @Petru101, @sx4im, @Zhong-fan.
|
||||
- **Process and cleanup ownership:** retain private runtime/file owners and detailed incomplete-cleanup observations, bound trace cycles/order state, preserve caller paths, and use monotonic time for budgets while retaining wall dates. Windows affinity/admission ([#2202](https://github.com/morluto/rea/pull/2202), [issue morluto/rea#2181](https://github.com/morluto/rea/issues/2181)) and Bun memory-enforcement paths retain their recorded native-versus-controlled-fixture limits; these fixes are not a general process-ownership guarantee. ([#1700](https://github.com/morluto/rea/pull/1700), [issue morluto/rea#1697](https://github.com/morluto/rea/issues/1697), [#1730](https://github.com/morluto/rea/pull/1730), [#1756](https://github.com/morluto/rea/pull/1756), [issue morluto/rea#1753](https://github.com/morluto/rea/issues/1753), [#1760](https://github.com/morluto/rea/pull/1760), [issue morluto/rea#1757](https://github.com/morluto/rea/issues/1757), [#1826](https://github.com/morluto/rea/pull/1826), [#1907](https://github.com/morluto/rea/pull/1907), [issue morluto/rea#1856](https://github.com/morluto/rea/issues/1856), [#1999](https://github.com/morluto/rea/pull/1999), [issue morluto/rea#1993](https://github.com/morluto/rea/issues/1993), [#2232](https://github.com/morluto/rea/pull/2232), [issue morluto/rea#2170](https://github.com/morluto/rea/issues/2170), [#2252](https://github.com/morluto/rea/pull/2252), [issue morluto/rea#2218](https://github.com/morluto/rea/issues/2218), [#2467](https://github.com/morluto/rea/pull/2467), [issue morluto/rea#2257](https://github.com/morluto/rea/issues/2257), [#2468](https://github.com/morluto/rea/pull/2468), [#2488](https://github.com/morluto/rea/pull/2488), [09b6828](https://github.com/morluto/rea/commit/09b68285166bef2b7633402c8adf7eb25823b47e), [7285e15](https://github.com/morluto/rea/commit/7285e15839a4fecf9940683699b781a60ccc4775), [b5ededd](https://github.com/morluto/rea/commit/b5ededd0f9c28d0f696a205103066928117368ee), [d8c76b6](https://github.com/morluto/rea/commit/d8c76b629639bda4405afa9ae2f7676b694f2f10), [98d8470](https://github.com/morluto/rea/commit/98d8470d36cfbf7cd25820a4ed430aebae20c0c5), [c22111e](https://github.com/morluto/rea/commit/c22111eca2c4bc84a084f12711f405b3e7422a40), [cecc6ec](https://github.com/morluto/rea/commit/cecc6ecbffe84689a173d1634f342e046b0c8ceb), [10935b7](https://github.com/morluto/rea/commit/10935b7b1136accab2a43b308ab380749da0f7ae), [63e796d](https://github.com/morluto/rea/commit/63e796d50ea323ae8dcfef80f86c8154bb106790), [d9e9fb2](https://github.com/morluto/rea/commit/d9e9fb2273543caab4eb21d896c33d462ada6971), [9049dba](https://github.com/morluto/rea/commit/9049dbaf0c3df32357419f9899965df2b611f493), [ed881c6](https://github.com/morluto/rea/commit/ed881c6d2ef6a8adba0c2a241ebc9ab4603b7868)). Thanks @Ashen00001, @charan-rathore, @coygeek, @EDMND-SRC, @JasonBuildAI, @jimpablo, @kaoru0822-kitauji, @N0zoM1z0, @NIU-123370, @Rinkia, @rudycelekli, @sx4im, @xeropresence, @XiaoBinGan, @xthunder0, @hzh666kevin-hue.
|
||||
- **Native image identity:** preserve universal Mach-O containers and selected slice/subtype identity, chained fixup locations and library ordinals, exact signing text, PE resource capacity and ELF/header framing. Mach-O summary selection and independent slice commands avoid unnecessary delivery/work. Controlled routing/parser fixtures do not establish every installed-provider path. ([#1703](https://github.com/morluto/rea/pull/1703), [issue morluto/rea#1702](https://github.com/morluto/rea/issues/1702), [#1708](https://github.com/morluto/rea/pull/1708), [#1709](https://github.com/morluto/rea/pull/1709), [#1713](https://github.com/morluto/rea/pull/1713), [#1772](https://github.com/morluto/rea/pull/1772), [issue morluto/rea#1765](https://github.com/morluto/rea/issues/1765), [#1784](https://github.com/morluto/rea/pull/1784), [issue morluto/rea#1775](https://github.com/morluto/rea/issues/1775), [#1982](https://github.com/morluto/rea/pull/1982), [#1995](https://github.com/morluto/rea/pull/1995), [issue morluto/rea#1990](https://github.com/morluto/rea/issues/1990), [#2040](https://github.com/morluto/rea/pull/2040), [#2107](https://github.com/morluto/rea/pull/2107), [#2161](https://github.com/morluto/rea/pull/2161), [issue morluto/rea#2105](https://github.com/morluto/rea/issues/2105), [#2248](https://github.com/morluto/rea/pull/2248), [issue morluto/rea#2176](https://github.com/morluto/rea/issues/2176), [#2281](https://github.com/morluto/rea/pull/2281), [issue morluto/rea#2274](https://github.com/morluto/rea/issues/2274), [#2292](https://github.com/morluto/rea/pull/2292), [#2466](https://github.com/morluto/rea/pull/2466), [#2506](https://github.com/morluto/rea/pull/2506), [#2733](https://github.com/morluto/rea/pull/2733), [issue morluto/rea#2661](https://github.com/morluto/rea/issues/2661), [f598bab](https://github.com/morluto/rea/commit/f598bab488e38c378d69346fa3c106f9e18529ed), [108b5f9](https://github.com/morluto/rea/commit/108b5f94254281639badd25b1f448cca35dc89a2), [f1924cf](https://github.com/morluto/rea/commit/f1924cf9c1910d5d29d11e7bf634b51af8d738cf), [8bd5ba2](https://github.com/morluto/rea/commit/8bd5ba2d234cbd9a5bb9dd5daeaa191c3a1cad24), [b38429c](https://github.com/morluto/rea/commit/b38429cb6a0de8f4bb5ae01633e24abe2d7cde46), [8d4d631](https://github.com/morluto/rea/commit/8d4d631606c701aac150cf009d3b80ae4a7ae7d5), [ac57c88](https://github.com/morluto/rea/commit/ac57c88efe64c3125850b54d3d833227e6b8ec51), [dc038d1](https://github.com/morluto/rea/commit/dc038d16bba9046afd28970de9c47f6259a43524), [34a4bb6](https://github.com/morluto/rea/commit/34a4bb6508006fa113463ef20ed799a2994e9ee8), [ebc22d9](https://github.com/morluto/rea/commit/ebc22d99d62225505c0a84f6e39823ba4ef2f829), [c02fb0e](https://github.com/morluto/rea/commit/c02fb0ea8055f98def2b12ad557aed0faefd3639)). Thanks @1NormalGuy, @Aliciawque, @baron, @coygeek, @dhl84, @hemanthrayuduu, @JasonBuildAI, @kaoru0822-kitauji, @N0zoM1z0, @thangldw, @viniciusdsandrade, @XiaoBinGan, @Zburgers.
|
||||
- **Archive and artifact provenance:** keep physical ZIP occurrence identity, exact signatures/names and partial inventory coverage; bound decompression and reject malformed keys/integers. Directory/ASAR/DMG reads retain ownership through cleanup and publication cancellation. Duplicate ZIP names remain explicitly ambiguous where the public projection cannot represent them; synthetic archive/cleanup cases do not prove every real Unblob or DMG failure path. ([#1726](https://github.com/morluto/rea/pull/1726), [issue morluto/rea#1725](https://github.com/morluto/rea/issues/1725), [#1854](https://github.com/morluto/rea/pull/1854), [issue morluto/rea#1844](https://github.com/morluto/rea/issues/1844), [#1865](https://github.com/morluto/rea/pull/1865), [issue morluto/rea#1860](https://github.com/morluto/rea/issues/1860), [#2069](https://github.com/morluto/rea/pull/2069), [#2106](https://github.com/morluto/rea/pull/2106), [#2144](https://github.com/morluto/rea/pull/2144), [#2365](https://github.com/morluto/rea/pull/2365), [#2493](https://github.com/morluto/rea/pull/2493), [918713a](https://github.com/morluto/rea/commit/918713af376e307ecf24d75e6a0dd9d1b544545b), [eb97da3](https://github.com/morluto/rea/commit/eb97da33881e3d1472d97a7d788ee35944fb6b07), [2260887](https://github.com/morluto/rea/commit/2260887f2092be6a3d9c79509e4ae4a3e52fce7f), [74d7da6](https://github.com/morluto/rea/commit/74d7da6ced0f869936ee0316b00616931b088066), [ed485a3](https://github.com/morluto/rea/commit/ed485a3dc8cc3a4baf8cabe01acb7aa0490b73b3), [921bb95](https://github.com/morluto/rea/commit/921bb9575505065f6cfd57b14d97d80d0f238546), [d8ceb3f](https://github.com/morluto/rea/commit/d8ceb3f276a65c181b146f68c0729ff4b6f9fa61), [9435c7a](https://github.com/morluto/rea/commit/9435c7aae279ba184937c0dfeb713fd574ba3f29), [bb54082](https://github.com/morluto/rea/commit/bb540826d65316c164d2de8ba4ef10f81a8ad073), [a73a55f](https://github.com/morluto/rea/commit/a73a55f53cc2b2e5b7618effe6ecb5cee6e0cee2), [3c57e1a](https://github.com/morluto/rea/commit/3c57e1a8b21b379fca55032d177078e1d8426e64), [ec0b081](https://github.com/morluto/rea/commit/ec0b081371f118e6e5e042fbef3479359c672ddb), [e7882b3](https://github.com/morluto/rea/commit/e7882b34f4ac4a26f903c5f49264acc2c19bb562), [b955b82](https://github.com/morluto/rea/commit/b955b825fc002a4bb408f2910b78768c566c2cd1)). Thanks @1052326311, @1NormalGuy, @baron, @Bortlesboat, @Chaoqi31, @DeryFerd, @kaoru0822-kitauji, @N0zoM1z0, @XiaoBinGan.
|
||||
- **Setup/uninstall recovery:** preserve unrelated YAML text, caller-selected roots, symlinked data and existing recovery snapshots; explain malformed configuration shapes and retain failed rollback/cleanup owners. Show terminal dry-run plans and each selected skill location. Bunx registration and fault-injected recovery cases remain simulated rather than live-client verification. ([#1731](https://github.com/morluto/rea/pull/1731), [#1754](https://github.com/morluto/rea/pull/1754), [#1821](https://github.com/morluto/rea/pull/1821), [issue morluto/rea#1816](https://github.com/morluto/rea/issues/1816), [#1983](https://github.com/morluto/rea/pull/1983), [#2118](https://github.com/morluto/rea/pull/2118), [issue morluto/rea#2111](https://github.com/morluto/rea/issues/2111), [#2154](https://github.com/morluto/rea/pull/2154), [issue morluto/rea#2152](https://github.com/morluto/rea/issues/2152), [#2157](https://github.com/morluto/rea/pull/2157), [#2182](https://github.com/morluto/rea/pull/2182), [issue morluto/rea#2159](https://github.com/morluto/rea/issues/2159), [#2481](https://github.com/morluto/rea/pull/2481), [bb50813](https://github.com/morluto/rea/commit/bb508134efbba392a404b651d6ec05be8b7eac1e), [c240768](https://github.com/morluto/rea/commit/c2407689cb04df082e8db0b6afa8f8d022f6bcd1), [dcfc5d4](https://github.com/morluto/rea/commit/dcfc5d403ac997c2b08bee4753e57a1a4a8a8538), [21eff3b](https://github.com/morluto/rea/commit/21eff3bae10f58711a35ad44f86e9f96f9bb4790), [94153f1](https://github.com/morluto/rea/commit/94153f1c757e07a6d092e00903aa8b125d4a0afa), [2397137](https://github.com/morluto/rea/commit/23971376158e7815edcbe6c04a8df7c9504650dc), [c339f9e](https://github.com/morluto/rea/commit/c339f9ebe7bd1f30608d6ccb7175da523dd3c485), [f6775a3](https://github.com/morluto/rea/commit/f6775a36694a6a9c620f4248638e8056a6cbcb66)). Thanks @BeqaKs, @Jax367, @kaoru0822-kitauji, @NIU-123370, @shoyann, @viniciusdsandrade, @xeropresence, @zaydiscold.
|
||||
- **Bun updates:** locate global installations before changing directories and reuse parsed client activation during maintenance. The update flow is covered with package-manager/filesystem simulations; a real Bun global installation update remains unverified. ([#1735](https://github.com/morluto/rea/pull/1735), [e90acae](https://github.com/morluto/rea/commit/e90acaea674f5f7f9f17ea472104afdc1422ada6), [b045df1](https://github.com/morluto/rea/commit/b045df100e1489e08a36d6ff04cea7feecf1332c)). Thanks @xeropresence.
|
||||
- **JavaScript relationships:** preserve loader-specific package/TypeScript/CommonJS boundaries, exact file-URL and source-map identity, Electron intrinsic receivers/IPC guards and chunk metadata. Index repeated lookups and retain completed facts through resource limits and cancellation. Real Electron integration, Bun worker integration and failure-path cleanup remain unverified where the recorded lanes use fixtures. ([#1740](https://github.com/morluto/rea/pull/1740), [#1759](https://github.com/morluto/rea/pull/1759), [issue morluto/rea#1748](https://github.com/morluto/rea/issues/1748), [#1762](https://github.com/morluto/rea/pull/1762), [issue morluto/rea#1761](https://github.com/morluto/rea/issues/1761), [#1767](https://github.com/morluto/rea/pull/1767), [#1788](https://github.com/morluto/rea/pull/1788), [issue morluto/rea#1781](https://github.com/morluto/rea/issues/1781), [issue morluto/rea#1782](https://github.com/morluto/rea/issues/1782), [#1789](https://github.com/morluto/rea/pull/1789), [#1799](https://github.com/morluto/rea/pull/1799), [issue morluto/rea#1793](https://github.com/morluto/rea/issues/1793), [#1827](https://github.com/morluto/rea/pull/1827), [issue morluto/rea#1802](https://github.com/morluto/rea/issues/1802), [#1833](https://github.com/morluto/rea/pull/1833), [issue morluto/rea#1831](https://github.com/morluto/rea/issues/1831), [#1857](https://github.com/morluto/rea/pull/1857), [issue morluto/rea#1842](https://github.com/morluto/rea/issues/1842), [#1871](https://github.com/morluto/rea/pull/1871), [#1874](https://github.com/morluto/rea/pull/1874), [#1905](https://github.com/morluto/rea/pull/1905), [#1912](https://github.com/morluto/rea/pull/1912), [issue morluto/rea#1909](https://github.com/morluto/rea/issues/1909), [#1915](https://github.com/morluto/rea/pull/1915), [issue morluto/rea#1911](https://github.com/morluto/rea/issues/1911), [#1916](https://github.com/morluto/rea/pull/1916), [#1921](https://github.com/morluto/rea/pull/1921), [#1934](https://github.com/morluto/rea/pull/1934), [#1939](https://github.com/morluto/rea/pull/1939), [issue morluto/rea#1881](https://github.com/morluto/rea/issues/1881), [issue morluto/rea#1882](https://github.com/morluto/rea/issues/1882), [issue morluto/rea#1883](https://github.com/morluto/rea/issues/1883), [#1941](https://github.com/morluto/rea/pull/1941), [#2062](https://github.com/morluto/rea/pull/2062), [issue morluto/rea#2058](https://github.com/morluto/rea/issues/2058), [#2104](https://github.com/morluto/rea/pull/2104), [#2422](https://github.com/morluto/rea/pull/2422), [#2475](https://github.com/morluto/rea/pull/2475), [#2737](https://github.com/morluto/rea/pull/2737), [issue morluto/rea#2663](https://github.com/morluto/rea/issues/2663), [d3088c3](https://github.com/morluto/rea/commit/d3088c3795f6c37cb3b6aec0bb0c371dd0640ee8), [1dfb933](https://github.com/morluto/rea/commit/1dfb933e47db0156cf1cb61f28715b81e740c57b), [f8513a4](https://github.com/morluto/rea/commit/f8513a42dc44451a621127f06edde5d38bc93f69), [b8fab74](https://github.com/morluto/rea/commit/b8fab7429771bb17a7ca0297661de3c9935212af), [42b0ed1](https://github.com/morluto/rea/commit/42b0ed126df7d5b908ac369715378d5e519051b7), [c0904b4](https://github.com/morluto/rea/commit/c0904b4c1aabfccf2f0ab12f44960a0a1fbbbfae), [9f67e44](https://github.com/morluto/rea/commit/9f67e44e5d451d705bae9a579ad9802d5c9f097c), [e4532b5](https://github.com/morluto/rea/commit/e4532b537663026c3e5b784380eecd009c959a36), [1c37914](https://github.com/morluto/rea/commit/1c37914306b3ff257d7a0360f11c9bf2a3f2cac7), [5ab0cbb](https://github.com/morluto/rea/commit/5ab0cbb48ea5a4f11542eac44479b9af48f1dabd), [b7169c8](https://github.com/morluto/rea/commit/b7169c8b747cbc032913b31f008a3b81c77b6219), [f43fc02](https://github.com/morluto/rea/commit/f43fc0247527aebb8efc7d9db04276fe2d489323), [3ae826c](https://github.com/morluto/rea/commit/3ae826ceba6f25b08bce50bcb51001209bc16b57), [1b95302](https://github.com/morluto/rea/commit/1b953021eadf4cac09e20be388d79536903633b3), [eaa9616](https://github.com/morluto/rea/commit/eaa961658578522cecfbba7c7be1acf1c64da65a), [c923fb0](https://github.com/morluto/rea/commit/c923fb0dc9462e302b84a15770131ff899f89455), [c7a890d](https://github.com/morluto/rea/commit/c7a890d12987d9b2d496119c1fbf1a88c0fc298e)). Thanks @Aurantium-47, @baron, @chqcjq, @coygeek, @cyanseek, @fengdwx, @flitzrrr, @immanuwell, @kaoru0822-kitauji, @N0zoM1z0, @Nelglor, @ranmori, @rudycelekli, @shoyann, @xu91102.
|
||||
- **Browser observations:** preserve effective source-map URLs, empty query/fragment identity, response-body provenance, service-worker policies and explicit uncertainty for malformed events. Bound discovery lifetimes and retain cleanup ownership. Interrupt/retry/failure cases validated with controlled CDP/session fixtures do not establish those paths in real Chromium. ([#1742](https://github.com/morluto/rea/pull/1742), [issue morluto/rea#1729](https://github.com/morluto/rea/issues/1729), [#1829](https://github.com/morluto/rea/pull/1829), [#2074](https://github.com/morluto/rea/pull/2074), [#2099](https://github.com/morluto/rea/pull/2099), [#2239](https://github.com/morluto/rea/pull/2239), [issue morluto/rea#2169](https://github.com/morluto/rea/issues/2169), [#2477](https://github.com/morluto/rea/pull/2477), [486b567](https://github.com/morluto/rea/commit/486b56724c4eaba50719177bcb9eae84d6907297), [a82e37c](https://github.com/morluto/rea/commit/a82e37c8065cc200a0ca3e14e7c4f85e4e468580), [a8d4984](https://github.com/morluto/rea/commit/a8d4984404b611e60b88bc42289c0ba07eee4578), [966f7cc](https://github.com/morluto/rea/commit/966f7cc84b4a8bb0043641cb64d7a7aaf8715e05), [221c856](https://github.com/morluto/rea/commit/221c85648108e9846d9920b0964dafa7f108d581), [951b095](https://github.com/morluto/rea/commit/951b095e11adf9faa9c4a27e212260060aa6b095), [f09bb8a](https://github.com/morluto/rea/commit/f09bb8a99773f79c7306b8799c86f856fe2c0d4b), [6bd2b28](https://github.com/morluto/rea/commit/6bd2b28161411f70ed5dc72e42c2736a1aab758b)). Thanks @1052326311, @chAng-L19, @chqcjq, @coygeek, @evenafterallisaid, @fisforfaheem, @itsF4LCON, @N0zoM1z0, @shayanxcyber.
|
||||
- **Managed metadata/CIL:** preserve ExportedType and pointer-table ownership, generic/array/function-pointer/modifier signatures, ReadyToRun host identity, metadata ExtraData and signed zero. Charge retained facts and decoded instruction counts instead of broad decoding bounds; malformed layouts remain caller input errors. ([#1743](https://github.com/morluto/rea/pull/1743), [issue morluto/rea#1736](https://github.com/morluto/rea/issues/1736), [#1768](https://github.com/morluto/rea/pull/1768), [#1822](https://github.com/morluto/rea/pull/1822), [issue morluto/rea#1801](https://github.com/morluto/rea/issues/1801), [#1828](https://github.com/morluto/rea/pull/1828), [issue morluto/rea#1812](https://github.com/morluto/rea/issues/1812), [#1835](https://github.com/morluto/rea/pull/1835), [issue morluto/rea#1832](https://github.com/morluto/rea/issues/1832), [#1886](https://github.com/morluto/rea/pull/1886), [#1893](https://github.com/morluto/rea/pull/1893), [#1897](https://github.com/morluto/rea/pull/1897), [issue morluto/rea#1878](https://github.com/morluto/rea/issues/1878), [#1943](https://github.com/morluto/rea/pull/1943), [issue morluto/rea#1940](https://github.com/morluto/rea/issues/1940), [#1985](https://github.com/morluto/rea/pull/1985), [#2194](https://github.com/morluto/rea/pull/2194), [issue morluto/rea#2191](https://github.com/morluto/rea/issues/2191), [#2437](https://github.com/morluto/rea/pull/2437), [52e76c5](https://github.com/morluto/rea/commit/52e76c5d39b58d1685bb5e22bd40d19524dfa2cb)). Thanks @ac1982, @chqcjq, @fisHarly0, @flitzrrr, @JasonBuildAI, @JayOfTheKeyboard, @jhuang-tw, @kaoru0822-kitauji, @N0zoM1z0, @Nelglor.
|
||||
- **Hopper failure ownership:** preserve startup/process outcomes, retain cleanup leases across one-shot failures, wait for late bridges, and stop owned Linux demo children when their REA owner exits. Denied/crashed/late-bridge/queued-request failure paths use simulated process or bridge fixtures; they are not fresh installed-Hopper verification. ([#1763](https://github.com/morluto/rea/pull/1763), [issue morluto/rea#1751](https://github.com/morluto/rea/issues/1751), [#1817](https://github.com/morluto/rea/pull/1817), [#1866](https://github.com/morluto/rea/pull/1866), [#1927](https://github.com/morluto/rea/pull/1927), [#2474](https://github.com/morluto/rea/pull/2474), [#2499](https://github.com/morluto/rea/pull/2499), [issue morluto/rea#2459](https://github.com/morluto/rea/issues/2459)). Thanks @coygeek, @Joshwong1908, @kaoru0822-kitauji, @N0zoM1z0, @rudycelekli.
|
||||
- **IDA session boundaries:** retain non-x86 call coverage and unresolved-procedure uncertainty, and bound private universal-Mach-O inputs to the exact selected slice. Transient headless busy recovery was exercised on macOS arm64 IDA Pro 9.1; Linux headless, attached GUI navigation and worker-reaped cleanup paths retain their documented verification gaps. ([#1764](https://github.com/morluto/rea/pull/1764), [issue morluto/rea#1712](https://github.com/morluto/rea/issues/1712), [#1778](https://github.com/morluto/rea/pull/1778), [issue morluto/rea#1766](https://github.com/morluto/rea/issues/1766), [#1785](https://github.com/morluto/rea/pull/1785), [issue morluto/rea#1769](https://github.com/morluto/rea/issues/1769), [#1906](https://github.com/morluto/rea/pull/1906), [issue morluto/rea#1794](https://github.com/morluto/rea/issues/1794), [#2201](https://github.com/morluto/rea/pull/2201), [issue morluto/rea#2139](https://github.com/morluto/rea/issues/2139), [#2291](https://github.com/morluto/rea/pull/2291), [issue morluto/rea#2285](https://github.com/morluto/rea/issues/2285), [#2324](https://github.com/morluto/rea/pull/2324), [12fa8fd](https://github.com/morluto/rea/commit/12fa8fd9f986dbd4ff58165c3ec71a46dc8a8a3b)). Thanks @akushonkamen, @coygeek, @kaoru0822-kitauji, @N0zoM1z0, @nikolay-dementiev, @zecaro.
|
||||
- **Input and capability diagnostics:** identify invalid paths, format refusals, missing host tools and non-retryable errors without losing caller selections or retryable capability facts. Foreign execution/device mutation annotations remain destructive, analysis-target text is untrusted, and output ordering follows code points rather than host locale. ([#1770](https://github.com/morluto/rea/pull/1770), [#1867](https://github.com/morluto/rea/pull/1867), [#1869](https://github.com/morluto/rea/pull/1869), [issue morluto/rea#1855](https://github.com/morluto/rea/issues/1855), [#1872](https://github.com/morluto/rea/pull/1872), [#1879](https://github.com/morluto/rea/pull/1879), [issue morluto/rea#1853](https://github.com/morluto/rea/issues/1853), [#1899](https://github.com/morluto/rea/pull/1899), [#1902](https://github.com/morluto/rea/pull/1902), [#1904](https://github.com/morluto/rea/pull/1904), [#1908](https://github.com/morluto/rea/pull/1908), [#1925](https://github.com/morluto/rea/pull/1925), [#1987](https://github.com/morluto/rea/pull/1987), [issue morluto/rea#1979](https://github.com/morluto/rea/issues/1979), [#2004](https://github.com/morluto/rea/pull/2004), [issue morluto/rea#2002](https://github.com/morluto/rea/issues/2002), [#2026](https://github.com/morluto/rea/pull/2026), [#2091](https://github.com/morluto/rea/pull/2091), [#2133](https://github.com/morluto/rea/pull/2133), [issue morluto/rea#2129](https://github.com/morluto/rea/issues/2129), [#2145](https://github.com/morluto/rea/pull/2145), [issue morluto/rea#2143](https://github.com/morluto/rea/issues/2143), [#2472](https://github.com/morluto/rea/pull/2472), [#2478](https://github.com/morluto/rea/pull/2478), [issue morluto/rea#2454](https://github.com/morluto/rea/issues/2454), [#2494](https://github.com/morluto/rea/pull/2494), [#2507](https://github.com/morluto/rea/pull/2507), [#2509](https://github.com/morluto/rea/pull/2509), [#2708](https://github.com/morluto/rea/pull/2708), [3d83135](https://github.com/morluto/rea/commit/3d8313598785359c424d7305f503cb69fc8639a6), [c9fed50](https://github.com/morluto/rea/commit/c9fed50691ae014890e91a72534773d2fb58ce44), [8b18171](https://github.com/morluto/rea/commit/8b181716aca2cb467be499cba226abfb5ae19141), [7a2cd1e](https://github.com/morluto/rea/commit/7a2cd1ea0a63e8c44166860b7ee9c7a1aa43492b)). Thanks @astralyx0129, @CassieFox23, @DeryFerd, @dollarsmoney, @fisforfaheem, @flitzrrr, @Ha1baraA11, @jimpablo, @kaoru0822-kitauji, @larinyo, @N0zoM1z0, @rudycelekli, @SII-YDD, @viniciusdsandrade, @XiaoBinGan.
|
||||
- **Android analysis:** retain decoded values, duplicate-class inventories and executable static initializers, reject malformed JADX UTF-8, and keep inline search provenance. Apktool accepts `--no-strings` and explains host requirements. Windows batch-launcher selection also resolves PATH/PATHEXT before choosing its launch mode. Recorded Apktool coverage is Linux/OpenJDK, and changed JADX/batch failure paths do not establish native Windows behavior. ([#1777](https://github.com/morluto/rea/pull/1777), [issue morluto/rea#1774](https://github.com/morluto/rea/issues/1774), [#1876](https://github.com/morluto/rea/pull/1876), [issue morluto/rea#1846](https://github.com/morluto/rea/issues/1846), [#1890](https://github.com/morluto/rea/pull/1890), [issue morluto/rea#1850](https://github.com/morluto/rea/issues/1850), [#1946](https://github.com/morluto/rea/pull/1946), [issue morluto/rea#1945](https://github.com/morluto/rea/issues/1945), [#2707](https://github.com/morluto/rea/pull/2707), [issue morluto/rea#2672](https://github.com/morluto/rea/issues/2672), [0677468](https://github.com/morluto/rea/commit/0677468877465c8dfbe25c72f142a7b5bc8a3212), [fe8d0bd](https://github.com/morluto/rea/commit/fe8d0bdd36fe12bc0836468d1a702a34fbdc219b), [#2802](https://github.com/morluto/rea/pull/2802)). Thanks @akushonkamen, @coygeek, @HeadmasterEggy, @kaoru0822-kitauji, @N0zoM1z0, @psipher.
|
||||
- **ADB acquisition:** preserve device paths and directory identity, prevent host destination collisions/overwrites, count UTF-8 logcat bytes and restricted permissions, and evaluate the complete Activity Manager start report. Launch attempts, status, activity and raw failure output remain observations; real-device timeout/mixed-report and Android 8 directory-read paths remain unverified beyond the documented fixtures. ([#1962](https://github.com/morluto/rea/pull/1962), [#2212](https://github.com/morluto/rea/pull/2212), [issue morluto/rea#2175](https://github.com/morluto/rea/issues/2175), [#2221](https://github.com/morluto/rea/pull/2221), [issue morluto/rea#2220](https://github.com/morluto/rea/issues/2220), [#2359](https://github.com/morluto/rea/pull/2359), [c42232f](https://github.com/morluto/rea/commit/c42232f566d55fe7191763649755148c768eef9f), [55b6148](https://github.com/morluto/rea/commit/55b61485e9f7568b69597eef8696ff6abfe53cfd), [3bdc873](https://github.com/morluto/rea/commit/3bdc8730bd98e073522bcb8ba86527be402c0cbe), [c89972c](https://github.com/morluto/rea/commit/c89972c8777d0114566fe76a060849a18bdd2659), [e1cd292](https://github.com/morluto/rea/commit/e1cd292aa6e8f5f13441d408c66126782c8dbb0d)). Thanks @coygeek, @dakjdakd, @DeryFerd, @huiq777, @sx4im.
|
||||
- **WASM names and diagnostics:** preserve debug/custom-section names including NUL and leading BOM, accept unpadded WABT section rows, and avoid irrelevant tool-remediation advice on unrelated runtime-root failures. ([#1963](https://github.com/morluto/rea/pull/1963), [issue morluto/rea#1956](https://github.com/morluto/rea/issues/1956), [#1967](https://github.com/morluto/rea/pull/1967), [#1981](https://github.com/morluto/rea/pull/1981), [#2084](https://github.com/morluto/rea/pull/2084), [#2102](https://github.com/morluto/rea/pull/2102)). Thanks @1052326311, @bugstan, @N0zoM1z0, @sathariels, @STiFLeR7, @viniciusdsandrade.
|
||||
|
||||
- **Source-map and path handling:** retain source-map entries when embedded-content counts differ or an optional `sourceMappingURL` is used, and keep `file:` URLs out of operator-relative path resolution. ([#2873](https://github.com/morluto/rea/pull/2873), [#2886](https://github.com/morluto/rea/pull/2886), [#2882](https://github.com/morluto/rea/pull/2882), [issue morluto/rea#2821](https://github.com/morluto/rea/issues/2821), [issue morluto/rea#2883](https://github.com/morluto/rea/issues/2883), [issue morluto/rea#2577](https://github.com/morluto/rea/issues/2577)). Thanks @JFSAS, @rileygramlich, @shoyann, @shubhamahuja9999, @coygeek.
|
||||
- **Native metadata:** accept declared facts in native dispatch inventories and share native verification input diagnostics. ([#2906](https://github.com/morluto/rea/pull/2906), [#2901](https://github.com/morluto/rea/pull/2901), [issue morluto/rea#2564](https://github.com/morluto/rea/issues/2564), [issue morluto/rea#2621](https://github.com/morluto/rea/issues/2621)). Thanks @ShitK, @coygeek.
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
- **Runtime attachment state:** `attached` may be `null` in JavaScript, browser and Electron discovery/inspection results. Treat null as unknown; do not coerce it to false. ([e55ef72](https://github.com/morluto/rea/commit/e55ef7213515ba204c3d88a4487b4ee6a2773b06)).
|
||||
- **Source maps:** original-source and mapping rows preserve a null source name when unavailable. Handle null explicitly instead of expecting an invented unknown-source string; new index fields remain optional when reading older Evidence. ([ee50ed1](https://github.com/morluto/rea/commit/ee50ed121e38cc6e1a2ff0dc8b789f15f71c5402)).
|
||||
- **Streamed filtered JSON:** `--filter-output` now emits JSON `null` instead of `{}` when the selected value is null; consumers must distinguish a present null from a missing selection. Use `--full-output` when an object envelope is required. ([#2333](https://github.com/morluto/rea/pull/2333)). Thanks @Jackmeson1.
|
||||
- **Call graphs:** `get_call_graph.normalized_result` is now `{ graph, limitations }`; read traversal-depth groups under `graph` and retain relationship-specific caveats. Contract/fixture validation does not establish new Ghidra or IDA engine coverage. ([5dfb9d0](https://github.com/morluto/rea/commit/5dfb9d050c2cc44a61b183dae765bef4d22a53b7)).
|
||||
- **MCP discovery:** `binary_session` no longer repeats `server_identity.catalog.tools`. Use the connected server tool list, grouped `tool_availability` and provider capabilities; see [MCP contracts](docs/mcp-contracts.md). Origin: [6395e8d](https://github.com/morluto/rea/commit/6395e8d3007d5ee453477460e7ee1c51cff76be0). ([#1988](https://github.com/morluto/rea/pull/1988)). Thanks @kaoru0822-kitauji, @N0zoM1z0.
|
||||
|
||||
- **Provider admission:** MIPS targets with unresolved GNU ABI attributes are refused. Supply supported ABI declarations. Saved Hopper databases on macOS are unavailable because the launcher cannot run the analysis bridge when opening a database; select an executable target instead. Database support requires a script-capable database launch route. ([d4bd65c](https://github.com/morluto/rea/commit/d4bd65c53c0254d0195cc7ab8da163dcadfd3b9b)).
|
||||
- **Segment extents:** `binary_overview` segment length may be null when Hopper coordinates cannot establish a finite extent in one address space. Preserve unknown rather than converting it to zero or a saturated value. ([36db19c](https://github.com/morluto/rea/commit/36db19c59)).
|
||||
- **Invocation identity:** `server.command_path` may be null, and registration/setup statuses or doctor classifications may be unknown. Preserve these states and establish the actual invocation before treating a registration as aligned. ([187f113](https://github.com/morluto/rea/commit/187f113f7)). Thanks @hemv-857, @qorvr.
|
||||
- **ADB installation:** the optional `replace` input is unchanged. Results now require `replace_requested` to record the requested `adb install -r` mode; `replaced` is nullable because prior device package state was not observed. Update result readers to accept null instead of treating reinstall intent as proof of replacement. ([864314a](https://github.com/morluto/rea/commit/864314ac857924a7e6e65bfb37e8dc720fcc21e7)).
|
||||
- **Managed/native verification:** unresolved P/Invoke results may retain `matched_native` and candidate facts when module identity is unknown. Interpret `status`, `basis` and `limitations`; `module-identity-unknown` does not verify correspondence, and unresolved no longer implies empty observations. ([3f6b575](https://github.com/morluto/rea/commit/3f6b5755b3e699bd9373641acffe0f2c5d2ab094)).
|
||||
|
||||
Full contribution record: [893 commits and 529 merged PRs](https://github.com/morluto/rea/blob/rea-agents-6.5.0/docs/releases/6.5.0.contributions.json).
|
||||
|
||||
|
||||
## [6.4.0](https://github.com/morluto/rea/compare/rea-agents-6.3.0...rea-agents-6.4.0) (2026-10-11)
|
||||
|
||||
### Highlights
|
||||
@@ -2683,7 +2380,7 @@ Read the [6.1 to 6.2 migration guide](docs/migration-6.2.md) before upgrading. R
|
||||
* enforce conventional pull request titles ([3a21f16](https://github.com/morluto/rea/commit/3a21f16c1832430765083636511a8c12dbccf2dd))
|
||||
* verify installed package with Linux Hopper ([4f97f69](https://github.com/morluto/rea/commit/4f97f699e4d5471557d1ce0ff91e144065d06a61))
|
||||
|
||||
## [0.3.0](https://github.com/morluto/rea/compare/rea-0.2.1...rea-agents-0.3.0) (2026-07-12)
|
||||
## [0.3.0](https://github.com/morluto/rea/compare/rea-agents-0.2.1...rea-agents-0.3.0) (2026-07-12)
|
||||
|
||||
|
||||
### Features
|
||||
@@ -2736,7 +2433,7 @@ Read the [6.1 to 6.2 migration guide](docs/migration-6.2.md) before upgrading. R
|
||||
* redesign and localize README ([051b6db](https://github.com/morluto/rea/commit/051b6db65687fd14826b6462cd623feb2504f123))
|
||||
* redesign and localize README ([910eda6](https://github.com/morluto/rea/commit/910eda6f323ba1151b0771fe5de6c60a91e3d46d))
|
||||
|
||||
## [0.2.0](https://github.com/morluto/rea/releases/tag/rea-0.2.0) (2026-07-12)
|
||||
## [0.2.0](https://github.com/morluto/rea/compare/rea-0.1.0...rea-0.2.0) (2026-07-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
+7
-176
@@ -10,129 +10,19 @@ and maintenance cost. Keep cosmetic polish with useful fixes; confusing or
|
||||
incorrect user instructions warrant standalone corrections. Collect other
|
||||
cosmetic-only suggestions in an issue rather than separate PRs.
|
||||
|
||||
For selected architectural owners and dependency paths, use the
|
||||
[architecture map](docs/architecture.mermaid). It is not a complete directory or
|
||||
import inventory. For current provider wiring, start with
|
||||
[`src/composition/binary.ts`](src/composition/binary.ts) for deep candidates and
|
||||
the other `src/composition/` factories for tool services, including
|
||||
[`gpu.ts`](src/composition/gpu.ts) and [`model.ts`](src/composition/model.ts).
|
||||
Live Frida, Cutter, GDB and Rizin construction also uses shared composition
|
||||
factories. For service ownership, availability and catalog declarations, follow
|
||||
the [capability composition guide](docs/capability-composition.md).
|
||||
Production factories construct fresh providers without starting engines or
|
||||
acquiring targets; optional adapter failures must preserve successful peers. Run
|
||||
For capability organization and provider composition, use the
|
||||
[architecture map](docs/architecture.mermaid). Production factories in
|
||||
`src/composition/` construct fresh providers without starting engines or acquiring
|
||||
targets; optional adapter failures must preserve successful peers. Run
|
||||
`npm run verify:test-discovery` after adding or moving tests. Resolved import
|
||||
boundaries are checked by `npm run verify:module-boundaries`, included in lint.
|
||||
|
||||
Before retiring an alias or facade, trace runtime registration, dynamic imports,
|
||||
JavaScript verifiers, subprocess scripts, and packaged entrypoints as well as
|
||||
TypeScript references. Tests may be the last consumers of a superseded internal
|
||||
path; migrate their distinct assertions to the current production path before
|
||||
removing it. Preserve validation, lazy loading, caller policy, and resource
|
||||
acquisition and cleanup supplied by the old boundary. Published APIs, persisted
|
||||
formats, and independently versioned bridges require an explicit compatibility
|
||||
decision; an internal representation alone does not establish that obligation.
|
||||
See [interpreting boundary facts once](docs/tool-design.md#interpret-boundary-facts-once)
|
||||
for admission ownership and the distinction between repeated validation and
|
||||
independent checks.
|
||||
|
||||
For analysis workflows and agent interfaces, follow the
|
||||
[performance priorities](AGENTS.md#performance). Explain the selected output
|
||||
scope, reuse/cache decisions, and relevant before/after measurements when these
|
||||
costs change.
|
||||
Use the [analysis optimization skill](.agents/skills/rea-analysis-optimization/SKILL.md)
|
||||
to locate provider and shared-workflow bottlenecks, preserve Evidence, and
|
||||
compare equivalent real tasks before claiming an improvement.
|
||||
|
||||
When adding or changing an MCP tool, follow the [tool design guide](docs/tool-design.md) and preserve the canonical contracts and generated catalog.
|
||||
When adding or changing an admission ceiling, budget, or deadline, follow
|
||||
[analysis settings, limits, and recovery](docs/design/limits-and-recovery.md)
|
||||
for ownership, operation snapshots, provider integration, and the steps for
|
||||
adding a runtime setting.
|
||||
|
||||
## Reporting issues
|
||||
|
||||
Use the issue form that fits the report and keep it concise. For bugs, include
|
||||
the actual result, the shortest reproduction you ran, the tested version and
|
||||
environment, and the smallest useful output, artifact, screenshot, or source
|
||||
reference. Ground expected behavior in documentation, source, or a known-good
|
||||
version when available. State observed impact and frequency; keep untested scope
|
||||
and suspected causes explicit.
|
||||
|
||||
Incomplete reports and intermittent failures are welcome. If a narrative answer
|
||||
or supporting evidence is unavailable, write `NOT_ENOUGH_INFO` and explain the
|
||||
gap. Clearly label source-only findings and proposed checks; do not claim runtime
|
||||
reproduction or results you did not observe. A root-cause analysis or fix is not
|
||||
required to report a problem. Documentation reports should identify the relevant
|
||||
passage or missing guidance and explain the correction or confusion.
|
||||
|
||||
Feature and design proposals can start with a problem and desired outcome.
|
||||
Examples, prior art, and measurements help when available; ideas without them
|
||||
are welcome. Maintainers can investigate reports and help resolve open questions.
|
||||
Human ownership and readiness declarations apply to PRs as described below.
|
||||
|
||||
## Pull request ownership and readiness
|
||||
|
||||
AI-assisted contributions are welcome. Every PR must have a human owner who
|
||||
takes responsibility for the complete change, including code, tests,
|
||||
documentation, and claims produced with AI assistance. The PR author is the
|
||||
owner unless another human is named in the PR and explicitly accepts ownership.
|
||||
These requirements also apply to maintainer-authored PRs.
|
||||
|
||||
Before requesting maintainer review, the human owner must:
|
||||
|
||||
- Review the final diff and relevant existing code. Be able to explain the
|
||||
problem, the chosen approach, how it fits REA, and meaningful edge cases.
|
||||
- Provide evidence that the change works. Summarize what was checked, the
|
||||
observed results, and meaningful gaps in the PR body. Use focused tests,
|
||||
representative CLI/MCP output, CI artifacts, screenshots, or other checks
|
||||
appropriate to the change. For fixes, show the original failure and why the
|
||||
regression coverage catches it when practical. Documentation-only changes
|
||||
can use source, link, or rendered-document checks. Label source-derived
|
||||
conclusions and proposed checks accurately; never report an unrun check as
|
||||
passing. Follow the [verification requirements](#development-feedback-and-pr-verification)
|
||||
for required checks and real-provider coverage.
|
||||
- Complete the **Author readiness** declaration in the PR template, explicitly
|
||||
stating that the PR is ready for initial review, unless the PR author is
|
||||
exempt from the declaration as listed below.
|
||||
|
||||
Keep the PR description concise and current. Explain the problem, observable
|
||||
outcome, approach, and useful evidence; link long logs or put them in optional
|
||||
details. Address actionable automated review findings and explain disagreements.
|
||||
Human ownership and review remain required when CI or an automated reviewer
|
||||
reports success. Readiness requests maintainer review; it does not grant scope
|
||||
approval or permission to merge.
|
||||
|
||||
Draft status is optional. Draft PRs may request early feedback with open questions
|
||||
and incomplete checks. Agents preserve the current draft status unless asked to
|
||||
change it; missing readiness declarations or later updates do not require a PR
|
||||
to become a draft.
|
||||
|
||||
Leave the readiness declaration unchecked until the human owner has completed
|
||||
it. The declaration records initial readiness; later pushes do not invalidate
|
||||
it or require another human sign-off. Keep the PR description and affected
|
||||
validation current as work changes. AI agents must leave human declarations to
|
||||
the human owner and must not attest to a human review on their behalf.
|
||||
|
||||
The `PR readiness` check reports pending for drafts and requires a named human
|
||||
owner and all three template declarations for non-drafts. PRs authored by
|
||||
@morluto, @kaoru0822-kitauji, @N0zoM1z0, or @gensokyo-dev are exempt from
|
||||
the declaration; their human ownership, review, and evidence responsibilities
|
||||
above still apply. The check identifies authors by GitHub account ID. It checks
|
||||
that the declaration is filled, not whether the code or evidence is correct.
|
||||
Existing PRs can copy the **Author readiness** section from the current template.
|
||||
Open PRs must have distinct head commits because GitHub shares commit statuses.
|
||||
|
||||
Maintainers enable merge enforcement after the workflow is merged: run
|
||||
`gh workflow run pr-readiness.yml -f pull_request_number=NUMBER` for existing
|
||||
open PRs, then add `PR readiness` from GitHub Actions to the branch's required
|
||||
status checks alongside its existing checks. Deploy the workflow to each
|
||||
protected target branch before requiring it there. The workflow reads PR
|
||||
metadata using trusted default-branch code and writes only a commit status.
|
||||
|
||||
Maintainers may return a PR to the author or close it when the problem is
|
||||
unsupported, validation is missing, or the owner cannot explain the change.
|
||||
Contributors are responsible for completing this work before requesting review.
|
||||
|
||||
## Contribution scope
|
||||
|
||||
@@ -235,8 +125,6 @@ Use the development toolchain pinned in `.nvmrc` and
|
||||
`package.json#packageManager`. Run `nvm use` before installing dependencies;
|
||||
installed-package runtime support is documented in
|
||||
[installation](docs/installation.md#start-setup).
|
||||
Use Node.js and npm for source builds and verifier scripts; the installed
|
||||
package's Bun runtime support does not extend to those development scripts.
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
@@ -251,15 +139,6 @@ catalogs, documentation projections, or managed verification evidence. Turbo
|
||||
caches deterministic builds and static checks across Git worktrees. After a package, lockfile, or managed-skill version change, run
|
||||
`npm run metadata:generate` before building.
|
||||
|
||||
Dependabot proposes updates for the npm lockfiles, website Python requirements,
|
||||
and GitHub Actions. Review dependency changes and run the checks relevant to
|
||||
their consumers. For dependencies maintained outside Dependabot, including
|
||||
`third_party/` and the HTTP/3 lab's pinned requirements, the maintainer should
|
||||
review upstream security advisories when updating or using them. Preserve source
|
||||
and version provenance, update license notices when needed, and report which
|
||||
verification was actually repeated. The lab requirements describe a recorded
|
||||
environment; a dependency update does not verify the revised environment.
|
||||
|
||||
Repository invariants live in [AGENTS.md](AGENTS.md). Keep provider-specific
|
||||
protocols in adapters and shared CLI/MCP workflows in the application layer.
|
||||
|
||||
@@ -270,10 +149,7 @@ manifest. A source checkout does not: `native/windows/build/` is ignored by
|
||||
Git, and neither `npm ci` nor `npm run build:cached` (or `npm run build`)
|
||||
produces `rea-windows-x64.node` or `manifest.json` in that directory.
|
||||
Windows workflows and tests that require native controls need these artifacts;
|
||||
this includes `run-exclusive` commands, which use the native Job Object to keep
|
||||
the artifact lock until every command descendant has exited. Without the
|
||||
artifacts, those controls are unavailable even after a successful runtime
|
||||
build.
|
||||
without them, those controls are unavailable even after a successful runtime build.
|
||||
|
||||
Follow the [Windows native build guide](native/windows/README.md#build) before
|
||||
running those workflows from source. The artifact build requires Linux,
|
||||
@@ -281,22 +157,6 @@ MinGW-w64, and `tar`; the build script does not run on Windows. See that guide
|
||||
for the build and artifact-verification commands and the separate real Windows
|
||||
verification prerequisites. Keep the generated addon and manifest uncommitted.
|
||||
|
||||
The general source suite (`npm run test:fast`, `npm run test:changed`, and
|
||||
`npm run test:local`) runs on Linux in CI, and Windows CI runs a curated lane.
|
||||
On a Windows checkout, some source tests fail because fixtures use POSIX paths,
|
||||
`#!/bin/sh` tool stubs, or POSIX rename semantics. Compare failures against the
|
||||
base revision, or run the suite on Linux or WSL, before attributing them to a
|
||||
change. The embedded-artifact parent-swap tests exercise the selected-path
|
||||
replacement when the rename succeeds; if Windows refuses it while the admitted
|
||||
file is open, they instead verify the refusal and cleanup. See
|
||||
[testing](docs/testing.md#developer-commands) for command scopes
|
||||
and lane selection.
|
||||
|
||||
`git submodule update --init --recursive` fails at the pinned Unblob submodule
|
||||
on Windows and can leave later submodules unchecked out. Apply the
|
||||
[Unblob checkout workaround](docs/firmware-analysis.md#windows-submodule-checkout),
|
||||
then rerun the command.
|
||||
|
||||
## Documentation website
|
||||
|
||||
The VitePress site uses the Markdown files in `docs/`. Run `npm run docs:dev`
|
||||
@@ -319,29 +179,8 @@ The separate GitHub Pages host uses only the manual
|
||||
**Settings → Pages → Build and deployment → Source** must be set to
|
||||
**GitHub Actions** before deploying there.
|
||||
|
||||
### Translation corrections
|
||||
|
||||
README editions and website editions have separate translation sources:
|
||||
|
||||
- Find the language's `readme` path in the
|
||||
[language inventory](website/locales/languages.json) and edit that file.
|
||||
Locale codes and README suffixes can differ. Use the
|
||||
[English README](README.md) to check commands, requirements, and analysis claims.
|
||||
- Edit `msgstr` values in `website/locales/<language>/messages.po` for website
|
||||
text. Follow the [website translation guide](website/locales/README.md) for
|
||||
the Python environment, catalog validation, and local preview. Preserve source
|
||||
strings, contexts, placeholders, commands, paths, and recorded evidence.
|
||||
|
||||
Before submitting a website translation, run the catalog and website checks in
|
||||
that guide. Translated HTML in `website/_site/` is generated; edit the PO catalog
|
||||
rather than the generated pages.
|
||||
|
||||
## Development feedback and PR verification
|
||||
|
||||
For routine merges, wait for the required checks to pass for the current PR
|
||||
revision. Administrators retain an override for deliberate maintainer recovery.
|
||||
If `main` is broken, prioritize a focused repair before updating affected PRs.
|
||||
|
||||
Select checks for the behavior changed. For a source edit:
|
||||
|
||||
```bash
|
||||
@@ -403,17 +242,9 @@ Real-provider execution is uncached.
|
||||
### Temporary files and diagnostics
|
||||
|
||||
Tests use `createTestTempDirectory` from `tests/fixtures/temporaryDirectory.ts`
|
||||
for exact-path, awaited cleanup, including failures and timeouts. It registers
|
||||
deletion with Vitest's `onTestFinished`; do not also add its result to a roots
|
||||
array or an `afterEach` removal. `createTestWorkspace` from
|
||||
`tests/support/workspace/workspaceFixture.ts` leaves cleanup with its caller,
|
||||
which must await `removeTestWorkspace`. Keep explicit resource teardown before
|
||||
directory deletion, using the case's `finally` or `afterEach`; Vitest runs
|
||||
`afterEach` before `onTestFinished` and awaits finish hooks in reverse
|
||||
registration order.
|
||||
for exact-path, awaited cleanup, including failures and timeouts.
|
||||
`npm run verify:test-temp-hygiene` runs the suite under a fresh `TMPDIR` and
|
||||
rejects remaining REA-owned paths. It cannot detect duplicate deletion owners or
|
||||
prove resource-close ordering. Never glob-clean shared `/tmp/rea-*` content.
|
||||
rejects remaining REA-owned paths. Never glob-clean shared `/tmp/rea-*` content.
|
||||
|
||||
`REA_LOG_LEVEL` selects `trace`, `debug`, `info`, `warn`, `error`, `fatal`, or
|
||||
`silent`. Logs go to stderr: MCP defaults to `info`, and one-shot CLI logging
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<div align="center">
|
||||
|
||||
**English** · [简体中文](docs/readme/README_zh.md) · [繁體中文](docs/readme/README_zh-TW.md) · [日本語](docs/readme/README_ja.md) · [한국어](docs/readme/README_ko.md) · [Türkçe](docs/readme/README_tr.md) · [Русский](docs/readme/README_ru.md) · [Tiếng Việt](docs/readme/README_vi.md) · [ไทย](docs/readme/README_th.md) · [Deutsch](docs/readme/README_de.md) · [Español](docs/readme/README_es.md) · [Français](docs/readme/README_fr.md) · [Українська](docs/readme/README_uk.md) · [Polski](docs/readme/README_pl.md) · [Português (Brasil)](docs/readme/README_pt-BR.md) · [العربية](docs/readme/README_ar.md) · [فارسی](docs/readme/README_fa.md) · [Bahasa Indonesia](docs/readme/README_id.md) · [Italiano](docs/readme/README_it.md)
|
||||
· [Ελληνικά](docs/readme/README_el.md) · [മലയാളം](docs/readme/README_ml.md) · [Roman Urdu](docs/readme/README_ur-Latn.md) · [Română](docs/readme/README_ro.md)
|
||||
|
||||
# REA: Reverse Engineer Anything
|
||||
|
||||
@@ -20,22 +19,10 @@
|
||||
|
||||
**[Website](https://rea.tools/) · [Guides](https://rea.tools/guides/) · [Showcases](https://rea.tools/showcase/)**
|
||||
|
||||
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Join the REA community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
|
||||
|
||||
[Quick start](#quick-start) · [How REA works](#how-rea-works) · [What you can analyze](#what-you-can-analyze) · [Showcases](#showcases) · [Build RL environments](#build-rl-environments) · [FAQ](#faq) · [Documentation](#documentation)
|
||||
[Quick start](#quick-start) · [How REA works](#how-rea-works) · [What you can analyze](#what-you-can-analyze) · [Showcases](#showcases) · [FAQ](#faq) · [Documentation](#documentation)
|
||||
|
||||
<code>npx rea-agents setup</code>
|
||||
|
||||
</div>
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Get REA from [morluto/rea](https://github.com/morluto/rea),
|
||||
> [rea.tools](https://rea.tools/), or the [rea-agents npm package](https://www.npmjs.com/package/rea-agents).
|
||||
> Follow the installation and release links from these official sources. A similar
|
||||
> repository name or copied README does not establish that a download is official.
|
||||
|
||||
<div align="center">
|
||||
|
||||
<br />
|
||||
|
||||
<img src="docs/assets/rea-hopper-analysis.png" alt="REA launching its analysis bridge inside Hopper while inspecting a native binary" width="1200" />
|
||||
@@ -64,17 +51,12 @@ See a feature in an app that you want in your own product? Ask your agent to inv
|
||||
|
||||
REA connects your agent to tools for inspecting native binaries, JavaScript and Electron apps, .NET assemblies, and websites. You can also use the same tools from your terminal. Analysis runs locally, and results include the evidence and limitations behind each conclusion.
|
||||
|
||||
Setup registers REA with your agent and installs matching workflow instructions. Native analysis can use an existing Hopper, Ghidra, IDA or Kuna installation; setup can optionally install Hopper with approval. Static JavaScript analysis needs no native analysis engine.
|
||||
Setup registers REA with your agent and installs matching workflow instructions. Native analysis can use an existing Hopper, Ghidra, or IDA installation; setup can optionally install Hopper with approval. Static JavaScript analysis needs no native analysis engine.
|
||||
|
||||
> **[Visit the REA website](https://rea.tools/)** for setup instructions, illustrated guides, and real case studies.
|
||||
|
||||
## Quick start
|
||||
|
||||
On Linux, Ghidra, JADX, firmware tool launches and static JavaScript/Electron
|
||||
analysis require a procps-compatible `ps` on REA's `PATH` for process ownership
|
||||
checks. Recovery steps are in `docs/installation.md` under
|
||||
"Linux owned-process prerequisite".
|
||||
|
||||
### Set up your agent
|
||||
|
||||
With Node.js and npm installed, run:
|
||||
@@ -85,27 +67,12 @@ npx rea-agents setup
|
||||
|
||||
Choose your agents, review the proposed changes, and approve them. Setup adds
|
||||
REA's MCP server and matching workflow instructions, with backups of existing
|
||||
configuration. Restart your agent afterward. Setup records the selected runtime and REA
|
||||
entry script directly. If acquired through a package runner, keep its cache entry
|
||||
available. Run package acquisition from a trusted directory outside the target
|
||||
project. Installation and recovery details are in the setup guide below.
|
||||
configuration. Restart your agent afterward.
|
||||
|
||||
Setup supports Claude Code, Codex, Cursor, Gemini CLI, Grok Build and
|
||||
[other agents](docs/installation.md#supported-agents). See
|
||||
[installation and setup](docs/installation.md) for provider configuration and
|
||||
manual MCP registration and Claude Code/Codex plugin installation.
|
||||
|
||||
MCP uses compact input schemas by default to reduce context overhead. Set
|
||||
`REA_MCP_INPUT_SCHEMA_PROFILE=full` in your MCP registration when you need the
|
||||
complete advertised field guidance and nested validation details. Both profiles
|
||||
keep the full tool set and canonical server validation. See
|
||||
"Compact input schema profile" in the MCP contracts guide.
|
||||
|
||||
Complete output schemas are advertised by default. If your client caps the
|
||||
complete `tools/list` frame, set `REA_MCP_OUTPUT_SCHEMA_PROFILE=compact` to
|
||||
bound each advertised output schema, or `off` to omit output schemas entirely.
|
||||
Both settings keep canonical server-side validation and complete structured
|
||||
results. See "Output schemas" in the MCP contracts guide.
|
||||
manual MCP registration.
|
||||
|
||||
### Ask your agent
|
||||
|
||||
@@ -149,14 +116,6 @@ For a CLI installed with `npm install --global` or `bun add --global`:
|
||||
rea update
|
||||
```
|
||||
|
||||
`rea --update` is an alias for this verified workflow and accepts the same global
|
||||
output flags. Use `rea update` when combining it with other command controls.
|
||||
|
||||
Updater commands retain at most 4 MiB of stdout and stderr combined. Oversized
|
||||
metadata replies are rejected after the command exits; verbose installs continue
|
||||
with bounded diagnostics. Human progress output respects terminal backpressure
|
||||
and waits for submitted writes to finish.
|
||||
|
||||
Bun global updates require Bun 1.2.17 or later. With an older Bun release,
|
||||
upgrade Bun manually before running `rea update`.
|
||||
|
||||
@@ -188,63 +147,43 @@ CLI commands use the same workflows.
|
||||
|
||||
## What you can analyze
|
||||
|
||||
REA requires Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), or 26.12.1+, plus npm.
|
||||
REA requires Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+, plus npm.
|
||||
Additional tools and host support depend on the target:
|
||||
|
||||
| Target | What REA returns | Requirements and guide |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Desktop installations | Runtime candidates, supporting files, component markers and next probes | Built-in bounded directory/header inspection; [desktop applications](docs/desktop-applications.md) |
|
||||
| Native binaries | Pseudocode, assembly, strings, symbols, calls and references | Hopper, Ghidra, IDA or Kuna; [native analysis](docs/native-investigation.md) |
|
||||
| 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 |
|
||||
| GPU code objects | CUDA/HIP targets, selected kernel parameters and resources, retained PTX text | Built-in static ELF/PE/fatbin/CUBIN/PTX inspection; [GPU code objects](docs/gpu-code-objects.md) |
|
||||
| Model artifacts | GGUF / safetensors metadata, tensor declarations and derived totals | Built-in header inspection; optional full-file hash; [model files](docs/model-artifacts.md) |
|
||||
| IL2CPP metadata | Unity global-metadata.dat assembly images, type and method names and index ranges | Built-in static decoding of versions 24, 27, 29, 31 and 39; no Unity required |
|
||||
| Godot PCK | Archive inventory and extraction, original header/directory facts and selected content identities | Built-in static formats 2, 3 and 4; no Godot required |
|
||||
| Rust binaries | Embedded cargo-auditable crate names, versions, sources and dependency edges | Built-in static ELF/PE/thin Mach-O inspection; [Rust binaries](docs/rust-binaries.md) |
|
||||
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](docs/pe-certificates.md) |
|
||||
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](docs/pe-imports.md) |
|
||||
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](docs/elf-notes.md) |
|
||||
| Apple IPS crashes | .ips metadata, threads, images, registers and documented crash classes | Built-in static JSON inspection; [Apple IPS](docs/apple-ips.md) |
|
||||
| Linux kernel modules | .modinfo keys, __versions CRCs, this_module presence, and signature trailer | Built-in static ELF inspection; [kernel modules](docs/kernel-modules.md) |
|
||||
| Erlang BEAM | IFF chunks, atoms, exports, imports, lambdas and the Code header | Built-in static BEAM inspection; [Erlang BEAM](docs/erlang-beam.md) |
|
||||
| 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) |
|
||||
| SQLite databases | Tables, columns, indexes, views, triggers and selected ordinary-table rows | Local database/WAL snapshot; Node.js 24.x >=24.15 or 26+ with native SQLite limits |
|
||||
| Recorded Linux crashes | Raw notes, every recorded thread's registers/signals and optional mapping candidates | Caller-supplied pwntools on Linux x64; optional GDB/pwndbg; [recorded crashes](docs/recorded-crashes.md) |
|
||||
| JavaScript / Electron | Modules, imports, source maps, routes, IPC and native add-on relationships | Node.js and npm; Linux: procps-compatible `ps`; [application analysis](docs/javascript-application-workflows.md) |
|
||||
| Websites | Page structure, scripts, network observations and requested screenshots | A Chrome-family browser; [browser analysis](docs/browser-observation.md) |
|
||||
| Saved network captures | Requests, responses, exposed payloads and source locations | HAR or Fiddler Everywhere SAZ; mitmdump on Linux for native mitmproxy; [capture guide](docs/web-network-captures.md) |
|
||||
| .NET assemblies | Metadata, CIL instructions, declared native dependencies and build comparisons | None (static only); [managed-code guide](docs/managed-code-analysis.md) |
|
||||
| Android APKs | Manifest declarations, classes, decompiled methods and references | Headless JADX and a full JDK on Linux/macOS/Windows x64; [Android guide](docs/android-analysis.md) |
|
||||
| APK signatures | v1/v2/v3/v3.1 signers, certificate fingerprints, key rotation and digest self-consistency | None (static only); `inspect_apk_signature` |
|
||||
| Android devices | Devices, packages, processes, logs, dumps, screen captures, and APK/file transfers | Caller-supplied adb (emulator or hardware); [ADB guide](docs/adb-device-analysis.md) |
|
||||
| JEB projects | Engine-backed project units, artifact digests and type/method pseudo-code | A caller-started JEB client serving MCP, selected with REA_JEB_MCP_URL; [JEB guide](docs/jeb-analysis.md) |
|
||||
| Android resources | Decoded manifest, string tables, locales and version facts | Caller-supplied Apktool launcher; [resource guide](docs/apktool-resource-analysis.md) |
|
||||
| Flutter apps | Dart snapshot hash, per-ABI library digests and engine build provenance | None (static APK parsing); [Flutter guide](docs/flutter-analysis.md) |
|
||||
| Firmware | Regions, extraction results and native-analysis handoffs | Binwalk / Unblob on Linux; [firmware guide](docs/firmware-analysis.md) |
|
||||
| Recorded packets | Selected fields, repeated values, filters and decode-as rules | Linux: TShark 4.x + prlimit; [capture guide](docs/packet-captures.md) |
|
||||
| Packet summaries | Endpoints, DNS, TLS SNI, protocol distribution and HTTP requests | Caller-supplied tshark and capinfos (Linux-verified); [pcap guide](docs/pcap-analysis.md) |
|
||||
| Recorded logic signals | Decoder annotations, sample ranges and diagnostics | Linux: sigrok-cli 0.7.2 + prlimit; [signal recordings](docs/signal-recordings.md) |
|
||||
| Packages and resources | File inventories, digests, plists, Apple bundle anatomy and extracted resources | [Artifact and JavaScript guide](docs/javascript-artifact-reconstruction.md), [Apple applications](docs/apple-application-analysis.md) |
|
||||
| Process behavior | Terminal output, interactions, exit and filesystem observations, and run comparisons | Linux/macOS with a native PTY; [process capture](docs/process-capture.md) |
|
||||
| PyInstaller executables | CArchive members, Python version, entry scripts and extracted bytecode | None (static only); [Python packaged applications](docs/python-packaged-applications.md) |
|
||||
| Target | What REA returns | Requirements and guide |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 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) |
|
||||
| SQLite databases | Tables, columns, indexes, views, triggers and selected ordinary-table rows | Local database/WAL snapshot; Node.js 24.x >=24.15 or 26+ with native SQLite limits |
|
||||
| Recorded Linux crashes | Raw notes, every recorded thread's registers/signals and optional mapping candidates | Caller-supplied pwntools on Linux x64; optional GDB/pwndbg; [recorded crashes](docs/recorded-crashes.md) |
|
||||
| JavaScript / Electron | Modules, imports, source maps, routes, IPC and native add-on relationships | Node.js and npm; [application analysis](https://rea.tools/guides/javascript/) |
|
||||
| Websites | Page structure, scripts, network observations and requested screenshots | A Chrome-family browser; [browser analysis](https://rea.tools/guides/browser/) |
|
||||
| Saved network captures | Requests, responses, exposed payloads and source locations | HAR or Fiddler Everywhere SAZ; mitmdump on Linux for native mitmproxy; [capture guide](docs/web-network-captures.md) |
|
||||
| .NET assemblies | Metadata, CIL instructions, declared native dependencies and build comparisons | None (static only); [managed-code guide](docs/managed-code-analysis.md) |
|
||||
| Android APKs | Manifest declarations, classes, decompiled methods and references | Headless JADX and a full JDK on Linux/macOS/Windows x64; [Android guide](docs/android-analysis.md) |
|
||||
| Android devices | Devices, packages, processes, logs, dumps, screen captures, and APK/file transfers | Caller-supplied adb (emulator or hardware); [ADB guide](docs/adb-device-analysis.md) |
|
||||
| JEB projects | Engine-backed project units, artifact digests and type/method pseudo-code | A caller-started JEB client serving MCP, selected with REA_JEB_MCP_URL; [JEB guide](docs/jeb-analysis.md) |
|
||||
| Android resources | Decoded manifest, string tables, locales and version facts | Caller-supplied Apktool launcher; [resource guide](docs/apktool-resource-analysis.md) |
|
||||
| Flutter apps | Dart snapshot hash, per-ABI library digests and engine build provenance | None (static APK parsing); [Flutter guide](docs/flutter-analysis.md) |
|
||||
| Firmware | Regions, extraction results and native-analysis handoffs | Binwalk / Unblob on Linux; [firmware guide](docs/firmware-analysis.md) |
|
||||
| Recorded packets | Selected fields, repeated values, filters and decode-as rules | Linux: TShark 4.x + prlimit; [capture guide](docs/packet-captures.md) |
|
||||
| Packet summaries | Endpoints, DNS, TLS SNI, protocol distribution and HTTP requests | Caller-supplied tshark and capinfos (Linux-verified); [pcap guide](docs/pcap-analysis.md) |
|
||||
| Recorded logic signals | Decoder annotations, sample ranges and diagnostics | Linux: sigrok-cli 0.7.2 + prlimit; [signal recordings](docs/signal-recordings.md) |
|
||||
| Packages and resources | File inventories, digests, plists, Apple bundle anatomy and extracted resources | [Artifact and JavaScript guide](docs/javascript-artifact-reconstruction.md), [Apple applications](docs/apple-application-analysis.md) |
|
||||
| Process behavior | Terminal output, interactions, exit and filesystem observations, and run comparisons | Linux/macOS with a native PTY; [process capture](docs/process-capture.md) |
|
||||
|
||||
Static JavaScript and .NET inspection read the supplied files without running
|
||||
the application. Runtime capture runs or interacts with the selected target
|
||||
using your user permissions; each runtime guide describes its effects.
|
||||
|
||||
For offline native-binary inspection, REA also supports caller-supplied GNU
|
||||
objdump and Rizin. See the [reverse-engineering tools guide](docs/reverse-engineering-tools.md)
|
||||
for provider setup, CLI commands, and Evidence limitations. REA does not install
|
||||
or update either provider.
|
||||
|
||||
<a id="choosing-a-deep-analysis-provider"></a>
|
||||
|
||||
Native formats and host support vary by provider. See
|
||||
[Hopper and Ghidra setup](docs/installation.md#hopper), the
|
||||
[IDA guide](docs/ida-provider.md) (including an optional native MCP bridge), the
|
||||
[Kuna decompiler guide](docs/kuna-provider.md), and
|
||||
[IDA guide](docs/ida-provider.md) (including an optional native MCP bridge), and
|
||||
[experimental Windows Ghidra support](docs/windows-ghidra-p0.md).
|
||||
Ghidra also supports [16-bit DOS analysis](docs/ghidra-dos.md).
|
||||
For large binaries, raise the provider's startup deadline with
|
||||
@@ -255,12 +194,7 @@ for features added since the latest npm release.
|
||||
|
||||
## Showcases
|
||||
|
||||
<a href="https://rea.tools/showcase/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/rea-showcases-dark.svg" />
|
||||
<img alt="Illustrations of the DX-Ball sound-pan, Notion clipboard-bridge, and TH04 bullet-ring showcases" src="docs/assets/rea-showcases.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
[](https://rea.tools/showcase/)
|
||||
|
||||
### DX-Ball: reconstruct a sound-pan calculation
|
||||
|
||||
@@ -287,53 +221,11 @@ historical compiler output.
|
||||
[Read the case study](https://rea.tools/showcase/th04/) ·
|
||||
[Reconstruction repository](https://github.com/N0zoM1z0/th04)
|
||||
|
||||
### Build RL environments with REA
|
||||
|
||||
Record a web app session, turn its API into tools, and export an environment an AI agent can use. Run it with AgentEnv, then add tasks and reward checks for RL training or evaluation.
|
||||
|
||||
[Read the guide](https://rea.tools/guides/rl-environments/) ·
|
||||
[Watch the video](https://x.com/reatools/status/2112574822584918075?s=20)
|
||||
|
||||
If you've used REA on something interesting, we'd love to see it. Share your
|
||||
case in an [issue](https://github.com/morluto/rea/issues) or a
|
||||
[pull request](https://github.com/morluto/rea/pulls), including the target,
|
||||
your question, how REA helped, and what you found.
|
||||
|
||||
## Build RL environments
|
||||
|
||||
Give an AI agent a local environment to practice tasks such as finding an issue or creating a folder, without running the original app. Use it for reinforcement learning (RL) or to test how well an agent completes tasks.
|
||||
|
||||
REA turns recorded API requests and responses into tools the agent can call. The environment uses the recorded data and supports changes only when the recording confirms their effects.
|
||||
|
||||
With REA set up in your agent, replace the example URL and folder tasks below with your own:
|
||||
|
||||
```text
|
||||
Use REA to build an RL environment from my test web app at
|
||||
http://127.0.0.1:3000.
|
||||
Cover listing, creating, renaming, and deleting folders. You may create and
|
||||
remove test folders. Leave existing data unchanged.
|
||||
Save it in a folder named my-env. Check which operations work and tell me
|
||||
how to run it.
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Use the command line instead</summary>
|
||||
|
||||
Use these commands if you already have a file containing the app’s recorded browser requests and responses. Running the result also requires AgentEnv and Docker.
|
||||
|
||||
```sh
|
||||
rea environment-build ./session.har ./my-env --name my-env --json
|
||||
agent-env run ./my-env
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
REA exports a runnable environment with a basic test to check that it starts and responds. Use it with your RL framework to define task goals, add reward checks, and run training.
|
||||
|
||||
[Build an environment](https://rea.tools/guides/rl-environments/) ·
|
||||
[Environment reference](docs/agent-environments.md) ·
|
||||
[Watch the demo on X](https://x.com/reatools/status/2112574822584918075?s=20)
|
||||
|
||||
## FAQ
|
||||
|
||||
<details>
|
||||
@@ -346,13 +238,11 @@ Any agent that supports local MCP servers. Setup configures the
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Do I need a native analysis engine?</strong></summary>
|
||||
<summary><strong>Do I need Hopper, Ghidra or IDA?</strong></summary>
|
||||
|
||||
Deep native analysis uses Hopper, Ghidra, IDA or Kuna, depending on the target
|
||||
and operation. Static JavaScript and .NET inspection work without a native
|
||||
analysis engine. Setup can install Hopper after approval; Ghidra, IDA and Kuna
|
||||
use your existing installations. See [provider setup](docs/installation.md#hopper)
|
||||
for the options and their supported targets.
|
||||
Deep native analysis uses one of them. Static JavaScript and .NET inspection
|
||||
work without a native analysis engine. Setup can install Hopper after approval;
|
||||
Ghidra and IDA use your existing installations. See [provider setup](docs/installation.md#hopper).
|
||||
|
||||
</details>
|
||||
|
||||
@@ -432,7 +322,7 @@ Report vulnerabilities through [SECURITY.md](SECURITY.md).
|
||||
|
||||
## Star history
|
||||
|
||||
🎉 **400,000 GitHub stars — thank you!**
|
||||
🎉 **180,000 GitHub stars — thank you!**
|
||||
|
||||
Thanks to everyone using REA, reporting bugs, requesting features, testing builds, and contributing fixes.
|
||||
|
||||
@@ -462,7 +352,7 @@ to improve the code or docs.
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and checks,
|
||||
[testing](docs/testing.md) for verification lanes, and the
|
||||
[architecture map](docs/architecture.mermaid) for selected layers and dependency paths.
|
||||
[architecture map](docs/architecture.mermaid) for the project structure.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ Do not disclose vulnerabilities, proof-of-concept exploits, credentials,
|
||||
private binaries, Hopper documents, or private Ghidra projects in a public
|
||||
issue.
|
||||
|
||||
Use **Report a vulnerability** in the repository's **Security** tab as the preferred way to submit the report privately. If GitHub private reporting is unavailable to you, email [[email protected]](mailto:[email protected]). Do not open a public issue to request a private contact channel.
|
||||
Use **Report a vulnerability** in the repository's **Security** tab to submit the report privately. Do not open a public issue to request a private contact channel.
|
||||
|
||||
Include the affected REA version, operating system/distribution, selected
|
||||
provider and version, impact, reproduction conditions, and any suggested
|
||||
|
||||
+20
-23
@@ -16,8 +16,6 @@ notices supplied with the software you actually obtain or redistribute.
|
||||
| Material | Use and licensing records |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| npm dependencies | [Complete inventory](licenses/npm-dependencies.txt) of every entry in `package-lock.json`, including transitive, optional platform, development, and website dependencies. [Runtime notices](licenses/npm-runtime-notices.txt) preserve the copyright, license, and NOTICE documents from integrity-verified release archives, plus the MIT notice for Rolldown's generated runtime helpers. Development dependencies are inventoried but are not all distributed in REA's npm package. |
|
||||
| pefile ordinal tables | Import-hash ordinal names are derived from [pefile 2024.8.26](https://github.com/erocarrera/pefile/tree/4b3b1e2e568a88d4f1897d694d684f23d9e270c4), copyright 2004–2024 Ero Carrera. Preserve the [MIT license](licenses/pefile-2024.8.26-LICENSE.txt). The Python package is not a runtime dependency. |
|
||||
| UF2 family names | UF2 family IDs and short names are copied from [microsoft/uf2 `utils/uf2families.json`](https://github.com/microsoft/uf2/blob/cf3bd7f9c274b949528f8cd6a19eef2502a687d2/utils/uf2families.json), copyright Microsoft Corporation. Preserve the [MIT license](licenses/microsoft-uf2-MIT.txt). |
|
||||
| NativeAOT parser | REA's derived parser uses the MIT-licensed [ghidra-nativeaot](https://github.com/Washi1337/ghidra-nativeaot/tree/effeb734fc570c32650f88b159608979dc7b423e) work of Washi and the NativeAOT layout definitions in [dotnet/runtime](https://github.com/dotnet/runtime/tree/a3b2d40328be0be3f8dd43f0e5cc925b8827b044). Preserve [Washi's MIT notice](licenses/nativeaot-parser-MIT.txt) and the [.NET Foundation MIT notice](licenses/dotnet-runtime-MIT.txt). |
|
||||
| EVM analysis | `[email protected]`, MIT, copyright 2023 Maxim Andreev. The published npm archive omits its license document; REA retains the [original upstream license](third_party/evmole/LICENSE) and [provenance](third_party/evmole/README.md). It is also included in the runtime notices. |
|
||||
| Windows native addon | The release addon is compiled with MinGW-w64 and statically links GCC runtime libraries. Preserve the [MinGW-w64 runtime notices](licenses/MinGW-w64-runtime.txt), [GPLv3 text](licenses/GPL-3.0.txt), and [GCC Runtime Library Exception 3.1](licenses/GCC-Runtime-Library-Exception-3.1.txt). The exception governs eligible compiled combinations; it does not relicense the runtime itself under MIT. The Node-API build headers are from Node 22.19.0; its [original license and component notices](licenses/Node-22.19.0-LICENSE.txt) are retained. Node itself is supplied by the user. Snapshot provenance and release-toolchain review requirements are in [licenses/README.md](licenses/README.md). |
|
||||
@@ -54,27 +52,26 @@ REA's adapters do not grant, replace, or waive these licenses. The upstream
|
||||
executables and their dependency graphs are supplied separately. Git submodules
|
||||
pin inspection sources; they are not included in the npm package.
|
||||
|
||||
| Project | License and source | REA relationship |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Ghidra | [Apache-2.0](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/LICENSE), with the government-work and component qualifications in its [NOTICE](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/NOTICE) | Bring your own Ghidra; see [supported versions](docs/installation.md#ghidra). Copies of the 12.1.4 [LICENSE](licenses/ghidra-12.1.4-LICENSE.txt), [NOTICE](licenses/ghidra-12.1.4-NOTICE.txt), and [DISCLAIMER](licenses/ghidra-12.1.4-DISCLAIMER.txt) are retained for reference. |
|
||||
| ghidra-nativeaot | [MIT](https://github.com/Washi1337/ghidra-nativeaot/blob/effeb734fc570c32650f88b159608979dc7b423e/LICENSE.md) | Pinned inspection source; derived parser attribution above. |
|
||||
| JADX | [Apache-2.0](https://github.com/skylot/jadx/blob/v1.5.6/LICENSE) | The audited jadx-headless-mcp source uses JADX 1.5.6 for Android decompilation; the engine JAR is separately supplied. |
|
||||
| jadx-headless-mcp | [Apache-2.0](https://github.com/1013503897/jadx-headless-mcp/blob/5844800a486d2248fa949b74c9896285f5b54de2/LICENSE) | v0.7.1; pinned submodule and user-supplied JAR. |
|
||||
| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.990/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.990/NOTICE) | Bring your own `kuna`; REA verified 1.990 and never downloads, installs or bundles it. REA invokes it as a separate process. |
|
||||
| Apktool | [Apache-2.0](https://github.com/iBotPeaches/Apktool/blob/master/LICENSE.md) | User-supplied Android resource decoder. |
|
||||
| Binwalk | [MIT](https://github.com/ReFirmLabs/binwalk/blob/4fdab3d464d97b68e0af9088df3f9e2e1545b21c/LICENSE) | v3.1.0; pinned submodule and user-supplied firmware engine. |
|
||||
| Unblob | [MIT](https://github.com/onekey-sec/unblob/blob/1fcc7a0a584a70a96c31f5a276c20944d199a089/LICENSE) | 26.6.4; pinned submodule and user-supplied firmware engine. |
|
||||
| Wakaru | [Apache-2.0](https://github.com/pionxzh/wakaru/blob/8219bf5016a063d7fb215101a8c45d5959ec733f/LICENSE) | v1.14.0; pinned source and separately obtained JavaScript reconstruction tooling. |
|
||||
| WABT | [Apache-2.0](https://github.com/WebAssembly/wabt/blob/ff0ef7e0009402740c805a9744c09b05be063e48/LICENSE) | v1.0.42; external WebAssembly tools; [integration provenance](third_party/wabt/README.md). |
|
||||
| pwntools | [MIT, with GPL/BSD component qualifications](https://github.com/Gallopsled/pwntools/blob/3ad86ec5adab1abf9781ca7552a6f5d7cff7a33c/LICENSE-pwntools.txt) | 4.15.0; caller-supplied offline ELF analysis package. |
|
||||
| pyelftools | [Public-domain dedication and component notices](https://github.com/eliben/pyelftools/blob/v0.33/LICENSE) | 0.33; includes separately licensed bundled material, including Construct. |
|
||||
| Unicorn | [GPL-2.0 and component notices](https://github.com/unicorn-engine/unicorn/blob/2.1.2/COPYING) | 2.1.2; used by the caller-supplied ELF analysis profile for instruction emulation. |
|
||||
| pwndbg | [MIT](third_party/pwndbg/LICENSE.md) | 2026.09.15, commit `55596aa676a2756efb8d67034f31675170eabe48`; separately obtained debugger extension. |
|
||||
| GNU GDB | [GPL-3.0-or-later](https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/COPYING) | Caller-supplied debugger for the optional pwndbg core-file workflow. |
|
||||
| LLDB / LLVM | [Apache-2.0 with LLVM exceptions](https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT) | Caller-supplied debugger; the installed distribution's component and historical notices also apply. |
|
||||
| mitmproxy | [MIT](https://github.com/mitmproxy/mitmproxy/blob/6c09d56e4c29a92f5ad01b03199977584b8ea14f/LICENSE) | 12.2.3; separately supplied network capture proxy. |
|
||||
| Android Debug Bridge (adb) | [Apache-2.0](https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/NOTICE) | User-supplied Android device tool. A complete Android SDK download has its own [SDK license terms](https://developer.android.com/studio/terms); adb's source license does not replace them. |
|
||||
| ILSpy / ICSharpCode.Decompiler | [MIT](https://github.com/icsharpcode/ILSpy/blob/master/ICSharpCode.Decompiler/Properties/AssemblyInfo.cs) | Optional separately installed .NET decompiler; not a redistributed REA dependency. |
|
||||
| Project | License and source | REA relationship |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Ghidra | [Apache-2.0](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/LICENSE), with the government-work and component qualifications in its [NOTICE](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/NOTICE) | Bring your own Ghidra 12.1.4. Copies of its [LICENSE](licenses/ghidra-12.1.4-LICENSE.txt), [NOTICE](licenses/ghidra-12.1.4-NOTICE.txt), and [DISCLAIMER](licenses/ghidra-12.1.4-DISCLAIMER.txt) are retained for reference. |
|
||||
| ghidra-nativeaot | [MIT](https://github.com/Washi1337/ghidra-nativeaot/blob/effeb734fc570c32650f88b159608979dc7b423e/LICENSE.md) | Pinned inspection source; derived parser attribution above. |
|
||||
| JADX | [Apache-2.0](https://github.com/skylot/jadx/blob/v1.5.6/LICENSE) | The audited jadx-headless-mcp source uses JADX 1.5.6 for Android decompilation; the engine JAR is separately supplied. |
|
||||
| jadx-headless-mcp | [Apache-2.0](https://github.com/1013503897/jadx-headless-mcp/blob/5844800a486d2248fa949b74c9896285f5b54de2/LICENSE) | v0.7.1; pinned submodule and user-supplied JAR. |
|
||||
| Apktool | [Apache-2.0](https://github.com/iBotPeaches/Apktool/blob/master/LICENSE.md) | User-supplied Android resource decoder. |
|
||||
| Binwalk | [MIT](https://github.com/ReFirmLabs/binwalk/blob/4fdab3d464d97b68e0af9088df3f9e2e1545b21c/LICENSE) | v3.1.0; pinned submodule and user-supplied firmware engine. |
|
||||
| Unblob | [MIT](https://github.com/onekey-sec/unblob/blob/1fcc7a0a584a70a96c31f5a276c20944d199a089/LICENSE) | 26.6.4; pinned submodule and user-supplied firmware engine. |
|
||||
| Wakaru | [Apache-2.0](https://github.com/pionxzh/wakaru/blob/8219bf5016a063d7fb215101a8c45d5959ec733f/LICENSE) | v1.14.0; pinned source and separately obtained JavaScript reconstruction tooling. |
|
||||
| WABT | [Apache-2.0](https://github.com/WebAssembly/wabt/blob/ff0ef7e0009402740c805a9744c09b05be063e48/LICENSE) | v1.0.42; external WebAssembly tools; [integration provenance](third_party/wabt/README.md). |
|
||||
| pwntools | [MIT, with GPL/BSD component qualifications](https://github.com/Gallopsled/pwntools/blob/3ad86ec5adab1abf9781ca7552a6f5d7cff7a33c/LICENSE-pwntools.txt) | 4.15.0; caller-supplied offline ELF analysis package. |
|
||||
| pyelftools | [Public-domain dedication and component notices](https://github.com/eliben/pyelftools/blob/v0.33/LICENSE) | 0.33; includes separately licensed bundled material, including Construct. |
|
||||
| Unicorn | [GPL-2.0 and component notices](https://github.com/unicorn-engine/unicorn/blob/2.1.2/COPYING) | 2.1.2; used by the caller-supplied ELF analysis profile for instruction emulation. |
|
||||
| pwndbg | [MIT](third_party/pwndbg/LICENSE.md) | 2026.09.15, commit `55596aa676a2756efb8d67034f31675170eabe48`; separately obtained debugger extension. |
|
||||
| GNU GDB | [GPL-3.0-or-later](https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/COPYING) | Caller-supplied debugger for the optional pwndbg core-file workflow. |
|
||||
| LLDB / LLVM | [Apache-2.0 with LLVM exceptions](https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT) | Caller-supplied debugger; the installed distribution's component and historical notices also apply. |
|
||||
| mitmproxy | [MIT](https://github.com/mitmproxy/mitmproxy/blob/6c09d56e4c29a92f5ad01b03199977584b8ea14f/LICENSE) | 12.2.3; separately supplied network capture proxy. |
|
||||
| Android Debug Bridge (adb) | [Apache-2.0](https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/NOTICE) | User-supplied Android device tool. A complete Android SDK download has its own [SDK license terms](https://developer.android.com/studio/terms); adb's source license does not replace them. |
|
||||
| ILSpy / ICSharpCode.Decompiler | [MIT](https://github.com/icsharpcode/ILSpy/blob/master/ICSharpCode.Decompiler/Properties/AssemblyInfo.cs) | Optional separately installed .NET decompiler; not a redistributed REA dependency. |
|
||||
|
||||
Ghidra's original notice credits software developed at the National Security
|
||||
Agency. It explains that US-government-created portions are not subject to US
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# Plugin icon
|
||||
|
||||
`rea-plugin.png` is a square adaptation of the left panel of REA's GitHub
|
||||
social preview, retrieved on 2026-10-28:
|
||||
|
||||
https://repository-images.githubusercontent.com/1209966933/1a3fef9a-be4b-41f4-8830-a94c0e8288cc
|
||||
|
||||
The image was adapted with imagegen, preserving the cyan REA lettering,
|
||||
pixel typography, orange rule, and taglines. Both plugin manifests reference
|
||||
this shared asset; only the PNG is included in the npm package.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,91 +1,38 @@
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||
import ghidra.app.services.Analyzer;
|
||||
import ghidra.app.util.headless.HeadlessScript;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
|
||||
/**
|
||||
* Disable the named auto-analyzers before default auto-analysis.
|
||||
*
|
||||
* REA_GHIDRA_DISABLED_ANALYZERS is process-wide, but Ghidra registers only the
|
||||
* installed analyzers whose canAnalyze(program) accepts this program. A name an
|
||||
* installed analyzer provides without being registered here is not applicable
|
||||
* and is recorded instead of rejected; a name no installed analyzer provides
|
||||
* aborts the import. The outcome is stored as a program property that the
|
||||
* bridge reports during its handshake.
|
||||
*/
|
||||
/** Disable the named auto-analyzers before default auto-analysis. */
|
||||
public final class ReaGhidraAnalysisOptions extends HeadlessScript {
|
||||
static final String REPORT_OPTION = "REA disabled analyzers";
|
||||
private static final String SETTING = "REA_GHIDRA_DISABLED_ANALYZERS";
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
String[] arguments = getScriptArgs();
|
||||
if (arguments.length != 1 || arguments[0].isBlank()) {
|
||||
throw new IllegalArgumentException(
|
||||
"ReaGhidraAnalysisOptions requires one comma-separated " + SETTING + " argument");
|
||||
"ReaGhidraAnalysisOptions requires one comma-separated analyzer-name argument");
|
||||
}
|
||||
String[] names = arguments[0].split(",");
|
||||
boolean[] registered = new boolean[names.length];
|
||||
AutoAnalysisManager analysis = AutoAnalysisManager.getAnalysisManager(currentProgram);
|
||||
Map<String, String> options = getCurrentAnalysisOptionsAndValues(currentProgram);
|
||||
Set<String> installed = null;
|
||||
for (int index = 0; index < names.length; index++) {
|
||||
for (String requestedName : names) {
|
||||
monitor.checkCancelled();
|
||||
String name = names[index].trim();
|
||||
names[index] = name;
|
||||
if (analysis.getAnalyzer(name) != null) {
|
||||
if (!options.containsKey(name)) {
|
||||
throw new IllegalStateException(SETTING + " names " + name +
|
||||
", which Ghidra registered for this program without an enablement option");
|
||||
}
|
||||
registered[index] = true;
|
||||
continue;
|
||||
}
|
||||
if (installed == null) installed = installedAnalyzerNames();
|
||||
if (!installed.contains(name)) {
|
||||
throw new IllegalArgumentException(SETTING + " names " + name +
|
||||
", which is not an analyzer in this Ghidra installation");
|
||||
String name = requestedName.trim();
|
||||
if (analysis.getAnalyzer(name) == null || !options.containsKey(name)) {
|
||||
throw new IllegalArgumentException("Unknown Ghidra analyzer for this program: " + name);
|
||||
}
|
||||
}
|
||||
for (int index = 0; index < names.length; index++) {
|
||||
for (String requestedName : names) {
|
||||
monitor.checkCancelled();
|
||||
if (registered[index]) setAnalysisOption(currentProgram, names[index], "false");
|
||||
setAnalysisOption(currentProgram, requestedName.trim(), "false");
|
||||
}
|
||||
options = getCurrentAnalysisOptionsAndValues(currentProgram);
|
||||
JsonArray analyzers = new JsonArray();
|
||||
for (int index = 0; index < names.length; index++) {
|
||||
for (String requestedName : names) {
|
||||
monitor.checkCancelled();
|
||||
String name = names[index];
|
||||
if (registered[index] && !"false".equals(options.get(name))) {
|
||||
throw new IllegalStateException(SETTING + " names " + name +
|
||||
", which remained enabled for this program");
|
||||
String name = requestedName.trim();
|
||||
if (!"false".equals(options.get(name))) {
|
||||
throw new IllegalStateException("Ghidra analyzer remained enabled: " + name);
|
||||
}
|
||||
JsonObject analyzer = new JsonObject();
|
||||
analyzer.addProperty("name", name);
|
||||
analyzer.addProperty("status", registered[index] ? "disabled" : "not_applicable");
|
||||
analyzers.add(analyzer);
|
||||
}
|
||||
JsonObject report = new JsonObject();
|
||||
report.addProperty("format", "rea-ghidra-disabled-analyzers-v1");
|
||||
report.add("analyzers", analyzers);
|
||||
currentProgram.getOptions(Program.PROGRAM_INFO).setString(REPORT_OPTION, report.toString());
|
||||
println("REA disabled analyzers: " + report);
|
||||
}
|
||||
|
||||
/** The same analyzer universe AutoAnalysisManager filters by canAnalyze(program). */
|
||||
private static Set<String> installedAnalyzerNames() {
|
||||
Set<String> names = new HashSet<>();
|
||||
for (Analyzer analyzer : ClassSearcher.getInstances(Analyzer.class)) {
|
||||
names.add(analyzer.getName());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
}
|
||||
+278
-1112
File diff suppressed because it is too large.
Load diff
@@ -1,193 +0,0 @@
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.program.database.mem.FileBytes;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.program.model.mem.MemoryBlockSourceInfo;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Rebuild a GameCube/Wii DOL memory map from the validated load ranges REA's TypeScript
|
||||
* admission wrote. BinaryLoader maps the whole file once at address 0; this
|
||||
* script replaces that block with text, data, and BSS blocks at their load addresses.
|
||||
*/
|
||||
public final class ReaGhidraPrepareDol extends GhidraScript {
|
||||
// Canonical v2 header, 18 section rows, 12 gap rows and entry, using uint32 hex.
|
||||
private static final int MAX_PLAN_BYTES = 13 + 18 * 49 + 12 * 30 + 17;
|
||||
private static final class Section {
|
||||
final String kind;
|
||||
final int index;
|
||||
final long fileOffset;
|
||||
final long address;
|
||||
final long size;
|
||||
|
||||
Section(String kind, int index, long fileOffset, long address, long size) {
|
||||
this.kind = kind;
|
||||
this.index = index;
|
||||
this.fileOffset = fileOffset;
|
||||
this.address = address;
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private record BssGap(long address, long size) {}
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
String[] arguments = getScriptArgs();
|
||||
if (arguments.length != 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"GameCube DOL preparation requires exactly one section table path"
|
||||
);
|
||||
}
|
||||
if (!currentProgram.getExecutableFormat().equals("Raw Binary") ||
|
||||
!currentProgram.getLanguageID().getIdAsString().startsWith("PowerPC:BE:32:")) {
|
||||
throw new IllegalArgumentException(
|
||||
"GameCube DOL preparation requires a BinaryLoader import in big-endian 32-bit PowerPC"
|
||||
);
|
||||
}
|
||||
Path tablePath = Path.of(arguments[0]);
|
||||
if (!Files.isRegularFile(tablePath, LinkOption.NOFOLLOW_LINKS)) {
|
||||
throw new IllegalArgumentException("GameCube DOL section table is not a regular file");
|
||||
}
|
||||
if (Files.size(tablePath) > MAX_PLAN_BYTES) {
|
||||
throw new IllegalArgumentException("GameCube DOL plan exceeds the canonical format's byte bound");
|
||||
}
|
||||
|
||||
List<Section> sections = new ArrayList<>();
|
||||
List<BssGap> bssGaps = new ArrayList<>();
|
||||
int expectedSections = -1;
|
||||
int expectedGaps = -1;
|
||||
long entry = -1;
|
||||
for (String line : Files.readAllLines(tablePath)) {
|
||||
String[] fields = line.trim().split("\\s+");
|
||||
switch (fields[0]) {
|
||||
case "plan" -> {
|
||||
requireFields(fields, 4, line);
|
||||
if (expectedSections >= 0 || !fields[1].equals("2")) {
|
||||
throw new IllegalArgumentException("Duplicate or unsupported GameCube DOL plan header");
|
||||
}
|
||||
expectedSections = Integer.parseInt(fields[2]);
|
||||
expectedGaps = Integer.parseInt(fields[3]);
|
||||
// A DOL has 18 slots, including at most 11 data slots and 12 BSS gaps.
|
||||
if (expectedSections < 1 || expectedSections > 18 || expectedGaps < 0 || expectedGaps > 12) {
|
||||
throw new IllegalArgumentException("GameCube DOL plan counts exceed the format's slot bounds");
|
||||
}
|
||||
}
|
||||
case "section" -> {
|
||||
requireFields(fields, 6, line);
|
||||
if (expectedSections < 0 || sections.size() >= expectedSections) {
|
||||
throw new IllegalArgumentException("GameCube DOL section exceeds its declared count");
|
||||
}
|
||||
sections.add(new Section(
|
||||
fields[1],
|
||||
Integer.parseInt(fields[2]),
|
||||
Long.decode(fields[3]),
|
||||
Long.decode(fields[4]),
|
||||
Long.decode(fields[5])
|
||||
));
|
||||
}
|
||||
case "bss-gap" -> {
|
||||
requireFields(fields, 3, line);
|
||||
if (expectedGaps < 0 || bssGaps.size() >= expectedGaps) {
|
||||
throw new IllegalArgumentException("GameCube DOL BSS gap exceeds its declared count");
|
||||
}
|
||||
long address = Long.decode(fields[1]);
|
||||
long size = Long.decode(fields[2]);
|
||||
if (address < 0 || size <= 0) {
|
||||
throw new IllegalArgumentException("GameCube DOL BSS gap is not a positive range");
|
||||
}
|
||||
bssGaps.add(new BssGap(address, size));
|
||||
}
|
||||
case "entry" -> {
|
||||
requireFields(fields, 2, line);
|
||||
entry = Long.decode(fields[1]);
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unknown GameCube DOL table line: " + line);
|
||||
}
|
||||
}
|
||||
if (expectedSections != sections.size() || expectedGaps != bssGaps.size() || entry < 0) {
|
||||
throw new IllegalArgumentException("GameCube DOL section table is incomplete");
|
||||
}
|
||||
|
||||
Memory memory = currentProgram.getMemory();
|
||||
List<FileBytes> files = memory.getAllFileBytes();
|
||||
if (files.size() != 1) {
|
||||
throw new IllegalStateException("GameCube DOL preparation requires exactly one imported file image");
|
||||
}
|
||||
FileBytes fileBytes = files.get(0);
|
||||
AddressSpace space = currentProgram.getAddressFactory().getDefaultAddressSpace();
|
||||
|
||||
// Remove the whole-file block before creating section blocks, so no two blocks claim one address.
|
||||
for (MemoryBlock block : memory.getBlocks()) {
|
||||
for (MemoryBlockSourceInfo source : block.getSourceInfos()) {
|
||||
if (source.getFileBytes().isPresent() && source.getFileBytes().get().equals(fileBytes)) {
|
||||
memory.removeBlock(block, monitor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Section section : sections) {
|
||||
if (section.fileOffset + section.size > fileBytes.getSize()) {
|
||||
throw new IllegalArgumentException(
|
||||
"GameCube DOL " + section.kind + " " + section.index + " exceeds the imported file"
|
||||
);
|
||||
}
|
||||
boolean text = section.kind.equals("text");
|
||||
if (!text && !section.kind.equals("data")) {
|
||||
throw new IllegalArgumentException("Unknown GameCube DOL section kind: " + section.kind);
|
||||
}
|
||||
MemoryBlock block = memory.createInitializedBlock(
|
||||
"." + section.kind + section.index,
|
||||
space.getAddress(section.address),
|
||||
fileBytes,
|
||||
section.fileOffset,
|
||||
section.size,
|
||||
false
|
||||
);
|
||||
block.setRead(true);
|
||||
block.setWrite(!text);
|
||||
block.setExecute(text);
|
||||
}
|
||||
|
||||
// Apply the domain owner's exact uninitialized ranges; do not reinterpret BSS here.
|
||||
int gapIndex = 0;
|
||||
for (BssGap gap : bssGaps) {
|
||||
MemoryBlock block = memory.createUninitializedBlock(
|
||||
".bss" + gapIndex++,
|
||||
space.getAddress(gap.address()),
|
||||
gap.size(),
|
||||
false
|
||||
);
|
||||
block.setRead(true);
|
||||
block.setWrite(true);
|
||||
block.setExecute(false);
|
||||
}
|
||||
|
||||
Address entryAddress = space.getAddress(entry);
|
||||
MemoryBlock entryBlock = memory.getBlock(entryAddress);
|
||||
if (entryBlock == null || !entryBlock.isExecute()) {
|
||||
throw new IllegalArgumentException("GameCube DOL entry must lie in an executable section");
|
||||
}
|
||||
addEntryPoint(entryAddress);
|
||||
if (!disassemble(entryAddress) || createFunction(entryAddress, "__start") == null) {
|
||||
throw new IllegalStateException("GameCube DOL entry cannot be decoded as a function");
|
||||
}
|
||||
currentProgram.getOptions(Program.PROGRAM_INFO).setBoolean("REA raw binary prepared", true);
|
||||
currentProgram.getOptions(Program.PROGRAM_INFO).setBoolean("REA GameCube DOL prepared", true);
|
||||
}
|
||||
|
||||
private static void requireFields(String[] fields, int count, String line) {
|
||||
if (fields.length != count) {
|
||||
throw new IllegalArgumentException("Malformed GameCube DOL table line: " + line);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.program.database.SpecExtension;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.data.DoubleDataType;
|
||||
import ghidra.program.model.data.Float16DataType;
|
||||
import ghidra.program.model.data.FloatDataType;
|
||||
import ghidra.program.model.data.FunctionDefinition;
|
||||
import ghidra.program.model.data.IntegerDataType;
|
||||
import ghidra.program.model.data.StructureDataType;
|
||||
import ghidra.program.model.lang.CompilerSpec;
|
||||
import ghidra.program.model.lang.Language;
|
||||
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.Varnode;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Apply the committed RISC-V calling-convention policy after analysis. Ghidra's generic RV32
|
||||
* compiler specification models ilp32d. Each policy derives a program-specific prototype from the
|
||||
* program's own default prototype, as its {@link Policy} row and docs/ghidra-riscv.md state. The
|
||||
* script installs the prototype as a specification extension and assigns it, with dynamic
|
||||
* storage, to every non-thunk function, external function and function definition.
|
||||
*/
|
||||
public final class ReaGhidraRiscvCallingConvention extends GhidraScript {
|
||||
static final String REPORT_KEY = "REA RISC-V calling convention";
|
||||
/** RISC-V floating-point registers by ABI name: ft0-ft11, fs0-fs11 and fa0-fa7. */
|
||||
private static final Pattern FLOAT_REGISTER = Pattern.compile("f[ast]\\d+");
|
||||
|
||||
/**
|
||||
* What a policy changes in the default ilp32d prototype. Every policy also passes values of 9
|
||||
* bytes or more, such as {@code long double}, by reference.
|
||||
*
|
||||
* @param floatEntryBytes the largest value a floating-point register may pass or return; 0
|
||||
* removes those entries and every rule that assigns floating-point storage
|
||||
* @param preservesFloatRegisters whether fs0-fs11 stay preserved across calls
|
||||
*/
|
||||
private record Policy(
|
||||
String id, String prototype, int floatEntryBytes, boolean preservesFloatRegisters) {}
|
||||
|
||||
private static final List<Policy> POLICIES =
|
||||
List.of(new Policy("ilp32-prototype-v1", "__rea_ilp32", 0, false));
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
String[] arguments = getScriptArgs();
|
||||
Policy policy =
|
||||
POLICIES.stream()
|
||||
.filter(candidate -> arguments.length == 1 && candidate.id().equals(arguments[0]))
|
||||
.findFirst()
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
"REA RISC-V calling convention requires one of the policies "
|
||||
+ POLICIES.stream().map(Policy::id).toList()));
|
||||
Language language = currentProgram.getLanguage();
|
||||
if (!language.getProcessor().toString().equals("RISCV")
|
||||
|| currentProgram.getDefaultPointerSize() != 4)
|
||||
throw new IllegalStateException(
|
||||
"REA " + policy.id() + " requires a 32-bit RISC-V program, not "
|
||||
+ language.getLanguageID());
|
||||
CompilerSpec spec = currentProgram.getCompilerSpec();
|
||||
new SpecExtension(currentProgram)
|
||||
.addReplaceCompilerSpecExtension(
|
||||
derive(spec.getDefaultCallingConvention(), spec, language, policy), monitor);
|
||||
PrototypeModel model =
|
||||
currentProgram.getCompilerSpec().getCallingConvention(policy.prototype());
|
||||
if (model == null)
|
||||
throw new IllegalStateException("REA " + policy.prototype() + " was not installed");
|
||||
selfCheck(model, policy, language);
|
||||
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()) continue;
|
||||
if (function.hasCustomVariableStorage()) function.setCustomVariableStorage(false);
|
||||
function.setCallingConvention(policy.prototype());
|
||||
if (policy.floatEntryBytes() == 0) {
|
||||
List<VariableStorage> storage = new ArrayList<>();
|
||||
storage.add(function.getReturn().getVariableStorage());
|
||||
for (Parameter parameter : function.getParameters())
|
||||
storage.add(parameter.getVariableStorage());
|
||||
// An untyped function has no assigned storage yet; the decompiler applies the
|
||||
// prototype when it recovers one.
|
||||
requireIntegerStorage(
|
||||
storage.toArray(VariableStorage[]::new), function.getName(true), true);
|
||||
}
|
||||
functions += 1;
|
||||
}
|
||||
int definitions = 0;
|
||||
Iterator<DataType> types = currentProgram.getDataTypeManager().getAllDataTypes();
|
||||
while (types.hasNext()) {
|
||||
if (!(types.next() instanceof FunctionDefinition definition)) continue;
|
||||
definition.setCallingConvention(policy.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.id() + "\",\"functions\":" + functions
|
||||
+ ",\"definitions\":" + definitions + "}");
|
||||
println(
|
||||
"REA RISC-V " + policy.prototype() + ": functions=" + functions
|
||||
+ " definitions=" + definitions);
|
||||
}
|
||||
|
||||
/** The default ilp32d prototype changed as the policy states. */
|
||||
private static String derive(
|
||||
PrototypeModel base, CompilerSpec spec, Language language, Policy policy) 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", policy.prototype());
|
||||
List<Element> floatEntries = new ArrayList<>();
|
||||
NodeList entries = root.getElementsByTagName("pentry");
|
||||
for (int index = 0; index < entries.getLength(); index += 1) {
|
||||
Element entry = (Element) entries.item(index);
|
||||
if (entry.getAttribute("storage").equals("float")) floatEntries.add(entry);
|
||||
}
|
||||
if (floatEntries.isEmpty())
|
||||
throw new IllegalStateException("Default prototype has no floating-point entries");
|
||||
if (policy.floatEntryBytes() == 0) {
|
||||
List<Node> removed = new ArrayList<>(floatEntries);
|
||||
NodeList rules = root.getElementsByTagName("rule");
|
||||
for (int index = 0; index < rules.getLength(); index += 1) {
|
||||
Element rule = (Element) rules.item(index);
|
||||
if (usesFloatStorage(rule)) removed.add(rule);
|
||||
}
|
||||
if (removed.size() == floatEntries.size())
|
||||
throw new IllegalStateException("Default prototype has no floating-point rules to remove");
|
||||
for (Node node : removed) node.getParentNode().removeChild(node);
|
||||
} else
|
||||
for (Element entry : floatEntries)
|
||||
entry.setAttribute("maxsize", Integer.toString(policy.floatEntryBytes()));
|
||||
// Values over 2 x XLEN go by reference. Returns need no rule: a value that the return
|
||||
// registers cannot hold already becomes a caller buffer passed in a0.
|
||||
NodeList inputs = root.getElementsByTagName("input");
|
||||
if (inputs.getLength() != 1)
|
||||
throw new IllegalStateException("Default prototype has no single input list");
|
||||
Element input = (Element) inputs.item(0);
|
||||
Element last = null;
|
||||
for (Node child = input.getFirstChild(); child != null; child = child.getNextSibling())
|
||||
if (child instanceof Element element && element.getTagName().equals("rule")) last = element;
|
||||
if (last == null || !joinsAnyValue(last))
|
||||
throw new IllegalStateException("Default prototype's input list does not end with a join");
|
||||
Element byReference = document.createElement("rule");
|
||||
Element datatype = document.createElement("datatype");
|
||||
datatype.setAttribute("name", "any");
|
||||
datatype.setAttribute("minsize", "9");
|
||||
byReference.appendChild(datatype);
|
||||
byReference.appendChild(document.createElement("convert_to_ptr"));
|
||||
input.insertBefore(byReference, last);
|
||||
if (!policy.preservesFloatRegisters()) {
|
||||
NodeList killedLists = root.getElementsByTagName("killedbycall");
|
||||
Element killed = killedLists.getLength() == 0 ? null : (Element) killedLists.item(0);
|
||||
if (killed == null) {
|
||||
killed = document.createElement("killedbycall");
|
||||
root.appendChild(killed);
|
||||
}
|
||||
List<Element> moved = new ArrayList<>();
|
||||
NodeList preservedLists = root.getElementsByTagName("unaffected");
|
||||
for (int list = 0; list < preservedLists.getLength(); list += 1) {
|
||||
NodeList preserved = preservedLists.item(list).getChildNodes();
|
||||
for (int index = 0; index < preserved.getLength(); index += 1) {
|
||||
if (preserved.item(index) instanceof Element varnode
|
||||
&& isFloatRegister(varnode, language))
|
||||
moved.add(varnode);
|
||||
}
|
||||
}
|
||||
for (Element varnode : moved) {
|
||||
varnode.getParentNode().removeChild(varnode);
|
||||
killed.appendChild(varnode);
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
/** The installed prototype places each class of value as its policy states. */
|
||||
private void selfCheck(PrototypeModel model, Policy policy, Language language) {
|
||||
StructureDataType pair = new StructureDataType("rea_float_pair", 0);
|
||||
pair.add(FloatDataType.dataType);
|
||||
pair.add(FloatDataType.dataType);
|
||||
VariableStorage[] storage =
|
||||
model.getStorageLocations(
|
||||
currentProgram,
|
||||
new DataType[] {
|
||||
DoubleDataType.dataType, FloatDataType.dataType, DoubleDataType.dataType,
|
||||
IntegerDataType.dataType, pair, Float16DataType.dataType
|
||||
},
|
||||
false);
|
||||
VariableStorage quad = storage[storage.length - 1];
|
||||
if (quad.size() != currentProgram.getDefaultPointerSize() || quad.getRegister() == null)
|
||||
throw new IllegalStateException(
|
||||
"REA " + policy.prototype() + " does not pass a 16-byte value by reference: " + quad);
|
||||
if (policy.floatEntryBytes() == 0)
|
||||
requireIntegerStorage(storage, policy.prototype() + " self-check", false);
|
||||
if (policy.preservesFloatRegisters()) return;
|
||||
for (Varnode preserved : model.getUnaffectedList()) {
|
||||
Register register = language.getRegister(preserved.getAddress(), preserved.getSize());
|
||||
if (register != null && FLOAT_REGISTER.matcher(register.getName()).matches())
|
||||
throw new IllegalStateException(
|
||||
"REA " + policy.prototype() + " still preserves " + register.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean joinsAnyValue(Element rule) {
|
||||
List<Element> children = new ArrayList<>();
|
||||
for (Node child = rule.getFirstChild(); child != null; child = child.getNextSibling())
|
||||
if (child instanceof Element element) children.add(element);
|
||||
return children.size() == 2
|
||||
&& children.get(0).getTagName().equals("datatype")
|
||||
&& children.get(0).getAttribute("name").equals("any")
|
||||
&& children.get(1).getTagName().equals("join");
|
||||
}
|
||||
|
||||
/** Whether a rule's action, at any depth, assigns floating-point storage. */
|
||||
private static boolean usesFloatStorage(Element element) {
|
||||
NodeList children = element.getChildNodes();
|
||||
for (int index = 0; index < children.getLength(); index += 1) {
|
||||
if (!(children.item(index) instanceof Element child)) continue;
|
||||
if (child.getAttribute("storage").equals("float") || usesFloatStorage(child)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isFloatRegister(Element varnode, Language language) {
|
||||
if (!varnode.getTagName().equals("varnode")
|
||||
|| !varnode.getAttribute("space").equals("register"))
|
||||
return false;
|
||||
Register register =
|
||||
language.getRegister(
|
||||
language.getAddressFactory().getRegisterSpace()
|
||||
.getAddress(Long.decode(varnode.getAttribute("offset"))),
|
||||
Integer.parseInt(varnode.getAttribute("size")));
|
||||
return register != null && FLOAT_REGISTER.matcher(register.getName()).matches();
|
||||
}
|
||||
|
||||
private static void requireIntegerStorage(
|
||||
VariableStorage[] storage, String owner, boolean allowUnassigned) {
|
||||
for (VariableStorage item : storage) {
|
||||
if (item.isBadStorage() || (!allowUnassigned && item.isUnassignedStorage()))
|
||||
throw new IllegalStateException("REA RISC-V storage unresolved for " + owner);
|
||||
List<Register> registers = item.getRegisters();
|
||||
if (registers == null) continue; // stack-only storage
|
||||
for (Register register : registers) {
|
||||
if (FLOAT_REGISTER.matcher(register.getName()).matches())
|
||||
throw new IllegalStateException(
|
||||
"REA RISC-V storage still uses " + register.getName() + " for " + owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
-354
@@ -8,103 +8,12 @@ API access on this thread: moving dispatch to a worker can deadlock Hopper.
|
||||
import json
|
||||
import hmac
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
from typing import Any, Optional, Protocol, Sequence
|
||||
|
||||
BAD_ADDRESSES = (-1, 0xFFFFFFFFFFFFFFFF, None)
|
||||
# The native label setter truncates NSString names at this UTF-16 extent.
|
||||
HOPPER_SYMBOL_NAME_UTF16_UNITS = 1024
|
||||
# Request JSON payload budget in bytes, excluding the trailing LF.
|
||||
# REA_BRIDGE_MAX_REQUEST_BYTES selects it. Unset stays 16777216.
|
||||
# 7464 is the authenticated Cutter frame of the largest bounded symbol name.
|
||||
# Cutter's 64-receipt recovery status is smaller with a null path. A path
|
||||
# that does not fit is null, not an older descriptor path.
|
||||
# 536870887 is Node's 536870888-character string ceiling minus the framing LF.
|
||||
# The ceiling is the encoded payload; JSON expansion is measured before it.
|
||||
# Encoded JSON allowance, in bytes, shared by one instruction list, one
|
||||
# native-caller list, and one caller-scan page. This release has no runtime override.
|
||||
CALLER_SCAN_ALLOWANCE_BYTES = 512 * 1024
|
||||
_BRIDGE_MAX_REQUEST_BYTES_SETTING = "REA_BRIDGE_MAX_REQUEST_BYTES"
|
||||
DEFAULT_BRIDGE_MAX_REQUEST_BYTES = 16 * 1024 * 1024
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES = 7464
|
||||
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES = 536870887
|
||||
|
||||
|
||||
def _bridge_max_request_bytes_constraint():
|
||||
return (
|
||||
"REA_BRIDGE_MAX_REQUEST_BYTES must be a positive decimal integer "
|
||||
"from %d to %d bytes; the default is %d bytes (16 MiB). "
|
||||
"A value below %d bytes cannot carry the largest admitted "
|
||||
"symbol-name command, and a larger value is refused so the encoded "
|
||||
"payload plus its framing LF stays one string under Node's "
|
||||
"single-string ceiling."
|
||||
% (
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
DEFAULT_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _parse_bridge_max_request_bytes(raw):
|
||||
"""Refuse a non-decimal or out-of-range ceiling. Never fall back."""
|
||||
constraint = _bridge_max_request_bytes_constraint()
|
||||
if isinstance(raw, bool):
|
||||
raise RuntimeError(constraint)
|
||||
if isinstance(raw, int):
|
||||
value = raw
|
||||
elif isinstance(raw, str):
|
||||
# ASCII digits only. str.isdigit() accepts numerals such as U+0668
|
||||
# that the TypeScript setting rejects.
|
||||
if re.fullmatch(r"[1-9][0-9]*", raw) is None:
|
||||
raise RuntimeError(constraint)
|
||||
value = int(raw)
|
||||
else:
|
||||
raise RuntimeError(constraint)
|
||||
if (
|
||||
value < MINIMUM_BRIDGE_MAX_REQUEST_BYTES
|
||||
or value > MAXIMUM_BRIDGE_MAX_REQUEST_BYTES
|
||||
):
|
||||
raise RuntimeError(constraint)
|
||||
return value
|
||||
|
||||
|
||||
def _resolve_bridge_max_request_bytes():
|
||||
"""Prefer the bootstrap injection, then the process environment, then 16 MiB."""
|
||||
injected = globals().get(_BRIDGE_MAX_REQUEST_BYTES_SETTING)
|
||||
if injected is not None:
|
||||
return _parse_bridge_max_request_bytes(injected), "setting"
|
||||
raw = os.environ.get(_BRIDGE_MAX_REQUEST_BYTES_SETTING)
|
||||
if raw is None:
|
||||
return DEFAULT_BRIDGE_MAX_REQUEST_BYTES, "default"
|
||||
return _parse_bridge_max_request_bytes(raw), "setting"
|
||||
|
||||
|
||||
MAX_REQUEST_BYTES, BRIDGE_MAX_REQUEST_BYTES_SOURCE = (
|
||||
_resolve_bridge_max_request_bytes()
|
||||
)
|
||||
|
||||
|
||||
def _bridge_max_request_bytes_recovery():
|
||||
return (
|
||||
"The effective REA_BRIDGE_MAX_REQUEST_BYTES value is %d bytes "
|
||||
"(source %s; default %d, floor %d, maximum %d). "
|
||||
"Set REA_BRIDGE_MAX_REQUEST_BYTES in the environment that starts REA "
|
||||
"to a decimal integer from %d to %d, then restart REA. "
|
||||
"Restart Hopper or Cutter from that same environment so the bridge "
|
||||
"uses this ceiling."
|
||||
% (
|
||||
MAX_REQUEST_BYTES,
|
||||
BRIDGE_MAX_REQUEST_BYTES_SOURCE,
|
||||
DEFAULT_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
)
|
||||
)
|
||||
_rea_session_document = None
|
||||
_search_inventory_cache = {}
|
||||
_pseudocode_cache = {}
|
||||
@@ -235,9 +144,6 @@ EXHAUSTIVE_ANALYSIS_METHODS = frozenset(
|
||||
"procedure_assembly",
|
||||
"procedure_callees",
|
||||
"procedure_callers",
|
||||
"rea_caller_scan_page",
|
||||
"rea_caller_scan_target",
|
||||
"rea_caller_base",
|
||||
"procedure_info",
|
||||
"procedure_pseudo_code",
|
||||
"procedure_references",
|
||||
@@ -330,27 +236,11 @@ def _name_matches(document, address, name):
|
||||
return observed == name or (not name and observed is None)
|
||||
|
||||
|
||||
def _bookmark_api(document, name):
|
||||
method = getattr(document, name, None)
|
||||
if not callable(method):
|
||||
raise CapabilityUnavailableError(
|
||||
"Hopper's public Python API does not expose %s" % name
|
||||
)
|
||||
return method
|
||||
|
||||
|
||||
def _bookmark_name(document, address):
|
||||
return _bookmark_api(document, "getBookmarkName")(address)
|
||||
|
||||
|
||||
def _address_selection(document, value=None):
|
||||
"""Own the coordinate and whether it was selected by symbol or address."""
|
||||
def _address(document, value=None):
|
||||
"""Keep explicit hexadecimal coordinates distinct from symbol names."""
|
||||
if value is None:
|
||||
return document.getCurrentAddress(), "cursor"
|
||||
return document.getCurrentAddress()
|
||||
_api_text(value, "Address")
|
||||
if value.startswith("file-offset:"):
|
||||
raise InvalidRequestError("Hopper does not support file-offset: selectors; select Ghidra for source-file coordinates or use a Hopper analysis address")
|
||||
source = "address"
|
||||
if value.lower().startswith("0x"):
|
||||
try:
|
||||
address = int(value, 16)
|
||||
@@ -361,8 +251,6 @@ def _address_selection(document, value=None):
|
||||
# Prefer an observed symbol, retaining bare-hex compatibility only
|
||||
# when Hopper has no symbol with the supplied name.
|
||||
address = document.getAddressForName(value)
|
||||
if address not in BAD_ADDRESSES:
|
||||
source = "symbol"
|
||||
if address in BAD_ADDRESSES:
|
||||
try:
|
||||
address = int(value, 16)
|
||||
@@ -370,11 +258,7 @@ def _address_selection(document, value=None):
|
||||
raise InvalidRequestError("Unknown Hopper address or name: %s" % value) from error
|
||||
if not 0 <= address < 0xFFFFFFFFFFFFFFFF:
|
||||
raise InvalidRequestError("Address is outside Hopper's usable unsigned 64-bit range: %s" % value)
|
||||
return address, source
|
||||
|
||||
|
||||
def _address(document, value=None):
|
||||
return _address_selection(document, value)[0]
|
||||
return address
|
||||
|
||||
|
||||
def _segment(document, address):
|
||||
@@ -385,15 +269,10 @@ def _segment(document, address):
|
||||
|
||||
|
||||
def _procedure(document, value=None):
|
||||
address, source = _address_selection(document, value)
|
||||
address = document.getCurrentAddress() if value is None else _address(document, value)
|
||||
result = _segment(document, address).getProcedureAtAddress(address)
|
||||
if result is None:
|
||||
raise InvalidRequestError("No procedure exists at the requested address")
|
||||
if source == "symbol" and address != result.getEntryPoint():
|
||||
raise InvalidRequestError(
|
||||
"Symbol %s at %s is inside procedure %s at %s; select its entry name/address or use resolve_containing_procedure for explicit containment"
|
||||
% (value, _hex(address), _procedure_name(result), _hex(result.getEntryPoint()))
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@@ -406,7 +285,6 @@ def _procedure_identity(procedure):
|
||||
return {
|
||||
"address": _hex(procedure.getEntryPoint()),
|
||||
"name": _procedure_name(procedure),
|
||||
"symbol": None,
|
||||
"classification": None,
|
||||
"body": {
|
||||
"available": False,
|
||||
@@ -491,30 +369,19 @@ def _instruction_addresses(procedure):
|
||||
return result
|
||||
|
||||
|
||||
def _iter_native_calls(procedure, direction):
|
||||
def _native_calls(procedure, direction):
|
||||
references = procedure.getAllCallees() if direction == "outgoing" else procedure.getAllCallers()
|
||||
kinds = {0: "none", 1: "unknown", 2: "direct", 3: "objective_c"}
|
||||
calls = {}
|
||||
unresolved = []
|
||||
for reference in references:
|
||||
source, target, kind = reference.fromAddress(), reference.toAddress(), reference.type()
|
||||
if source in BAD_ADDRESSES:
|
||||
yield None, None, None
|
||||
continue
|
||||
if target in BAD_ADDRESSES:
|
||||
yield source, None, {"address": _hex(source), "reason": "Native CallReference has no resolved target (type %s)" % kind}
|
||||
unresolved.append({"address": _hex(source), "reason": "Native CallReference has no resolved target (type %s)" % kind})
|
||||
continue
|
||||
yield source, target, {"classification": kinds.get(kind, "unknown"), "provider_type": kind, "provenance": "hopper-public-python-api:CallReference"}
|
||||
|
||||
|
||||
def _native_calls(procedure, direction):
|
||||
calls = {}
|
||||
unresolved = []
|
||||
for source, target, detail in _iter_native_calls(procedure, direction):
|
||||
if detail is None:
|
||||
continue
|
||||
if target is None:
|
||||
unresolved.append(detail)
|
||||
else:
|
||||
calls[(source, target)] = detail
|
||||
calls[(source, target)] = {"classification": kinds.get(kind, "unknown"), "provider_type": kind, "provenance": "hopper-public-python-api:CallReference"}
|
||||
return calls, sorted(unresolved, key=lambda item: int(item["address"], 16))
|
||||
|
||||
|
||||
@@ -564,134 +431,8 @@ def _procedure_map(document):
|
||||
return result
|
||||
|
||||
|
||||
def _caller_target_data(procedure):
|
||||
addresses = []
|
||||
seen = set()
|
||||
size = 0
|
||||
for block in procedure.basicBlockIterator():
|
||||
for address in _block_instruction_addresses(procedure, block):
|
||||
if address in seen:
|
||||
continue
|
||||
encoded = _hex(address)
|
||||
size += len(encoded) + 4
|
||||
if size > CALLER_SCAN_ALLOWANCE_BYTES:
|
||||
return {"target": _procedure_identity(procedure), "instructions": addresses, "instructions_complete": False}
|
||||
addresses.append(encoded)
|
||||
seen.add(address)
|
||||
return {"target": _procedure_identity(procedure), "instructions": addresses, "instructions_complete": True}
|
||||
|
||||
|
||||
def _caller_scan_target(document, params):
|
||||
return _caller_target_data(_procedure(document, params.get("procedure")))
|
||||
|
||||
|
||||
def _caller_base(document, params):
|
||||
procedure = _procedure(document, params.get("procedure"))
|
||||
callers = []
|
||||
size = 0
|
||||
complete = True
|
||||
for item in procedure.getAllCallerProcedures():
|
||||
address = _hex(item.getEntryPoint())
|
||||
size += len(address) + 4
|
||||
if size > CALLER_SCAN_ALLOWANCE_BYTES:
|
||||
complete = False
|
||||
break
|
||||
callers.append(address)
|
||||
return {
|
||||
**_caller_target_data(procedure),
|
||||
"callers": sorted(callers, key=lambda value: int(value, 16)),
|
||||
"callers_complete": complete,
|
||||
}
|
||||
|
||||
|
||||
def _caller_scan_page(document, params):
|
||||
"""Observe forward relationships in one bounded, stateless bridge request."""
|
||||
start = params.get("start")
|
||||
if isinstance(start, bool) or not isinstance(start, int) or start < 0:
|
||||
raise InvalidRequestError("Caller scan start must be a non-negative integer")
|
||||
segments = document.getSegmentsList()
|
||||
counts = [segment.getProcedureCount() for segment in segments]
|
||||
total = sum(counts)
|
||||
end = min(start + 64, total)
|
||||
if start > total:
|
||||
raise InvalidRequestError("Caller scan start exceeds the procedure inventory")
|
||||
items = []
|
||||
item_bytes = 0
|
||||
page_limit = CALLER_SCAN_ALLOWANCE_BYTES
|
||||
oversized = False
|
||||
identities = {}
|
||||
containing = {}
|
||||
|
||||
def identity(procedure):
|
||||
entry = procedure.getEntryPoint()
|
||||
if entry not in identities:
|
||||
identities[entry] = _procedure_identity(procedure)
|
||||
return identities[entry]
|
||||
|
||||
def owner(address):
|
||||
if address not in containing:
|
||||
procedure, _ = _containing_procedure(document, address)
|
||||
containing[address] = identity(procedure) if procedure is not None else None
|
||||
return containing[address]
|
||||
|
||||
segment_start = 0
|
||||
for segment, count in zip(segments, counts):
|
||||
begin, stop = max(start - segment_start, 0), min(end - segment_start, count)
|
||||
segment_start += count
|
||||
for index in range(begin, max(begin, stop)):
|
||||
candidate = segment.getProcedureAtIndex(index)
|
||||
item = {"caller": identity(candidate), "callees": [], "references": []}
|
||||
size = len(json.dumps(item, ensure_ascii=True).encode("utf-8")) + 1
|
||||
remaining = page_limit - item_bytes
|
||||
too_large = size > remaining
|
||||
if not too_large:
|
||||
for callee in candidate.getAllCalleeProcedures():
|
||||
address = _hex(callee.getEntryPoint())
|
||||
size += len(address) + 4
|
||||
if size > remaining:
|
||||
too_large = True
|
||||
break
|
||||
item["callees"].append(address)
|
||||
if not too_large:
|
||||
callee_entries = set(item["callees"])
|
||||
for source, destination, call in _iter_native_calls(candidate, "outgoing"):
|
||||
# Account for every native record, even when its target cannot
|
||||
# be associated with an observed procedure.
|
||||
size += 32
|
||||
if size > remaining:
|
||||
too_large = True
|
||||
break
|
||||
if destination is None:
|
||||
continue
|
||||
source_procedure = item["caller"] if _hex(source) == item["caller"]["address"] else owner(source)
|
||||
target_address = _hex(destination)
|
||||
if target_address in callee_entries:
|
||||
target_entry = target_address
|
||||
else:
|
||||
target_procedure = owner(destination)
|
||||
if target_procedure is None:
|
||||
continue
|
||||
target_entry = target_procedure["address"]
|
||||
edge = {
|
||||
"source_address": _hex(source), "target_address": _hex(destination),
|
||||
"source_procedure": "caller" if source_procedure is not None and source_procedure["address"] == item["caller"]["address"] else source_procedure,
|
||||
"target_procedure": target_entry,
|
||||
"call": call,
|
||||
}
|
||||
size += len(json.dumps(edge, ensure_ascii=True).encode("utf-8")) + 1
|
||||
if size > remaining:
|
||||
too_large = True
|
||||
break
|
||||
item["references"].append(edge)
|
||||
if too_large:
|
||||
oversized = not items
|
||||
break
|
||||
items.append(item)
|
||||
item_bytes += size
|
||||
else:
|
||||
continue
|
||||
break
|
||||
return {"start": start, "next": start + len(items), "total": total, "items": items, "oversized": oversized}
|
||||
def _strings(document):
|
||||
return {address: record["value"] for address, record in _string_inventory(document).items()}
|
||||
|
||||
|
||||
def _string_record(segment, address, display):
|
||||
@@ -780,7 +521,7 @@ def _search_inventory(document, kind):
|
||||
if kind == "procedure":
|
||||
values = _procedure_map(document)
|
||||
elif kind == "string":
|
||||
values = _string_inventory(document)
|
||||
values = _strings(document)
|
||||
elif kind == "name":
|
||||
values = {_hex(address): value for address, value in _name_map(document).items()}
|
||||
else:
|
||||
@@ -807,17 +548,23 @@ def _search_results(document, kind, params):
|
||||
else:
|
||||
raise CapabilityUnavailableError("Regex matching requires REA's supervised worker; the native bridge only executes literal searches")
|
||||
|
||||
selected = []
|
||||
for item in _search_inventory(document, kind):
|
||||
if not matches(item[1]):
|
||||
continue
|
||||
selected.append(item)
|
||||
if not selected:
|
||||
return []
|
||||
if kind == "string":
|
||||
return [
|
||||
{"address": address, **record}
|
||||
for address, record in _search_inventory(document, kind)
|
||||
if matches(record["value"])
|
||||
]
|
||||
records = _string_inventory(document)
|
||||
return [{"address": address, **records[address]} for address, _ in selected]
|
||||
return [
|
||||
{"address": address, "value": value}
|
||||
for address, value in _search_inventory(document, kind)
|
||||
if matches(value)
|
||||
]
|
||||
{
|
||||
"address": address,
|
||||
"value": value,
|
||||
}
|
||||
for address, value in selected
|
||||
]
|
||||
|
||||
|
||||
def _unavailable(reason):
|
||||
@@ -897,7 +644,7 @@ def _analyze_function(document, params):
|
||||
"end": _hex(_basic_block_end(procedure, block)),
|
||||
"successors": sorted(set(successors), key=lambda value: int(value, 16)),
|
||||
})
|
||||
pseudo = _pseudocode(document, procedure)
|
||||
pseudo = _pseudocode(document, procedure) or ""
|
||||
assembly_lines = _assembly(procedure).splitlines()
|
||||
callers = sorted((_procedure_identity(item) for item in procedure.getAllCallerProcedures()), key=lambda item: int(item["address"], 16))
|
||||
callees = sorted((_procedure_identity(item) for item in procedure.getAllCalleeProcedures()), key=lambda item: int(item["address"], 16))
|
||||
@@ -959,7 +706,7 @@ def _analyze_function(document, params):
|
||||
"signature": procedure.signatureString(),
|
||||
"locals": _procedure_locals(procedure),
|
||||
},
|
||||
"pseudocode": pseudo or "",
|
||||
"pseudocode": pseudo,
|
||||
"assembly": assembly_lines,
|
||||
"comments": comments,
|
||||
"callers": callers, "callees": callees,
|
||||
@@ -971,12 +718,6 @@ def _analyze_function(document, params):
|
||||
"basic_blocks": blocks,
|
||||
"native_api": None,
|
||||
"native_value_flow": None,
|
||||
"facet_availability": {
|
||||
"pseudocode": {"available": True} if pseudo is not None else _unavailable("Hopper did not supply pseudocode."),
|
||||
**{facet: {"available": True} for facet in (
|
||||
"assembly", "comments", "callers", "callees", "references", "strings_names", "cfg"
|
||||
)},
|
||||
},
|
||||
"limitations": [
|
||||
"Native CallReference classifications are observed where available; detailed reference flags remain unknown.",
|
||||
"Hopper's public Python API does not expose equivalent external or thunk classification in this dossier.",
|
||||
@@ -989,13 +730,7 @@ def _analyze_function(document, params):
|
||||
def _dispatch(method, params):
|
||||
"""Dispatch only the closed operation set implemented by REA's public tools."""
|
||||
if method == "health":
|
||||
return {
|
||||
"name": "REA Hopper bridge",
|
||||
"version": "1.0.0",
|
||||
"run_id": REA_RUN_ID,
|
||||
"process_id": os.getpid(),
|
||||
"max_request_bytes": MAX_REQUEST_BYTES,
|
||||
}
|
||||
return {"name": "REA Hopper bridge", "version": "1.0.0", "run_id": REA_RUN_ID}
|
||||
if method in ("shutdown", "shutdown_document"):
|
||||
document = _session_document()
|
||||
if document is None:
|
||||
@@ -1034,15 +769,6 @@ def _dispatch(method, params):
|
||||
if method in EXHAUSTIVE_ANALYSIS_METHODS:
|
||||
_api().require_analysis_complete(document, method)
|
||||
|
||||
if method == "rea_caller_scan_target":
|
||||
return _caller_scan_target(document, params)
|
||||
|
||||
if method == "rea_caller_base":
|
||||
return _caller_base(document, params)
|
||||
|
||||
if method == "rea_caller_scan_page":
|
||||
return _caller_scan_page(document, params)
|
||||
|
||||
if method == "analyze_function":
|
||||
return _analyze_function(document, params)
|
||||
if method == "read_function_instructions":
|
||||
@@ -1227,7 +953,6 @@ def _dispatch(method, params):
|
||||
length = sum(max(0, _basic_block_end(procedure, block) - block.getStartingAddress()) for block in blocks)
|
||||
return {
|
||||
"name": _procedure_name(procedure),
|
||||
"symbol": None,
|
||||
"entrypoint": _hex(procedure.getEntryPoint()),
|
||||
"basicblock_count": procedure.getBasicBlockCount(),
|
||||
"length": length,
|
||||
@@ -1268,41 +993,23 @@ def _dispatch(method, params):
|
||||
observed = getter(address)
|
||||
return observed == comment or (comment == "" and observed is None)
|
||||
if method == "list_bookmarks":
|
||||
return [{"address": _hex(item), "name": _bookmark_name(document, item)} for item in _bookmark_api(document, "getBookmarks")()]
|
||||
return [{"address": _hex(item), "name": document.getBookmarkName(item)} for item in document.getBookmarks()]
|
||||
if method == "set_bookmark":
|
||||
address = _address(document, params.get("address"))
|
||||
_segment(document, address)
|
||||
name = params.get("name")
|
||||
if name is not None:
|
||||
_api_text(name, "Bookmark name")
|
||||
has_bookmark = _bookmark_api(document, "hasBookmarkAtAddress")
|
||||
exists = has_bookmark(address)
|
||||
if name is None and exists:
|
||||
# Omission means preserve the existing bookmark and its name.
|
||||
return True
|
||||
_bookmark_api(document, "getBookmarkName")
|
||||
if name == "" and exists:
|
||||
rename = _bookmark_api(document, "renameBookmarkAtAddress")
|
||||
rename(address, name)
|
||||
else:
|
||||
setter = _bookmark_api(document, "setBookmarkAtAddress")
|
||||
setter(address, name)
|
||||
if not has_bookmark(address):
|
||||
return False
|
||||
observed = _bookmark_name(document, address)
|
||||
if name is None:
|
||||
return observed is None or observed == ""
|
||||
return observed == name or (name == "" and observed is None)
|
||||
document.setBookmarkAtAddress(address, name)
|
||||
return document.hasBookmarkAtAddress(address)
|
||||
if method == "unset_bookmark":
|
||||
address = _address(document, params.get("address"))
|
||||
# Permit removing a legacy orphan bookmark, but do not report success
|
||||
# for an unmapped address with no bookmark to remove.
|
||||
has_bookmark = _bookmark_api(document, "hasBookmarkAtAddress")
|
||||
remove_bookmark = _bookmark_api(document, "removeBookmarkAtAddress")
|
||||
if not has_bookmark(address):
|
||||
if not document.hasBookmarkAtAddress(address):
|
||||
_segment(document, address)
|
||||
remove_bookmark(address)
|
||||
return not has_bookmark(address)
|
||||
document.removeBookmarkAtAddress(address)
|
||||
return not document.hasBookmarkAtAddress(address)
|
||||
raise InvalidRequestError("Unknown bridge method")
|
||||
|
||||
|
||||
@@ -1312,23 +1019,9 @@ def _serve_connection(connection):
|
||||
should_stop = False
|
||||
try:
|
||||
while True:
|
||||
# Bound the read itself. The payload budget excludes LF, so permit
|
||||
# one additional byte for the delimiter while still refusing an
|
||||
# oversized unterminated peer without waiting for EOF.
|
||||
line = file.readline(MAX_REQUEST_BYTES + 1)
|
||||
line = file.readline()
|
||||
if not line:
|
||||
break
|
||||
if not line.endswith(b"\n"):
|
||||
message = (
|
||||
"Bridge request exceeds the %d-byte payload limit. %s"
|
||||
% (MAX_REQUEST_BYTES, _bridge_max_request_bytes_recovery())
|
||||
if len(line) > MAX_REQUEST_BYTES
|
||||
else "Incomplete bridge request frame"
|
||||
)
|
||||
_write_message(file, {"id": 0, "error": _safe_diagnostic(
|
||||
InvalidRequestError(message)
|
||||
)})
|
||||
break
|
||||
request_id = None
|
||||
authenticated = False
|
||||
should_stop = False
|
||||
@@ -1353,10 +1046,6 @@ def _serve_connection(connection):
|
||||
):
|
||||
raise PermissionError("Invalid bridge capability")
|
||||
authenticated = True
|
||||
# The connection grace period only bounds unauthenticated idle
|
||||
# clients. Native analysis and a connected owner's idle time
|
||||
# remain unbounded after capability validation.
|
||||
connection.settimeout(None)
|
||||
_write_message(file, {"id": request_id, "event": {
|
||||
"type": "progress",
|
||||
"phase": "hopper_bridge",
|
||||
@@ -1448,17 +1137,13 @@ def _run():
|
||||
server.bind(REA_SOCKET)
|
||||
os.chmod(REA_SOCKET, 0o600)
|
||||
server.listen(1)
|
||||
# A bridge can start after its caller's startup/cleanup deadline. Bound
|
||||
# the first accept as well as reconnects so an orphan cannot pin Hopper's
|
||||
# dedicated Python thread and prevent later documents from starting REA.
|
||||
server.settimeout(REA_RECONNECT_TIMEOUT_SECONDS)
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
connection, _ = server.accept()
|
||||
except socket.timeout:
|
||||
break
|
||||
connection.settimeout(REA_RECONNECT_TIMEOUT_SECONDS)
|
||||
connection.settimeout(None)
|
||||
if _serve_connection(connection):
|
||||
break
|
||||
# Match REA's cleanup handshake budget without pinning Hopper's
|
||||
|
||||
+12
-188
@@ -6,13 +6,11 @@ The containing mitmdump process and its configuration directory are REA-owned.
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import ipaddress
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import resource
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote_to_bytes
|
||||
|
||||
from mitmproxy import ctx, version
|
||||
from mitmproxy.io import tnetstring
|
||||
@@ -43,167 +41,6 @@ class BoundedReader:
|
||||
return self.handle.read(count)
|
||||
|
||||
|
||||
SPECIAL_SCHEMES = {"ftp", "file", "http", "https", "ws", "wss"}
|
||||
SCHEME_PREFIX = r"^[\x00-\x20]*([a-z][a-z0-9+.-]*):"
|
||||
NETWORK_PREFIX = r"^[\x00-\x20]*[/\\]{2}"
|
||||
URL_IGNORABLE_CONTROLS = "\t\n\r"
|
||||
URL_CONTROL_TRANSLATION = {ord(char): None for char in URL_IGNORABLE_CONTROLS}
|
||||
C0_CONTROL_OR_SPACE = "".join(chr(code) for code in range(0x21))
|
||||
FORBIDDEN_DOMAIN_CODE_POINTS = set(C0_CONTROL_OR_SPACE) | set("#%/:<>?@[\\]^|\x7f")
|
||||
|
||||
|
||||
def authority_start(text, base_scheme=None):
|
||||
"""Return where an authority begins and whether it is special, or None.
|
||||
|
||||
Follows WHATWG URL parsing as src/domain/recordedTransportUrl.ts does:
|
||||
special schemes (and scheme-relative references) skip every leading "/"
|
||||
or "\\" before the authority, so `https://\\user:pass@host` still has
|
||||
userinfo. File URLs never carry userinfo: an "@" in a file host makes
|
||||
WHATWG reject the whole URL, so there are no credentials to remove.
|
||||
"""
|
||||
scheme = re.match(SCHEME_PREFIX, text, flags=re.I)
|
||||
name = scheme.group(1).lower() if scheme is not None else None
|
||||
if name == "file" or (name is None and base_scheme == "file"):
|
||||
return None
|
||||
if name is not None and name in SPECIAL_SCHEMES:
|
||||
# A same-scheme Location without two slash-equivalent separators is
|
||||
# relative to the recorded request, so its @ belongs to the path.
|
||||
following = text[scheme.end():scheme.end() + 2]
|
||||
if name == base_scheme and (len(following) < 2 or any(char not in "/\\" for char in following)):
|
||||
return None
|
||||
start = scheme.end()
|
||||
else:
|
||||
if scheme is not None:
|
||||
if text[scheme.end():scheme.end() + 2] != "//":
|
||||
return None
|
||||
return scheme.end() + 2, False
|
||||
# A non-special base treats backslashes as authority data and only
|
||||
# recognizes two forward slashes as a network reference.
|
||||
if base_scheme is not None and base_scheme not in SPECIAL_SCHEMES:
|
||||
prefix = re.match(r"^[\x00-\x20]*//", text)
|
||||
return None if prefix is None else (prefix.end(), False)
|
||||
prefix = re.match(NETWORK_PREFIX, text)
|
||||
if prefix is None:
|
||||
return None
|
||||
start = prefix.end()
|
||||
while start < len(text) and text[start] in "/\\":
|
||||
start += 1
|
||||
return start, True
|
||||
|
||||
|
||||
IPV4_DIGITS = {8: "01234567", 10: "0123456789", 16: "0123456789abcdefABCDEF"}
|
||||
|
||||
|
||||
def ipv4_number(part):
|
||||
"""WHATWG IPv4 number parser: decimal, 0x hex or 0-prefixed octal, else None."""
|
||||
if part == "":
|
||||
return None
|
||||
radix, digits = 10, part
|
||||
if part[:2] in ("0x", "0X"):
|
||||
radix, digits = 16, part[2:]
|
||||
elif len(part) > 1 and part[0] == "0":
|
||||
radix, digits = 8, part[1:]
|
||||
if digits == "":
|
||||
return 0
|
||||
if any(char not in IPV4_DIGITS[radix] for char in digits):
|
||||
return None
|
||||
# Bound decimal text before int(): Python limits decimal conversions to
|
||||
# 4300 digits, and anything past 10 significant digits exceeds IPv4.
|
||||
significant = digits.lstrip("0")
|
||||
if radix == 10 and len(significant) > 10:
|
||||
return 2**32
|
||||
return int(digits, radix)
|
||||
|
||||
|
||||
def domain_parses(domain):
|
||||
"""Apply WHATWG's ends-in-a-number check and IPv4 parser to a domain."""
|
||||
parts = domain.split(".")
|
||||
if parts[-1] == "" and len(parts) > 1:
|
||||
parts.pop()
|
||||
last = parts[-1]
|
||||
if not ((last != "" and last.isascii() and last.isdigit()) or ipv4_number(last) is not None):
|
||||
return True
|
||||
numbers = [ipv4_number(part) for part in parts]
|
||||
if len(parts) > 4 or any(number is None for number in numbers):
|
||||
return False
|
||||
return all(number <= 255 for number in numbers[:-1]) and numbers[-1] < 256 ** (5 - len(numbers))
|
||||
|
||||
|
||||
def special_host_parses(host_port):
|
||||
"""Whether WHATWG could accept a special authority's host and port.
|
||||
|
||||
A rejected host fails the whole URL, so it has no credentials to remove.
|
||||
This mirrors the forbidden-code-point, empty-host, IPv4, IPv6 and port
|
||||
rules for ASCII hosts. UTS #46 mapping is not modeled: a non-ASCII host
|
||||
may map to an accepted one (fullwidth digits, ideographic full stops,
|
||||
ignored code points), so it is treated as accepted and still redacts.
|
||||
"""
|
||||
if host_port.startswith("["):
|
||||
host_end = host_port.find("]") + 1
|
||||
literal = host_port[1 : host_end - 1]
|
||||
if host_end == 0 or "%" in literal:
|
||||
return False
|
||||
try:
|
||||
ipaddress.IPv6Address(literal)
|
||||
except ValueError:
|
||||
return False
|
||||
else:
|
||||
colon = host_port.find(":")
|
||||
host_end = len(host_port) if colon < 0 else colon
|
||||
host = unquote_to_bytes(host_port[:host_end]).decode("utf-8", errors="replace")
|
||||
# Mapping never removes ASCII, so these rejections hold either way.
|
||||
if host == "" or any(char in FORBIDDEN_DOMAIN_CODE_POINTS for char in host):
|
||||
return False
|
||||
if host.isascii() and not domain_parses(host.lower()):
|
||||
return False
|
||||
port = host_port[host_end:]
|
||||
if port == "":
|
||||
return True
|
||||
digits = port[1:]
|
||||
if port[0] != ":" or not (digits == "" or (digits.isascii() and digits.isdigit())):
|
||||
return False
|
||||
# Bound the text first: int() refuses decimal strings over 4300 digits.
|
||||
significant = digits.lstrip("0")
|
||||
return len(significant) <= 5 and int(significant or "0") <= 65535
|
||||
|
||||
|
||||
def without_userinfo(value, base_scheme=None):
|
||||
"""Remove an authority's userinfo using the WHATWG authority boundary.
|
||||
|
||||
Special schemes and scheme-relative references also end the authority
|
||||
at a backslash, so later path text containing "@" is not userinfo.
|
||||
"""
|
||||
binary = isinstance(value, bytes)
|
||||
text = value.decode("latin-1") if binary else value
|
||||
# WHATWG removes these controls before interpreting scheme/separators.
|
||||
# Keep that parse view separate from the exact source spelling and bytes.
|
||||
# It also drops trailing C0 controls and spaces; offsets are unchanged.
|
||||
parsed_text = text.translate(URL_CONTROL_TRANSLATION).rstrip(C0_CONTROL_OR_SPACE)
|
||||
located = authority_start(parsed_text, base_scheme)
|
||||
if located is None:
|
||||
return value
|
||||
start, special = located
|
||||
terminators = "/?#\\" if special else "/?#"
|
||||
end = start
|
||||
while end < len(parsed_text) and parsed_text[end] not in terminators:
|
||||
end += 1
|
||||
at = parsed_text.rfind("@", start, end)
|
||||
if at < start or (special and not special_host_parses(parsed_text[at + 1 : end])):
|
||||
return value
|
||||
parsed_offset = 0
|
||||
raw_start = 0
|
||||
for raw_offset, char in enumerate(text):
|
||||
if char in URL_IGNORABLE_CONTROLS:
|
||||
continue
|
||||
if parsed_offset == start:
|
||||
raw_start = raw_offset
|
||||
if parsed_offset == at:
|
||||
safe = text[:raw_start] + text[raw_offset + 1 :]
|
||||
return safe.encode("latin-1") if binary else safe
|
||||
parsed_offset += 1
|
||||
raise CaptureFailure("decoder", "Normalized URL authority could not be bound to its source.")
|
||||
|
||||
|
||||
def project_record(state, secrets):
|
||||
binaries, numbers, redactions = [], [], []
|
||||
byte_secrets = []
|
||||
@@ -221,23 +58,16 @@ def project_record(state, secrets):
|
||||
return None
|
||||
return text
|
||||
|
||||
def transport_value(value, path, transport_url, base_scheme):
|
||||
def transport_value(value, path, transport_url):
|
||||
if not (transport_url or re.fullmatch(r"/(?:backup/)*request/(?:path|authority)", path)):
|
||||
return value, False
|
||||
binary = isinstance(value, bytes)
|
||||
pattern = rb"^([\x00-\x20]*(?:[a-z][a-z0-9+.-]*:)?//)[^/?#]*@" if binary else r"^([\x00-\x20]*(?:[a-z][a-z0-9+.-]*:)?//)[^/?#]*@"
|
||||
replacement = rb"\1" if binary else r"\1"
|
||||
separator = b"@" if binary else "@"
|
||||
if transport_url:
|
||||
safe_url = without_userinfo(value, base_scheme)
|
||||
elif path.endswith("/authority"):
|
||||
safe_url = value.rsplit(separator, 1)[-1]
|
||||
else:
|
||||
# Native request.path is origin-form unless it explicitly carries
|
||||
# a scheme (absolute-form). Leading separators are path evidence,
|
||||
# not a header URL reference's network authority.
|
||||
text = value.decode("latin-1") if binary else value
|
||||
safe_url = without_userinfo(value) if re.match(
|
||||
SCHEME_PREFIX, text.translate(URL_CONTROL_TRANSLATION), flags=re.I
|
||||
) else value
|
||||
safe_url = re.sub(pattern, replacement, value, flags=re.I)
|
||||
if path.endswith("/authority") and separator in safe_url:
|
||||
safe_url = safe_url.rsplit(separator, 1)[1]
|
||||
changed = safe_url != value
|
||||
if changed:
|
||||
redactions.append({"pointer": path, "reason": "transport-credential"})
|
||||
@@ -250,7 +80,7 @@ def project_record(state, secrets):
|
||||
del numbers[before[1]:]
|
||||
del redactions[before[2]:]
|
||||
|
||||
def visit(value, path, depth=0, credential=False, transport_url=False, base_scheme=None):
|
||||
def visit(value, path, depth=0, credential=False, transport_url=False):
|
||||
if depth > 64:
|
||||
raise CaptureFailure("input-limit", "Native capture exceeds the 64-level evidence nesting budget.", path)
|
||||
if credential:
|
||||
@@ -260,7 +90,7 @@ def project_record(state, secrets):
|
||||
binaries.append({"pointer": path, "representation": "producer-bytes", "state": "redacted", "content_base64": None, "bytes": None, "sha256": None})
|
||||
return None
|
||||
if isinstance(value, bytes):
|
||||
safe_bytes, credential_url = transport_value(value, path, transport_url, base_scheme)
|
||||
safe_bytes, credential_url = transport_value(value, path, transport_url)
|
||||
content_base64 = base64.b64encode(value).decode("ascii")
|
||||
digest = hashlib.sha256(value).hexdigest()
|
||||
hidden = any(secret in value or secret in safe_bytes for secret in byte_secrets) or any(secret in content_base64 or secret in digest or secret in str(len(value)) for secret in secrets)
|
||||
@@ -277,7 +107,7 @@ def project_record(state, secrets):
|
||||
return None
|
||||
return safe_text
|
||||
if isinstance(value, str):
|
||||
safe_text, _ = transport_value(value, path, transport_url, base_scheme)
|
||||
safe_text, _ = transport_value(value, path, transport_url)
|
||||
return redact(safe_text, path, value)
|
||||
if isinstance(value, bool) or value is None:
|
||||
return value
|
||||
@@ -295,16 +125,10 @@ def project_record(state, secrets):
|
||||
for index, field in enumerate(value):
|
||||
if not isinstance(field, (list, tuple)) or len(field) != 2 or not isinstance(field[0], bytes):
|
||||
raise CaptureFailure("format", "Native header/trailer is not an ordered byte pair.", pointer(path, index))
|
||||
result.append([visit(field[0], pointer(pointer(path, index), 0), depth + 2), visit(field[1], pointer(pointer(path, index), 1), depth + 2, field[0].lower() in CREDENTIALS, field[0].lower() in {b"location", b"referer", b"origin"}, base_scheme if field[0].lower() == b"location" else None)])
|
||||
result.append([visit(field[0], pointer(pointer(path, index), 0), depth + 2), visit(field[1], pointer(pointer(path, index), 1), depth + 2, field[0].lower() in CREDENTIALS, field[0].lower() in {b"location", b"referer", b"origin"})])
|
||||
return result
|
||||
return [visit(item, pointer(path, index), depth + 1, base_scheme=base_scheme) for index, item in enumerate(value)]
|
||||
return [visit(item, pointer(path, index), depth + 1) for index, item in enumerate(value)]
|
||||
if isinstance(value, dict):
|
||||
if path == "" or re.fullmatch(r"/backup(?:/backup)*", path):
|
||||
request = value.get("request")
|
||||
scheme = request.get("scheme") if isinstance(request, dict) else None
|
||||
if isinstance(scheme, bytes):
|
||||
scheme = scheme.decode("latin-1")
|
||||
base_scheme = scheme.lower() if isinstance(scheme, str) and re.fullmatch(r"[a-z][a-z0-9+.-]*", scheme, flags=re.I) else None
|
||||
result = {}
|
||||
for key, item in value.items():
|
||||
if not isinstance(key, str):
|
||||
@@ -315,7 +139,7 @@ def project_record(state, secrets):
|
||||
validate_omitted(item, pointer(path, key), depth + 1)
|
||||
redactions.append({"pointer": path, "reason": "explicit-sensitive-value", "scope": "property-name"})
|
||||
continue
|
||||
result[key] = visit(item, pointer(path, key), depth + 1, base_scheme=base_scheme)
|
||||
result[key] = visit(item, pointer(path, key), depth + 1)
|
||||
return result
|
||||
raise CaptureFailure("decoder", "Native parser returned an unexpected value type: " + type(value).__name__ + ".", path)
|
||||
|
||||
|
||||
@@ -1,265 +0,0 @@
|
||||
"""Source-owned tests for the mitmproxy adapter's record projection."""
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
|
||||
def load_capture():
|
||||
"""Load the adapter with fresh mitmproxy stand-ins scoped to its import."""
|
||||
fake_mitmproxy = types.ModuleType("mitmproxy")
|
||||
fake_mitmproxy.ctx = types.SimpleNamespace()
|
||||
fake_mitmproxy.version = types.SimpleNamespace(VERSION="12.2.3")
|
||||
fake_io = types.ModuleType("mitmproxy.io")
|
||||
fake_io.tnetstring = types.ModuleType("mitmproxy.io.tnetstring")
|
||||
stand_ins = {
|
||||
"mitmproxy": fake_mitmproxy,
|
||||
"mitmproxy.io": fake_io,
|
||||
"mitmproxy.io.tnetstring": fake_io.tnetstring,
|
||||
}
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"rea_mitmproxy_capture", os.path.join(os.path.dirname(__file__), "capture.py")
|
||||
)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
with mock.patch.dict(sys.modules, stand_ins):
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
capture = load_capture()
|
||||
|
||||
|
||||
def location(value, scheme=None):
|
||||
state = {"response": {"headers": [[b"Location", value]]}}
|
||||
if scheme is not None:
|
||||
state["request"] = {"scheme": scheme}
|
||||
record = capture.project_record(state, [])
|
||||
sidecar = next(
|
||||
field for field in record["binary_fields"] if field["pointer"] == "/response/headers/0/1"
|
||||
)
|
||||
return record["reported"]["response"]["headers"][0][1], sidecar, record["redactions"]
|
||||
|
||||
|
||||
class TransportUserinfoTests(unittest.TestCase):
|
||||
def test_ignorable_controls_cannot_hide_authority_credentials(self):
|
||||
for control in [b"\t", b"\n", b"\r"]:
|
||||
for value in [b"https://" + control + b"/user:pass@host/path", b"ht" + control + b"tps://user:" + control + b"pass@host/path"]:
|
||||
with self.subTest(value=value):
|
||||
text, sidecar, redactions = location(value, b"https")
|
||||
self.assertNotIn("user", text)
|
||||
self.assertNotIn("pass", text)
|
||||
self.assertEqual(sidecar["state"], "redacted")
|
||||
self.assertEqual(len(redactions), 1)
|
||||
|
||||
def test_mixed_network_separators_remove_credentials(self):
|
||||
for prefix in [b"\\\\", b"/\\", b"\\/"]:
|
||||
with self.subTest(prefix=prefix):
|
||||
text, sidecar, redactions = location(prefix + b"user:pass@host/path", b"https")
|
||||
self.assertEqual(text, prefix.decode("ascii") + "host/path")
|
||||
self.assertEqual(sidecar["state"], "redacted")
|
||||
self.assertEqual(len(redactions), 1)
|
||||
|
||||
def test_non_special_base_keeps_backslashes_inside_network_authorities(self):
|
||||
text, sidecar, redactions = location(b"//user\\name:pass@host/x", b"foo")
|
||||
self.assertEqual(text, "//host/x")
|
||||
self.assertEqual(sidecar["state"], "redacted")
|
||||
self.assertEqual(len(redactions), 1)
|
||||
text, sidecar, redactions = location(b"/\\invoice@customer", b"foo")
|
||||
self.assertEqual(text, "/\\invoice@customer")
|
||||
self.assertEqual(sidecar["state"], "retained")
|
||||
self.assertEqual(redactions, [])
|
||||
|
||||
def test_native_request_paths_preserve_origin_form_and_redact_absolute_form(self):
|
||||
for value in [b"/\\invoice@customer", b"//invoice@customer", b"\\\\invoice@customer"]:
|
||||
with self.subTest(value=value):
|
||||
record = capture.project_record({"request": {"path": value}}, [])
|
||||
self.assertEqual(record["reported"]["request"]["path"], value.decode("ascii"))
|
||||
self.assertEqual(record["binary_fields"][0]["state"], "retained")
|
||||
self.assertEqual(record["redactions"], [])
|
||||
record = capture.project_record({"request": {"path": b"https://user:pass@host/x"}}, [])
|
||||
self.assertEqual(record["reported"]["request"]["path"], "https://host/x")
|
||||
self.assertEqual(record["binary_fields"][0]["state"], "redacted")
|
||||
|
||||
def test_same_scheme_locations_preserve_relative_path_text_and_bytes(self):
|
||||
for value in [b"https:invoice@customer", b"https:/invoice@customer", b"https:\\invoice@customer"]:
|
||||
with self.subTest(value=value):
|
||||
text, sidecar, redactions = location(value, b"https")
|
||||
self.assertEqual(text, value.decode("ascii"))
|
||||
self.assertEqual(sidecar["state"], "retained")
|
||||
self.assertEqual(redactions, [])
|
||||
|
||||
def test_location_authorities_still_remove_credentials_with_a_base_scheme(self):
|
||||
for value, scheme, expected in [
|
||||
(b"https://user:pass@host/path", b"https", "https://host/path"),
|
||||
(b"https:\\\\user:pass@host/path", b"https", "https:\\\\host/path"),
|
||||
(b"https:user:pass@host/path", b"http", "https:host/path"),
|
||||
]:
|
||||
with self.subTest(value=value, scheme=scheme):
|
||||
text, sidecar, redactions = location(value, scheme)
|
||||
self.assertEqual(text, expected)
|
||||
self.assertEqual(sidecar["state"], "redacted")
|
||||
self.assertEqual(len(redactions), 1)
|
||||
|
||||
def test_backup_location_uses_its_own_request_scheme(self):
|
||||
record = capture.project_record({
|
||||
"request": {"scheme": b"http"},
|
||||
"backup": {
|
||||
"request": {"scheme": b"https"},
|
||||
"response": {"headers": [[b"Location", b"https:invoice@customer"]]},
|
||||
},
|
||||
}, [])
|
||||
self.assertEqual(record["reported"]["backup"]["response"]["headers"][0][1], "https:invoice@customer")
|
||||
self.assertEqual(record["redactions"], [])
|
||||
|
||||
def test_backslash_ends_a_special_scheme_authority(self):
|
||||
text, sidecar, redactions = location(b"https://example.test\\part@tail")
|
||||
self.assertEqual(text, "https://example.test\\part@tail")
|
||||
self.assertEqual(sidecar["state"], "retained")
|
||||
self.assertIsNotNone(sidecar["content_base64"])
|
||||
self.assertEqual(redactions, [])
|
||||
|
||||
def test_userinfo_before_a_backslash_is_still_removed(self):
|
||||
text, sidecar, redactions = location(b"https://user:[email protected]\\path")
|
||||
self.assertEqual(text, "https://example.test\\path")
|
||||
self.assertEqual(sidecar["state"], "redacted")
|
||||
self.assertEqual(
|
||||
redactions,
|
||||
[{"pointer": "/response/headers/0/1", "reason": "transport-credential"}],
|
||||
)
|
||||
|
||||
def test_ordinary_userinfo_is_removed(self):
|
||||
text, _sidecar, _redactions = location(b"https://user:[email protected]/a@b")
|
||||
self.assertEqual(text, "https://example.test/a@b")
|
||||
|
||||
def test_empty_userinfo_marker_is_removed(self):
|
||||
self.assertEqual(
|
||||
capture.without_userinfo("https://@host"), "https://host"
|
||||
)
|
||||
|
||||
def test_special_schemes_skip_leading_slashes_before_userinfo(self):
|
||||
for value, expected in [
|
||||
(b"https://\\user:pass@host/path", b"https://\\host/path"),
|
||||
(b"https:\\\\user:pass@host/path", b"https:\\\\host/path"),
|
||||
(b"https:user:pass@host", b"https:host"),
|
||||
(b"//\\user:pass@host", b"//\\host"),
|
||||
]:
|
||||
with self.subTest(value=value):
|
||||
self.assertEqual(capture.without_userinfo(value), expected)
|
||||
text, sidecar, redactions = location(b"https://\\user:pass@host/path")
|
||||
self.assertEqual(text, "https://\\host/path")
|
||||
self.assertEqual(sidecar["state"], "redacted")
|
||||
self.assertEqual(len(redactions), 1)
|
||||
|
||||
def test_file_base_keeps_paths_after_the_fixed_network_boundary(self):
|
||||
for value in [b"///u@h", b"////u@h"]:
|
||||
with self.subTest(value=value):
|
||||
text, sidecar, redactions = location(value, b"file")
|
||||
self.assertEqual(text, value.decode("ascii"))
|
||||
self.assertEqual(sidecar["state"], "retained")
|
||||
self.assertIsNotNone(sidecar["content_base64"])
|
||||
self.assertEqual(redactions, [])
|
||||
|
||||
def test_file_urls_keep_path_text(self):
|
||||
self.assertEqual(capture.without_userinfo("file:///a@b"), "file:///a@b")
|
||||
|
||||
def test_file_urls_reject_userinfo_and_keep_their_source(self):
|
||||
# WHATWG rejects an "@" in a file host, so nothing parses as credentials.
|
||||
self.assertEqual(
|
||||
capture.without_userinfo("file://user:pass@host/x"), "file://user:pass@host/x"
|
||||
)
|
||||
for value in [b"\\\\user:pass@host/x", b"/\\user:pass@host/x", b"//user:pass@host/x"]:
|
||||
with self.subTest(value=value):
|
||||
text, sidecar, redactions = location(value, b"file")
|
||||
self.assertEqual(text, value.decode("ascii"))
|
||||
self.assertEqual(sidecar["state"], "retained")
|
||||
self.assertEqual(redactions, [])
|
||||
|
||||
def test_rejected_special_hosts_keep_their_source(self):
|
||||
# A rejected host or port fails the whole URL, so it has no userinfo.
|
||||
for value in [
|
||||
"https:user@%",
|
||||
"https://user@",
|
||||
"https://user:pass@h^st/x",
|
||||
"https://user@ho st/x",
|
||||
"https://user@host:99999/x",
|
||||
"https://user@host:12a",
|
||||
"https://user@[::1",
|
||||
]:
|
||||
with self.subTest(value=value):
|
||||
self.assertEqual(capture.without_userinfo(value), value)
|
||||
text, sidecar, redactions = location(b"https://user:pass@h^st/x", b"https")
|
||||
self.assertEqual(text, "https://user:pass@h^st/x")
|
||||
self.assertEqual(sidecar["state"], "retained")
|
||||
self.assertEqual(redactions, [])
|
||||
|
||||
def test_accepted_special_hosts_still_remove_userinfo(self):
|
||||
for value, expected in [
|
||||
("https:user@%41", "https:%41"),
|
||||
("https://user@[::1]:8443/x", "https://[::1]:8443/x"),
|
||||
("https://user@host:/x", "https://host:/x"),
|
||||
("wss://user:pass@host:65535", "wss://host:65535"),
|
||||
("https://[email protected]/", "https://0x7f.1/"),
|
||||
("https://[email protected]./", "https://1.2.3.4./"),
|
||||
("https://user@4294967295/", "https://4294967295/"),
|
||||
("https://[email protected]/", "https://1.2.3.08a/"),
|
||||
("https://user@[::ffff:1.2.3.4]/", "https://[::ffff:1.2.3.4]/"),
|
||||
]:
|
||||
with self.subTest(value=value):
|
||||
self.assertEqual(capture.without_userinfo(value), expected)
|
||||
|
||||
def test_unicode_hosts_that_may_map_to_accepted_hosts_remove_userinfo(self):
|
||||
# UTS #46 maps these to 1.2.3.4; mapping is not modeled, so redact.
|
||||
for host in ["%EF%BC%91.2.3.4", "1。2.3.4", "%C2%AD1.2.3.4", "1.2.3.4"]:
|
||||
with self.subTest(host=host):
|
||||
self.assertEqual(
|
||||
capture.without_userinfo(f"https://user:pw@{host}/"), f"https://{host}/"
|
||||
)
|
||||
text, sidecar, redactions = location(b"https://user:pw@%EF%BC%91.2.3.4/", b"https")
|
||||
self.assertEqual(text, "https://%EF%BC%91.2.3.4/")
|
||||
self.assertEqual(sidecar["state"], "redacted")
|
||||
self.assertEqual(len(redactions), 1)
|
||||
# ASCII rejections still hold for non-ASCII hosts.
|
||||
self.assertEqual(capture.without_userinfo("https://user@1^/"), "https://user@1^/")
|
||||
|
||||
def test_trailing_controls_and_spaces_do_not_hide_userinfo(self):
|
||||
# WHATWG strips trailing C0 controls and spaces before parsing.
|
||||
for suffix in [b" ", b"\x01", b"\x00 \x1f"]:
|
||||
with self.subTest(suffix=suffix):
|
||||
value = b"https://user:[email protected]" + suffix
|
||||
text, sidecar, redactions = location(value, b"https")
|
||||
self.assertEqual(text, "https://example.test" + suffix.decode("latin-1"))
|
||||
self.assertEqual(sidecar["state"], "redacted")
|
||||
self.assertEqual(len(redactions), 1)
|
||||
|
||||
def test_rejected_ip_literals_keep_their_source(self):
|
||||
for value in [
|
||||
"https://user:[email protected]/path",
|
||||
"https://[email protected]/",
|
||||
"https://[email protected]/",
|
||||
"https://user@4294967296/",
|
||||
"https://[email protected]/",
|
||||
"https://[email protected]/",
|
||||
"https://user@1." + "9" * 5000 + "/",
|
||||
"https://user@[1::2::3]/",
|
||||
"https://user@[::1%25eth0]/",
|
||||
]:
|
||||
with self.subTest(value=value[:40]):
|
||||
self.assertEqual(capture.without_userinfo(value), value)
|
||||
|
||||
def test_overlong_ports_are_compared_without_numeric_overflow(self):
|
||||
# Python 3.11+ refuses int() on decimal strings over 4300 digits.
|
||||
accepted = "https://user@host:" + "0" * 5000 + "80/x"
|
||||
self.assertEqual(capture.without_userinfo(accepted), "https://host:" + "0" * 5000 + "80/x")
|
||||
rejected = "https://user@host:" + "9" * 5000 + "/x"
|
||||
self.assertEqual(capture.without_userinfo(rejected), rejected)
|
||||
|
||||
def test_non_special_schemes_keep_backslashes_in_the_authority(self):
|
||||
self.assertEqual(capture.without_userinfo("foo://a\\b@host/x"), "foo://host/x")
|
||||
self.assertEqual(capture.without_userinfo(b"//a\\b@host"), b"//a\\b@host")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,26 +0,0 @@
|
||||
import Foundation
|
||||
import CoreFoundation
|
||||
|
||||
struct AccessibilityValue {
|
||||
let value: Any
|
||||
let gap: String?
|
||||
}
|
||||
|
||||
/** Preserve JSON scalars; report successful reads that JSON cannot represent faithfully. */
|
||||
func captureAccessibilityValue(_ source: Any?) -> AccessibilityValue {
|
||||
guard let source else { return AccessibilityValue(value: NSNull(), gap: nil) }
|
||||
if let text = source as? String { return AccessibilityValue(value: text, gap: nil) }
|
||||
if let number = source as? NSNumber {
|
||||
if CFGetTypeID(number) == CFBooleanGetTypeID() {
|
||||
return AccessibilityValue(value: number.boolValue, gap: nil)
|
||||
}
|
||||
let value = number.doubleValue
|
||||
let encoding = String(cString: number.objCType)
|
||||
let floating = encoding == "f" || encoding == "d"
|
||||
if number is NSDecimalNumber || !value.isFinite || (value == 0 && value.sign == .minus) || (!floating && abs(value) > 9_007_199_254_740_991) {
|
||||
return AccessibilityValue(value: NSNull(), gap: "AXValue number \(number.stringValue) cannot be represented faithfully as a JSON number")
|
||||
}
|
||||
return AccessibilityValue(value: number, gap: nil)
|
||||
}
|
||||
return AccessibilityValue(value: NSNull(), gap: "AXValue type \(type(of: source)) is not a supported JSON scalar")
|
||||
}
|
||||
@@ -43,13 +43,10 @@ func windowBounds(_ element: AXUIElement) -> CGRect? {
|
||||
return CGRect(origin: point, size: size)
|
||||
}
|
||||
func observe(_ request: Request) async throws -> [String: Any] {
|
||||
guard let app = NSRunningApplication(processIdentifier: request.pid) else {
|
||||
try fail("pid-not-application", "LaunchServices does not expose an application for the selected PID; this does not establish that the process is absent")
|
||||
guard let app = NSRunningApplication(processIdentifier: request.pid), let url = app.executableURL else {
|
||||
try fail("target-mismatch", "Selected PID does not expose an application executable URL")
|
||||
}
|
||||
guard let url = app.executableURL else {
|
||||
try fail("application-executable-unavailable", "The selected application does not expose an executable URL; select a PID with an identifiable application executable")
|
||||
}
|
||||
guard let canonicalPointer = realpath(url.path, nil) else { try fail("executable-path-unavailable", "Running executable path cannot be resolved") }
|
||||
guard let canonicalPointer = realpath(url.path, nil) else { try fail("target-mismatch", "Running executable path cannot be resolved") }
|
||||
defer { free(canonicalPointer) }
|
||||
let canonicalExecutable = String(cString: canonicalPointer)
|
||||
guard canonicalExecutable == request.executable else {
|
||||
@@ -65,10 +62,7 @@ func observe(_ request: Request) async throws -> [String: Any] {
|
||||
guard SHA256.hash(data: executableBytes).map({ String(format: "%02x", $0) }).joined() == request.sha256 else {
|
||||
try fail("target-mismatch", "Running executable bytes differ from the active target digest")
|
||||
}
|
||||
// .optionIncludingWindow omits a selected window while it is off screen, which
|
||||
// is not absence: select from every window, and let capture report what it can.
|
||||
guard let windows = (CGWindowListCopyWindowInfo([.optionAll], kCGNullWindowID) as? [[String: Any]])?
|
||||
.filter({ ($0[kCGWindowNumber as String] as? NSNumber)?.uint32Value == request.window_id }),
|
||||
guard let windows = CGWindowListCopyWindowInfo([.optionIncludingWindow], request.window_id) as? [[String: Any]],
|
||||
windows.count == 1, let window = windows.first,
|
||||
(window[kCGWindowOwnerPID as String] as? NSNumber)?.int32Value == request.pid,
|
||||
(window[kCGWindowLayer as String] as? NSNumber)?.intValue == 0,
|
||||
@@ -123,9 +117,7 @@ func observe(_ request: Request) async throws -> [String: Any] {
|
||||
let childCountResult = childCount(element)
|
||||
var actions: CFArray?
|
||||
AXUIElementCopyActionNames(element, &actions)
|
||||
let observedValue = captureAccessibilityValue(attribute(element, kAXValueAttribute))
|
||||
if let gap = observedValue.gap { gaps.append("\(gap) at path \(path)"); truncated = true }
|
||||
nodes.append(["path": path, "role": text(element, kAXRoleAttribute), "title": text(element, kAXTitleAttribute), "value": observedValue.value, "actions": actions as? [String] ?? [], "children_count": childCountResult.value.map { $0 as Any } ?? NSNull()])
|
||||
nodes.append(["path": path, "role": text(element, kAXRoleAttribute), "title": text(element, kAXTitleAttribute), "value": text(element, kAXValueAttribute), "actions": actions as? [String] ?? [], "children_count": childCountResult.value.map { $0 as Any } ?? NSNull()])
|
||||
guard let totalChildren = childCountResult.value else {
|
||||
truncated = true
|
||||
gaps.append("AX child count unavailable at path \(path): AXError \(childCountResult.error ?? -1)")
|
||||
|
||||
@@ -555,38 +555,6 @@ def _failed_launch_description(process):
|
||||
return None
|
||||
|
||||
|
||||
def _retire_failed_launch(process, config, listener, stdout, stderr):
|
||||
"""Reconcile an SBProcess returned alongside a failed Launch error."""
|
||||
if not process.IsValid():
|
||||
stdout.close()
|
||||
stderr.close()
|
||||
return False
|
||||
|
||||
pid = process.GetProcessID()
|
||||
if pid > 0:
|
||||
# Even an already-exited process needs its PID published so the host
|
||||
# can distinguish a completed launch failure from an unknown target.
|
||||
with open(config["pid_path"], "w") as handle:
|
||||
handle.write(str(pid))
|
||||
|
||||
if process.GetState() != lldb.eStateExited:
|
||||
stdout.mark_launched()
|
||||
stderr.mark_launched()
|
||||
if pid > 0:
|
||||
# Hold a surviving target until the host has captured its start
|
||||
# identity. If that proof is unavailable, Kill still addresses
|
||||
# this exact SBProcess created by the current launch request.
|
||||
_wait_for_identity_ack(config)
|
||||
process.Kill()
|
||||
_wait_for_exit(listener, process, 5)
|
||||
|
||||
exit_observed = process.GetState() == lldb.eStateExited
|
||||
expected_exit = exit_observed
|
||||
stdout.close(expected_exit=expected_exit)
|
||||
stderr.close(expected_exit=expected_exit)
|
||||
return exit_observed
|
||||
|
||||
|
||||
def trace(debugger, config):
|
||||
global _TRACE_CONTEXT
|
||||
debugger.SetAsync(True)
|
||||
@@ -599,27 +567,18 @@ def trace(debugger, config):
|
||||
before_digest = _hash_file(config["executable"])
|
||||
before_identity = _file_identity(config["executable"])
|
||||
except OSError as error:
|
||||
return {
|
||||
"status": "target-integrity-error",
|
||||
"error": str(error),
|
||||
"target_process_created": False,
|
||||
}
|
||||
return {"status": "target-integrity-error", "error": str(error)}
|
||||
if before_digest != config["expected_sha256"]:
|
||||
return {
|
||||
"status": "target-integrity-error",
|
||||
"error": "selected executable digest changed before LLDB target creation",
|
||||
"target_process_created": False,
|
||||
}
|
||||
error = lldb.SBError()
|
||||
target = debugger.CreateTarget(
|
||||
config["executable"], config["architecture"], None, False, error
|
||||
)
|
||||
if not target.IsValid():
|
||||
return {
|
||||
"status": "target-error",
|
||||
"error": error.GetCString(),
|
||||
"target_process_created": False,
|
||||
}
|
||||
return {"status": "target-error", "error": error.GetCString()}
|
||||
module_path_before, module_file_identity_before, module_uuid_before = (
|
||||
_module_identity(target)
|
||||
)
|
||||
@@ -630,7 +589,6 @@ def trace(debugger, config):
|
||||
return {
|
||||
"status": "target-integrity-error",
|
||||
"error": "LLDB executable module resolved to a different pre-launch file identity",
|
||||
"target_process_created": False,
|
||||
}
|
||||
created = [_create_breakpoints(target, spec) for spec in config["breakpoints"]]
|
||||
breakpoint_index = {}
|
||||
@@ -661,36 +619,15 @@ def trace(debugger, config):
|
||||
open(observation_path, "wb")
|
||||
)
|
||||
process = target.Launch(_launch_info(target, config, listener), error)
|
||||
process_created = process.IsValid()
|
||||
if not error.Success() or not process_created:
|
||||
exit_observed = _retire_failed_launch(
|
||||
process, config, listener, stdout, stderr
|
||||
)
|
||||
return {
|
||||
"status": "launch-error",
|
||||
"error": error.GetCString(),
|
||||
"target_process_created": process_created,
|
||||
**(
|
||||
{"target_process_exit_observed": True}
|
||||
if exit_observed
|
||||
else {}
|
||||
),
|
||||
}
|
||||
if not error.Success() or not process.IsValid():
|
||||
stdout.close()
|
||||
stderr.close()
|
||||
return {"status": "launch-error", "error": error.GetCString()}
|
||||
launch_failure = _failed_launch_description(process)
|
||||
if launch_failure is not None:
|
||||
exit_observed = _retire_failed_launch(
|
||||
process, config, listener, stdout, stderr
|
||||
)
|
||||
return {
|
||||
"status": "launch-error",
|
||||
"error": launch_failure,
|
||||
"target_process_created": True,
|
||||
**(
|
||||
{"target_process_exit_observed": True}
|
||||
if exit_observed
|
||||
else {}
|
||||
),
|
||||
}
|
||||
stdout.close(expected_exit=True)
|
||||
stderr.close(expected_exit=True)
|
||||
return {"status": "launch-error", "error": launch_failure}
|
||||
stdout.mark_launched()
|
||||
stderr.mark_launched()
|
||||
# Publish the PID before any post-launch file or module inspection. The
|
||||
@@ -707,7 +644,6 @@ def trace(debugger, config):
|
||||
return {
|
||||
"status": "target-identity-unavailable",
|
||||
"error": "host could not establish the launched process identity; target was stopped",
|
||||
"target_process_created": True,
|
||||
}
|
||||
try:
|
||||
after_digest = _hash_file(config["executable"])
|
||||
@@ -716,11 +652,7 @@ def trace(debugger, config):
|
||||
process.Kill()
|
||||
stdout.close(expected_exit=process.GetState() == lldb.eStateExited)
|
||||
stderr.close(expected_exit=process.GetState() == lldb.eStateExited)
|
||||
return {
|
||||
"status": "target-integrity-error",
|
||||
"error": str(identity_error),
|
||||
"target_process_created": True,
|
||||
}
|
||||
return {"status": "target-integrity-error", "error": str(identity_error)}
|
||||
if after_digest != config["expected_sha256"] or after_identity != before_identity:
|
||||
process.Kill()
|
||||
_wait_for_exit(listener, process, 5)
|
||||
@@ -729,7 +661,6 @@ def trace(debugger, config):
|
||||
return {
|
||||
"status": "target-integrity-error",
|
||||
"error": "selected executable digest or file identity changed during LLDB launch",
|
||||
"target_process_created": True,
|
||||
}
|
||||
module_path_after, module_file_identity_after, module_uuid_after = (
|
||||
_module_identity(target)
|
||||
@@ -745,7 +676,6 @@ def trace(debugger, config):
|
||||
return {
|
||||
"status": "target-integrity-error",
|
||||
"error": "LLDB executable module resolved to a different file identity",
|
||||
"target_process_created": True,
|
||||
}
|
||||
if (
|
||||
module_uuid_before is not None
|
||||
@@ -759,7 +689,6 @@ def trace(debugger, config):
|
||||
return {
|
||||
"status": "target-integrity-error",
|
||||
"error": "LLDB executable module identity changed during launch",
|
||||
"target_process_created": True,
|
||||
}
|
||||
context = {
|
||||
"target": target,
|
||||
@@ -819,11 +748,7 @@ def trace(debugger, config):
|
||||
# exit code; preserve the debugger's concrete launch denial.
|
||||
launch_failure = _failed_launch_description(process)
|
||||
if launch_failure is not None:
|
||||
return {
|
||||
"status": "launch-error",
|
||||
"error": launch_failure,
|
||||
"target_process_created": True,
|
||||
}
|
||||
return {"status": "launch-error", "error": launch_failure}
|
||||
launch_identity_stable = (
|
||||
module_path_before is not None
|
||||
and module_path_after == module_path_before
|
||||
|
||||
@@ -8,15 +8,12 @@ import sys
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
fake_lldb = sys.modules.setdefault("lldb", types.ModuleType("lldb"))
|
||||
fake_lldb.LLDB_INVALID_ADDRESS = -1
|
||||
fake_lldb.eLaunchFlagDebug = 2
|
||||
fake_lldb.eLaunchFlagStopAtEntry = 4
|
||||
fake_lldb.eStateExited = 10
|
||||
fake_lldb.SBEvent = lambda: object()
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"rea_lldb_tracer", os.path.join(os.path.dirname(__file__), "rea_lldb_tracer.py")
|
||||
)
|
||||
@@ -25,239 +22,6 @@ spec.loader.exec_module(tracer)
|
||||
|
||||
|
||||
class NativeTraceBudgetTests(unittest.TestCase):
|
||||
def test_invalid_launch_process_reports_explicit_no_creation(self):
|
||||
class InvalidProcess:
|
||||
def IsValid(self):
|
||||
return False
|
||||
|
||||
def Kill(self):
|
||||
raise AssertionError("an invalid process cannot own target cleanup")
|
||||
|
||||
class LaunchError:
|
||||
def Success(self):
|
||||
return False
|
||||
|
||||
def GetCString(self):
|
||||
return "No such file or directory: fixture/absent"
|
||||
|
||||
class Target:
|
||||
def IsValid(self):
|
||||
return True
|
||||
|
||||
def Launch(self, _launch_info, _error):
|
||||
return InvalidProcess()
|
||||
|
||||
class Debugger:
|
||||
def SetAsync(self, _enabled):
|
||||
pass
|
||||
|
||||
def GetInstanceName(self):
|
||||
return "fixture"
|
||||
|
||||
def CreateTarget(self, *_arguments):
|
||||
return Target()
|
||||
|
||||
class Pipe:
|
||||
def __init__(self, *_arguments):
|
||||
self.closed = False
|
||||
pipes.append(self)
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def close(self, expected_exit=False):
|
||||
self.closed = True
|
||||
|
||||
pipes = []
|
||||
debugger_api = types.SimpleNamespace(SetInternalVariable=lambda *_arguments: None)
|
||||
with patch.object(tracer.lldb, "SBDebugger", debugger_api, create=True), patch.object(
|
||||
tracer.lldb, "SBError", LaunchError, create=True
|
||||
), patch.object(tracer.lldb, "SBListener", lambda _name: object(), create=True), patch.object(
|
||||
tracer, "_hash_file", return_value="expected"
|
||||
), patch.object(tracer, "_file_identity", return_value=(1, 2)), patch.object(
|
||||
tracer, "_module_identity", return_value=("fixture", (1, 2), "fixture-uuid")
|
||||
), patch.object(tracer, "_launch_info", return_value=object()), patch.object(
|
||||
tracer, "_OutputPipe", Pipe
|
||||
):
|
||||
result = tracer.trace(Debugger(), {
|
||||
"executable": "fixture", "architecture": "arm64", "expected_sha256": "expected",
|
||||
"breakpoints": [], "working_directory": "fixture/absent",
|
||||
"stdout_path": "stdout", "stdout_capture_path": "stdout-capture",
|
||||
"stderr_path": "stderr", "stderr_capture_path": "stderr-capture",
|
||||
})
|
||||
self.assertEqual(result["status"], "launch-error")
|
||||
self.assertEqual(result["error"], "No such file or directory: fixture/absent")
|
||||
self.assertIs(result["target_process_created"], False)
|
||||
self.assertNotIn("target_process_exit_observed", result)
|
||||
self.assertEqual(len(pipes), 2)
|
||||
self.assertTrue(all(pipe.closed for pipe in pipes))
|
||||
|
||||
def test_failed_launch_does_not_infer_exit_from_a_failed_kill(self):
|
||||
class Process:
|
||||
def IsValid(self):
|
||||
return True
|
||||
|
||||
def GetProcessID(self):
|
||||
return 12345
|
||||
|
||||
def GetState(self):
|
||||
return 6
|
||||
|
||||
def Kill(self):
|
||||
return None
|
||||
|
||||
class Sink:
|
||||
close_exit = None
|
||||
|
||||
def mark_launched(self):
|
||||
pass
|
||||
|
||||
def close(self, expected_exit=False):
|
||||
self.close_exit = expected_exit
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
stdout, stderr = Sink(), Sink()
|
||||
with patch.object(tracer, "_wait_for_identity_ack", return_value="unavailable"), patch.object(
|
||||
tracer, "_wait_for_exit", return_value=None
|
||||
):
|
||||
exited = tracer._retire_failed_launch(
|
||||
Process(), {"pid_path": os.path.join(directory, "pid")}, object(), stdout, stderr
|
||||
)
|
||||
self.assertFalse(exited)
|
||||
self.assertFalse(stdout.close_exit)
|
||||
self.assertFalse(stderr.close_exit)
|
||||
|
||||
def test_failed_launch_publishes_pid_and_retires_live_process(self):
|
||||
class Process:
|
||||
state = 6
|
||||
killed = False
|
||||
|
||||
def IsValid(self):
|
||||
return True
|
||||
|
||||
def GetProcessID(self):
|
||||
return 12345
|
||||
|
||||
def GetState(self):
|
||||
return self.state
|
||||
|
||||
def Kill(self):
|
||||
self.killed = True
|
||||
self.state = tracer.lldb.eStateExited
|
||||
|
||||
class Sink:
|
||||
launched = False
|
||||
close_exit = None
|
||||
|
||||
def mark_launched(self):
|
||||
self.launched = True
|
||||
|
||||
def close(self, expected_exit=False):
|
||||
self.close_exit = expected_exit
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
pid_path = os.path.join(directory, "pid")
|
||||
process = Process()
|
||||
stdout, stderr = Sink(), Sink()
|
||||
|
||||
def acknowledge(config):
|
||||
with open(config["pid_path"], "r") as pid_file:
|
||||
self.assertEqual(pid_file.read(), "12345")
|
||||
return "readable"
|
||||
|
||||
with patch.object(tracer, "_wait_for_identity_ack", acknowledge):
|
||||
exited = tracer._retire_failed_launch(
|
||||
process,
|
||||
{"pid_path": pid_path},
|
||||
object(),
|
||||
stdout,
|
||||
stderr,
|
||||
)
|
||||
|
||||
self.assertTrue(exited)
|
||||
self.assertTrue(process.killed)
|
||||
self.assertTrue(stdout.launched)
|
||||
self.assertTrue(stderr.launched)
|
||||
self.assertTrue(stdout.close_exit)
|
||||
self.assertTrue(stderr.close_exit)
|
||||
|
||||
def test_failed_launch_records_when_valid_process_is_already_exited(self):
|
||||
class Process:
|
||||
def IsValid(self):
|
||||
return True
|
||||
|
||||
def GetProcessID(self):
|
||||
return 12345
|
||||
|
||||
def GetState(self):
|
||||
return tracer.lldb.eStateExited
|
||||
|
||||
def Kill(self):
|
||||
raise AssertionError("an exited process must not be killed")
|
||||
|
||||
class Sink:
|
||||
close_exit = None
|
||||
|
||||
def mark_launched(self):
|
||||
raise AssertionError("an exited process has no output to drain")
|
||||
|
||||
def close(self, expected_exit=False):
|
||||
self.close_exit = expected_exit
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
pid_path = os.path.join(directory, "pid")
|
||||
stdout, stderr = Sink(), Sink()
|
||||
with patch.object(
|
||||
tracer,
|
||||
"_wait_for_identity_ack",
|
||||
side_effect=AssertionError("exit is already observed"),
|
||||
):
|
||||
exited = tracer._retire_failed_launch(
|
||||
Process(), {"pid_path": pid_path}, object(), stdout, stderr
|
||||
)
|
||||
with open(pid_path, "r") as pid_file:
|
||||
self.assertEqual(pid_file.read(), "12345")
|
||||
|
||||
self.assertTrue(exited)
|
||||
self.assertTrue(stdout.close_exit)
|
||||
self.assertTrue(stderr.close_exit)
|
||||
|
||||
def test_pre_target_integrity_failure_reports_no_process_created(self):
|
||||
original_debugger = getattr(tracer.lldb, "SBDebugger", None)
|
||||
|
||||
class DebuggerApi:
|
||||
@staticmethod
|
||||
def SetInternalVariable(*_arguments):
|
||||
return None
|
||||
|
||||
class Debugger:
|
||||
def SetAsync(self, _enabled):
|
||||
return None
|
||||
|
||||
def GetInstanceName(self):
|
||||
return "fixture"
|
||||
|
||||
def CreateTarget(self, *_arguments):
|
||||
raise AssertionError("integrity failure precedes target creation")
|
||||
|
||||
tracer.lldb.SBDebugger = DebuggerApi
|
||||
try:
|
||||
with patch.object(tracer, "_hash_file", return_value="changed"), patch.object(
|
||||
tracer, "_file_identity", return_value=(1, 2)
|
||||
):
|
||||
result = tracer.trace(
|
||||
Debugger(),
|
||||
{"executable": "fixture", "expected_sha256": "expected"},
|
||||
)
|
||||
finally:
|
||||
if original_debugger is None:
|
||||
del tracer.lldb.SBDebugger
|
||||
else:
|
||||
tracer.lldb.SBDebugger = original_debugger
|
||||
|
||||
self.assertEqual(result["status"], "target-integrity-error")
|
||||
self.assertIs(result["target_process_created"], False)
|
||||
|
||||
def test_journal_checks_exact_byte_budget_before_writing(self):
|
||||
record = {"kind": "other-stop", "reason": "signal"}
|
||||
encoded_size = len((tracer.json.dumps(record) + "\n").encode("utf-8"))
|
||||
|
||||
@@ -12,32 +12,16 @@ struct ProcessIdentityObservation: Encodable {
|
||||
let pid: Int32
|
||||
let state: String
|
||||
let identity: String?
|
||||
let effective_uid: UInt32?
|
||||
let lifecycle: String?
|
||||
let relation: ProcessRelationObservation?
|
||||
/** Display-only kernel accounting name (p_comm, at most MAXCOMLEN bytes). */
|
||||
let name: String?
|
||||
let reason: String?
|
||||
}
|
||||
|
||||
struct ProcessRelationObservation: Encodable {
|
||||
let state: String
|
||||
let parent_pid: Int32
|
||||
let process_group_id: Int32
|
||||
}
|
||||
|
||||
struct ProcessMetadata {
|
||||
private struct ProcessMetadata {
|
||||
let identity: String
|
||||
let effectiveUid: UInt32
|
||||
let lifecycle: String
|
||||
let parentPid: Int32
|
||||
let processGroupId: Int32
|
||||
let pointerSize: Int
|
||||
let name: String
|
||||
}
|
||||
|
||||
struct ProcessSnapshot {
|
||||
let metadata: [Int32: ProcessMetadata]
|
||||
fileprivate let metadata: [Int32: ProcessMetadata]
|
||||
let failure: String?
|
||||
}
|
||||
|
||||
@@ -91,12 +75,7 @@ func readProcessSnapshot(
|
||||
process.kp_proc.p_pid,
|
||||
ProcessMetadata(
|
||||
identity: "\(start.tv_sec):\(start.tv_usec)",
|
||||
effectiveUid: process.kp_eproc.e_ucred.cr_uid,
|
||||
lifecycle: process.kp_proc.p_stat == SZOMB ? "zombie" : "non-zombie",
|
||||
parentPid: process.kp_eproc.e_ppid,
|
||||
processGroupId: process.kp_eproc.e_pgid,
|
||||
pointerSize: process.kp_proc.p_flag & P_LP64 != 0 ? 8 : 4,
|
||||
name: processName(process)
|
||||
pointerSize: process.kp_proc.p_flag & P_LP64 != 0 ? 8 : 4
|
||||
)
|
||||
)
|
||||
},
|
||||
@@ -107,88 +86,25 @@ func readProcessSnapshot(
|
||||
return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(ENOMEM)")
|
||||
}
|
||||
|
||||
/** Decode the NUL-terminated accounting name; invalid UTF-8 is replaced for display. */
|
||||
private func processName(_ process: kinfo_proc) -> String {
|
||||
withUnsafeBytes(of: process.kp_proc.p_comm) { bytes in
|
||||
String(decoding: bytes.prefix { $0 != 0 }, as: UTF8.self)
|
||||
}
|
||||
}
|
||||
|
||||
func readProcessIdentities(pids: [Int32]) -> [ProcessIdentityObservation] {
|
||||
readProcessIdentities(pids: pids, snapshot: readProcessSnapshot())
|
||||
}
|
||||
|
||||
func readProcessIdentities(
|
||||
pids: [Int32],
|
||||
snapshot: ProcessSnapshot
|
||||
) -> [ProcessIdentityObservation] {
|
||||
let snapshot = readProcessSnapshot()
|
||||
return pids.map { pid in
|
||||
guard pid > 0 else {
|
||||
return unavailableIdentity(pid: pid, reason: "invalid_process_id")
|
||||
}
|
||||
guard let metadata = snapshot.metadata[pid] else {
|
||||
if let failure = snapshot.failure {
|
||||
return unavailableIdentity(pid: pid, reason: failure)
|
||||
}
|
||||
// This records absence from this successful KERN_PROC_ALL snapshot only.
|
||||
// Unlike the Linux procfs path, it has no PID-specific kill(0)
|
||||
// corroboration; callers must not treat it as universal or future exit
|
||||
// proof.
|
||||
return ProcessIdentityObservation(
|
||||
return unavailableIdentity(
|
||||
pid: pid,
|
||||
state: "absent",
|
||||
identity: nil,
|
||||
effective_uid: nil,
|
||||
lifecycle: nil,
|
||||
relation: nil,
|
||||
name: nil,
|
||||
reason: "not-in-snapshot"
|
||||
reason: snapshot.failure ?? "process_not_in_snapshot"
|
||||
)
|
||||
}
|
||||
return ProcessIdentityObservation(
|
||||
pid: pid,
|
||||
state: "readable",
|
||||
identity: metadata.identity,
|
||||
effective_uid: metadata.effectiveUid,
|
||||
lifecycle: metadata.lifecycle,
|
||||
relation: ProcessRelationObservation(
|
||||
state: "observed",
|
||||
parent_pid: metadata.parentPid,
|
||||
process_group_id: metadata.processGroupId
|
||||
),
|
||||
name: metadata.name,
|
||||
reason: nil
|
||||
)
|
||||
return ProcessIdentityObservation(pid: pid, state: "readable", identity: metadata.identity, reason: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func unavailableIdentity(pid: Int32, reason: String) -> ProcessIdentityObservation {
|
||||
ProcessIdentityObservation(
|
||||
pid: pid,
|
||||
state: "unavailable",
|
||||
identity: nil,
|
||||
effective_uid: nil,
|
||||
lifecycle: nil,
|
||||
relation: nil,
|
||||
name: nil,
|
||||
reason: reason
|
||||
)
|
||||
}
|
||||
|
||||
enum ProcargsMalformedStage: String, CaseIterable {
|
||||
case header
|
||||
case argumentCount = "argument_count"
|
||||
case executablePath = "executable_path"
|
||||
case pointerSize = "pointer_size"
|
||||
case pathPadding = "path_padding"
|
||||
case arguments
|
||||
case terminator
|
||||
case trailingEntries = "trailing_entries"
|
||||
ProcessIdentityObservation(pid: pid, state: "unavailable", identity: nil, reason: reason)
|
||||
}
|
||||
|
||||
enum RunTokenReadError: Error {
|
||||
case invalidBuffer
|
||||
case invalidBufferAt(ProcargsMalformedStage)
|
||||
case duplicateToken
|
||||
case invalidTokenEncoding
|
||||
case environmentUnavailable
|
||||
@@ -228,22 +144,6 @@ func processRunTokenObservation(
|
||||
parse: Result<ParsedRunToken, RunTokenReadError>,
|
||||
codeSigningFlags: () -> UInt32?
|
||||
) -> RunTokenObservation {
|
||||
// Native inspection restrictions apply even when argv is incomplete or
|
||||
// overwritten, before parsing reaches the environment. Keep this fact at
|
||||
// the native boundary; cleanup still decides whether the process is related.
|
||||
func withheldEnvironment() -> RunTokenObservation? {
|
||||
guard let flags = codeSigningFlags() else { return nil }
|
||||
if (flags & csPlatformBinary) != 0 {
|
||||
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
|
||||
}
|
||||
if (flags & csRestrict) != 0 {
|
||||
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func unavailableEnvironment(_ reason: String) -> RunTokenObservation {
|
||||
withheldEnvironment() ?? unavailable(pid: pid, reason: reason)
|
||||
}
|
||||
switch parse {
|
||||
case .success(let parsed):
|
||||
if let runId = parsed.runId {
|
||||
@@ -254,25 +154,38 @@ func processRunTokenObservation(
|
||||
if parsed.environmentEntryCount > 0 {
|
||||
return RunTokenObservation(pid: pid, state: "readable", run_id: nil, reason: nil)
|
||||
}
|
||||
if let withheld = withheldEnvironment() { return withheld }
|
||||
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:
|
||||
return unavailableEnvironment("environment_unavailable")
|
||||
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:
|
||||
return unavailableEnvironment("ambiguous_environment_boundary")
|
||||
// These failures already establish that token bytes were available.
|
||||
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 unavailableEnvironment("apple_vector_unavailable")
|
||||
return unavailable(pid: pid, reason: "apple_vector_unavailable")
|
||||
case .invalidBuffer:
|
||||
return unavailableEnvironment("malformed_procargs")
|
||||
case .invalidBufferAt(let stage):
|
||||
return unavailableEnvironment("malformed_procargs_\(stage.rawValue)")
|
||||
return unavailable(pid: pid, reason: "malformed_procargs")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,30 +244,27 @@ func parseProcessEnvironment(
|
||||
in bytes: [UInt8],
|
||||
pointerSize: Int = MemoryLayout<UnsafeRawPointer>.size
|
||||
) throws -> ParsedRunToken {
|
||||
guard bytes.count >= MemoryLayout<Int32>.size else { throw RunTokenReadError.invalidBufferAt(.header) }
|
||||
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.invalidBufferAt(.argumentCount) }
|
||||
guard argc >= 0, Int(argc) <= bytes.count else { throw RunTokenReadError.invalidBuffer }
|
||||
var offset = MemoryLayout<Int32>.size
|
||||
guard let executablePath = readString(in: bytes, offset: &offset) else {
|
||||
throw RunTokenReadError.invalidBufferAt(.executablePath)
|
||||
throw RunTokenReadError.invalidBuffer
|
||||
}
|
||||
guard pointerSize == 4 || pointerSize == 8 else { throw RunTokenReadError.invalidBufferAt(.pointerSize) }
|
||||
guard pointerSize == 4 || pointerSize == 8 else { throw RunTokenReadError.invalidBuffer }
|
||||
let pathRecordSize = Array("executable_path=".utf8).count + executablePath.count + 1
|
||||
let padding = (pointerSize - pathRecordSize % pointerSize) % pointerSize
|
||||
guard offset + padding <= bytes.count,
|
||||
bytes[offset..<(offset + padding)].allSatisfy({ $0 == 0 }) else {
|
||||
throw RunTokenReadError.invalidBufferAt(.pathPadding)
|
||||
throw RunTokenReadError.invalidBuffer
|
||||
}
|
||||
offset += padding
|
||||
let tokenPrefix = Array("REA_PROCESS_RUN_ID=".utf8)
|
||||
var tokenInArgument = false
|
||||
for _ in 0..<argc {
|
||||
guard let argument = readString(in: bytes, offset: &offset, allowEmpty: true) else {
|
||||
throw RunTokenReadError.invalidBufferAt(.arguments)
|
||||
guard readString(in: bytes, offset: &offset, allowEmpty: true) != nil else {
|
||||
throw RunTokenReadError.invalidBuffer
|
||||
}
|
||||
tokenInArgument = tokenInArgument || argument.starts(with: tokenPrefix)
|
||||
}
|
||||
guard bytes.last == 0 else { throw RunTokenReadError.invalidBufferAt(.terminator) }
|
||||
guard bytes.last == 0 else { throw RunTokenReadError.invalidBuffer }
|
||||
if offset == bytes.count {
|
||||
throw RunTokenReadError.environmentUnavailable
|
||||
}
|
||||
@@ -372,7 +282,7 @@ func parseProcessEnvironment(
|
||||
continue
|
||||
}
|
||||
guard let entry = readString(in: bytes, offset: &offset) else {
|
||||
throw RunTokenReadError.invalidBufferAt(.trailingEntries)
|
||||
throw RunTokenReadError.invalidBuffer
|
||||
}
|
||||
trailingEntries.append(entry)
|
||||
}
|
||||
@@ -393,28 +303,22 @@ func parseProcessEnvironment(
|
||||
throw RunTokenReadError.appleVectorUnavailable
|
||||
}
|
||||
if trailingEntries[appleVectorStart...].contains(where: {
|
||||
$0.starts(with: tokenPrefix)
|
||||
$0.starts(with: Array("REA_PROCESS_RUN_ID=".utf8))
|
||||
}) {
|
||||
throw RunTokenReadError.ambiguousEnvironmentBoundary
|
||||
}
|
||||
let environmentEntries = trailingEntries[..<appleVectorStart]
|
||||
var found: String?
|
||||
for entry in environmentEntries {
|
||||
if entry.starts(with: tokenPrefix) {
|
||||
if entry.starts(with: Array("REA_PROCESS_RUN_ID=".utf8)) {
|
||||
guard found == nil else { throw RunTokenReadError.duplicateToken }
|
||||
let value = entry.dropFirst(tokenPrefix.count)
|
||||
let value = entry.dropFirst("REA_PROCESS_RUN_ID=".utf8.count)
|
||||
guard let decoded = String(bytes: value, encoding: .utf8) else {
|
||||
throw RunTokenReadError.invalidTokenEncoding
|
||||
}
|
||||
found = decoded
|
||||
}
|
||||
}
|
||||
// A changed process title can overwrite the original argument boundaries,
|
||||
// causing a real environment entry to be consumed as an argument. A real
|
||||
// argument can have this form too, so it proves neither ownership nor absence.
|
||||
if tokenInArgument {
|
||||
throw RunTokenReadError.ambiguousEnvironmentBoundary
|
||||
}
|
||||
return ParsedRunToken(runId: found, environmentEntryCount: environmentEntries.count)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,8 @@ def inspect(request):
|
||||
root = Path(os.environ["REA_CORE_CONTEXT_REQUEST"]).parent
|
||||
empty = root / "no-libraries"
|
||||
empty.mkdir(mode=0o700)
|
||||
for command in ["set auto-load off", "set debuginfod enabled off", "set auto-solib-add off", "set history save off", "set pagination off", "set target-file-system-kind unix",
|
||||
"set sysroot ./no-libraries", "set solib-search-path ./no-libraries",
|
||||
"set debug-file-directory ./no-libraries"]:
|
||||
for command in ["set auto-load off", "set debuginfod enabled off", "set auto-solib-add off", "set history save off", "set pagination off",
|
||||
"set sysroot ./no-libraries", "set solib-search-path ./no-libraries"]:
|
||||
gdb.execute(command, to_string=True)
|
||||
entry = request["gdbinit"]
|
||||
try:
|
||||
@@ -39,19 +38,7 @@ def inspect(request):
|
||||
# relative filename in the owned cwd so no selected path becomes GDB syntax.
|
||||
if Path(request["snapshot_path"]) != root.parent / "object.snapshot":
|
||||
raise ContextFailure("decoder", "Core context request did not select the shared owned snapshot.")
|
||||
# GDB 16 searches AT_EXECFN/NT_FILE outside sysroot when no executable BFD
|
||||
# is selected. Occupy that slot with the already running, owned debugger;
|
||||
# exec-file loads no symbols and never runs it. Clear it before any query.
|
||||
# /proc/self/exe identifies this process, not the core's historical PID.
|
||||
architecture = {"x86_64-little-endian": "i386:x86-64", "aarch64-little-endian": "aarch64"}.get(request["architecture"])
|
||||
if architecture is None:
|
||||
raise ContextFailure("unsupported", "Unsupported recorded core architecture.")
|
||||
gdb.execute("exec-file /proc/self/exe", to_string=True)
|
||||
# The placeholder must not select the core's architecture. Reuse the
|
||||
# decoder's validated profile, including for cross-architecture GDB builds.
|
||||
gdb.execute("set architecture " + architecture, to_string=True)
|
||||
gdb.execute("core-file ../object.snapshot", to_string=True)
|
||||
gdb.execute("file", to_string=True)
|
||||
inferior = gdb.selected_inferior()
|
||||
if inferior.connection is None or inferior.connection.type != "core" or gdb.current_progspace().filename is not None:
|
||||
raise ContextFailure("decoder", "Requested core-only boundary was not established; no mapping query performed.")
|
||||
@@ -95,7 +82,7 @@ def main():
|
||||
try:
|
||||
encoded = json.dumps(reply, ensure_ascii=True, allow_nan=False).encode("utf-8")
|
||||
if len(encoded) > OUTPUT_BYTES:
|
||||
encoded = json.dumps({"ok": False, "reason": "output-limit", "message": "Complete debugger context exceeds the 64 MiB reply budget."}).encode("utf-8")
|
||||
encoded = json.dumps({"ok": False, "reason": "output-limit", "message": "Complete debugger context exceeds64 MiB reply budget."}).encode("utf-8")
|
||||
descriptor = os.open(request["reply_path"], os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
||||
with os.fdopen(descriptor, "wb") as handle:
|
||||
handle.write(encoded)
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
"""Producer regressions with controlled GDB state, not native debugger qualification."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import runpy
|
||||
import sys
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
BRIDGE = Path(__file__).with_name("core_context.py")
|
||||
|
||||
|
||||
class CoreContextTests(unittest.TestCase):
|
||||
def inspect(self, failure=None, connection="core", symbols=None, architecture="x86_64-little-endian", objfile="recorded-executable"):
|
||||
commands, reads, queries = [], [], []
|
||||
state = {"executable": None, "symbols": None, "connection": None}
|
||||
gdb = types.ModuleType("gdb")
|
||||
gdb.VERSION = "16.3"
|
||||
gdb.selected_inferior = lambda: types.SimpleNamespace(
|
||||
connection=state["connection"], pid=123)
|
||||
gdb.current_progspace = lambda: types.SimpleNamespace(filename=state["symbols"])
|
||||
|
||||
def execute(command, to_string):
|
||||
self.assertTrue(to_string)
|
||||
commands.append(command)
|
||||
if command == failure:
|
||||
raise RuntimeError("controlled command failure")
|
||||
if command == "exec-file /proc/self/exe":
|
||||
state["executable"] = "/proc/self/exe"
|
||||
elif command == "core-file ../object.snapshot":
|
||||
if state["executable"] is None:
|
||||
reads.append("recorded-executable")
|
||||
state["executable"] = state["symbols"] = "recorded-executable"
|
||||
state["connection"] = types.SimpleNamespace(type=connection)
|
||||
elif command == "file":
|
||||
state["executable"] = None
|
||||
state["symbols"] = symbols
|
||||
|
||||
gdb.execute = execute
|
||||
pwndbg = types.ModuleType("pwndbg")
|
||||
pwndbg.aglib = types.ModuleType("pwndbg.aglib")
|
||||
pwndbg.aglib.vmmap = types.ModuleType("pwndbg.aglib.vmmap")
|
||||
pwndbg.lib = types.ModuleType("pwndbg.lib")
|
||||
pwndbg.lib.version = types.ModuleType("pwndbg.lib.version")
|
||||
pwndbg.lib.version.__version__ = "2026.09.15"
|
||||
|
||||
def maps():
|
||||
self.assertIsNone(state["executable"], "Helper executable must be discarded before mapping queries")
|
||||
queries.append("maps")
|
||||
return [types.SimpleNamespace(start=4096, end=8192, offset=0, flags=5, objfile=objfile)]
|
||||
|
||||
pwndbg.aglib.vmmap.get = maps
|
||||
modules = {module.__name__: module for module in [
|
||||
gdb, pwndbg, pwndbg.aglib, pwndbg.aglib.vmmap, pwndbg.lib, pwndbg.lib.version]}
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
stage = root / "debugger"
|
||||
stage.mkdir()
|
||||
plugin = root / "gdbinit.py"
|
||||
plugin.write_text("")
|
||||
request = stage / "request.json"
|
||||
reply = stage / "reply.json"
|
||||
request.write_text(json.dumps({"gdbinit": str(plugin), "architecture": architecture, "snapshot_path": str(root / "object.snapshot"), "reply_path": str(reply)}))
|
||||
with patch.dict(sys.modules, modules), patch.dict(os.environ, REA_CORE_CONTEXT_REQUEST=str(request)):
|
||||
runpy.run_path(str(BRIDGE))
|
||||
result = json.loads(reply.read_text())
|
||||
return result, commands, reads, queries
|
||||
|
||||
def test_core_load_never_opens_recorded_executable_and_queries_only_after_discard(self):
|
||||
result, commands, reads, queries = self.inspect()
|
||||
self.assertTrue(result["ok"], result)
|
||||
self.assertEqual(reads, [])
|
||||
self.assertEqual(queries, ["maps"])
|
||||
self.assertEqual(commands[-4:], ["exec-file /proc/self/exe", "set architecture i386:x86-64", "core-file ../object.snapshot", "file"])
|
||||
self.assertLess(commands.index("set debug-file-directory ./no-libraries"), commands.index("core-file ../object.snapshot"))
|
||||
self.assertIsNone(result["value"]["executable"])
|
||||
self.assertEqual(result["value"]["maps"][0]["current_file_identity"], "unknown")
|
||||
|
||||
def test_failed_guard_core_load_or_discard_never_queries_mappings(self):
|
||||
for command in ["exec-file /proc/self/exe", "set architecture i386:x86-64", "core-file ../object.snapshot", "file"]:
|
||||
with self.subTest(command=command):
|
||||
result, commands, reads, queries = self.inspect(failure=command)
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual(reads, [])
|
||||
self.assertEqual(queries, [])
|
||||
if command in ["exec-file /proc/self/exe", "set architecture i386:x86-64"]:
|
||||
self.assertNotIn("core-file ../object.snapshot", commands)
|
||||
|
||||
def test_cross_architecture_uses_the_validated_decoder_profile(self):
|
||||
result, commands, reads, queries = self.inspect(architecture="aarch64-little-endian")
|
||||
self.assertTrue(result["ok"], result)
|
||||
self.assertEqual(reads, [])
|
||||
self.assertEqual(queries, ["maps"])
|
||||
self.assertLess(commands.index("set architecture aarch64"), commands.index("core-file ../object.snapshot"))
|
||||
|
||||
def test_unknown_architecture_refuses_before_loading_core(self):
|
||||
result, commands, reads, queries = self.inspect(architecture="unknown")
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual(result["reason"], "unsupported")
|
||||
self.assertNotIn("core-file ../object.snapshot", commands)
|
||||
self.assertEqual(reads, [])
|
||||
self.assertEqual(queries, [])
|
||||
|
||||
def test_output_limit_message_separates_the_words_and_names_the_limit(self):
|
||||
result, _, reads, queries = self.inspect(objfile="m" * (64 * 1024 * 1024))
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual(result["reason"], "output-limit")
|
||||
self.assertNotIn("exceeds64", result["message"])
|
||||
self.assertEqual(result["message"], "Complete debugger context exceeds the 64 MiB reply budget.")
|
||||
self.assertEqual(reads, [])
|
||||
self.assertEqual(queries, ["maps"])
|
||||
|
||||
def test_wrong_connection_or_remaining_symbols_never_queries_mappings(self):
|
||||
for options in [{"connection": "native"}, {"symbols": "unexpected-symbols"}]:
|
||||
with self.subTest(options=options):
|
||||
result, _, reads, queries = self.inspect(**options)
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual(reads, [])
|
||||
self.assertEqual(queries, [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -15,11 +15,6 @@ import sys
|
||||
PROFILE = "[email protected];[email protected];[email protected]"
|
||||
PACKAGES = {"pwntools": "4.15.0", "pyelftools": "0.33", "unicorn": "2.1.2"}
|
||||
OUTPUT_BYTES = 64 * 1024 * 1024
|
||||
# (e_machine, ELF class bits, byte order) -> architecture pwntools must also report.
|
||||
PROFILES = {(62, 64, "little"): "amd64", (183, 64, "little"): "aarch64", (3, 32, "little"): "i386",
|
||||
(40, 32, "little"): "arm", (8, 32, "little"): "mips", (8, 32, "big"): "mips"}
|
||||
PROFILE_SCOPE = ("Layout profile requires ELF64 x86-64, ELF64 AArch64 little-endian, ELF32 i386, "
|
||||
"ELF32 ARM little-endian or ELF32 MIPS little- or big-endian objects.")
|
||||
|
||||
|
||||
class LayoutFailure(Exception):
|
||||
@@ -121,16 +116,13 @@ def inspect_elf(path, cache):
|
||||
raise LayoutFailure("format", "Selected object does not contain an ELF header.")
|
||||
if len(content) < 16:
|
||||
raise LayoutFailure("format", "Selected ELF identification is truncated.")
|
||||
if content[4] not in (1, 2) or content[5] not in (1, 2):
|
||||
raise LayoutFailure("unsupported-target", "Selected ELF declares an unknown class or byte order.")
|
||||
bits, header_size = (32, 52) if content[4] == 1 else (64, 64)
|
||||
order = "little" if content[5] == 1 else "big"
|
||||
if len(content) < header_size:
|
||||
raise LayoutFailure("format", f"Selected ELF{bits} header is truncated.")
|
||||
profile = (int.from_bytes(content[18:20], order), bits, order)
|
||||
if profile not in PROFILES:
|
||||
raise LayoutFailure("unsupported-target", PROFILE_SCOPE)
|
||||
if int.from_bytes(content[16:18], order) not in (1, 2, 3):
|
||||
if content[4] != 2 or content[5] != 1:
|
||||
raise LayoutFailure("unsupported-target", "Initial layout profile requires ELF64 little-endian objects.")
|
||||
if len(content) < 64:
|
||||
raise LayoutFailure("format", "Selected ELF64 header is truncated.")
|
||||
if int.from_bytes(content[18:20], "little") != 62:
|
||||
raise LayoutFailure("unsupported-target", "Initial layout profile requires x86-64 ELF objects.")
|
||||
if int.from_bytes(content[16:18], "little") not in (1, 2, 3):
|
||||
raise LayoutFailure("unsupported-target", "Selected ELF type is outside the EXEC/DYN/REL layout profile; recorded core analysis is separate.")
|
||||
from pwnlib.context import context
|
||||
with context.local(cache_dir=str(cache), log_level="warning"):
|
||||
@@ -140,11 +132,6 @@ def inspect_elf(path, cache):
|
||||
try:
|
||||
headers = ELFFile(io.BytesIO(content))
|
||||
section_count = headers.num_sections()
|
||||
if section_count:
|
||||
location(headers.header.e_shoff, section_count * headers.header.e_shentsize, len(content))
|
||||
segment_count = headers.num_segments()
|
||||
if segment_count:
|
||||
location(headers.header.e_phoff, segment_count * headers.header.e_phentsize, len(content))
|
||||
if headers.header.e_shstrndx == 0xffff and section_count == 0:
|
||||
raise LayoutFailure("format", "ELF extended section-name index requires a declared section-zero header.")
|
||||
raw_name_index = headers.header.e_shstrndx
|
||||
@@ -160,21 +147,9 @@ def inspect_elf(path, cache):
|
||||
if names_header.sh_type != "SHT_STRTAB":
|
||||
raise LayoutFailure("format", f"ELF section-name table {section_name_index} is not SHT_STRTAB.")
|
||||
location(names_header.sh_offset, names_header.sh_size, len(content))
|
||||
except (ELFError, ValueError, OverflowError, AssertionError, IndexError) as error:
|
||||
except ELFError as error:
|
||||
raise LayoutFailure("format", str(error)) from error
|
||||
class LayoutELF(ELF):
|
||||
def search(self, needle, writable=False, executable=False):
|
||||
# This adapter exposes structural facts, GOT/PLT and mitigation
|
||||
# indicators, not kernel config/version discovery. Upstream's
|
||||
# constructor searches virtual memory for those unrelated
|
||||
# heuristics, materializing NOBITS tails and interpreting
|
||||
# ordinary IKCFG_ST data as a compressed kernel config.
|
||||
return iter(())
|
||||
|
||||
def _populate_kernel_version(self):
|
||||
# Kernel banner decoding is outside this report too.
|
||||
return None
|
||||
|
||||
def _get_section_name(self, header):
|
||||
# SHN_UNDEF declares no names; upstream would otherwise read
|
||||
# strings at the inactive section-zero offset.
|
||||
@@ -193,8 +168,8 @@ def inspect_elf(path, cache):
|
||||
if type is None or section.header.sh_type == type)
|
||||
try:
|
||||
image = LayoutELF(str(path), checksec=False)
|
||||
if (image.arch, image.bits, image.endian) != (PROFILES[profile], bits, order):
|
||||
raise LayoutFailure("unsupported-target", PROFILE_SCOPE)
|
||||
if image.arch != "amd64" or image.bits != 64 or image.endian != "little":
|
||||
raise LayoutFailure("unsupported-target", "Initial layout profile requires x86-64 ELF64 little-endian objects.")
|
||||
if image.header.e_type not in ("ET_EXEC", "ET_DYN", "ET_REL"):
|
||||
raise LayoutFailure("unsupported-target", "Selected ELF type is outside the EXEC/DYN/REL layout profile; recorded core analysis is separate.")
|
||||
length = len(content)
|
||||
@@ -216,7 +191,7 @@ def inspect_elf(path, cache):
|
||||
})
|
||||
if h.sh_type in ("SHT_SYMTAB", "SHT_DYNSYM"):
|
||||
if h.sh_entsize < image.structs.Elf_Sym.sizeof():
|
||||
raise LayoutFailure("format", f"ELF symbol entry is smaller than the decoded Elf{bits}_Sym structure.")
|
||||
raise LayoutFailure("format", "ELF symbol entry is smaller than the decoded Elf64_Sym structure.")
|
||||
if h.sh_link >= len(all_sections) or all_sections[h.sh_link].header.sh_type != "SHT_STRTAB":
|
||||
raise LayoutFailure("format", f"ELF symbol table {index} links to invalid string table {h.sh_link}.")
|
||||
strings = all_sections[h.sh_link]
|
||||
@@ -319,7 +294,6 @@ def inspect_elf(path, cache):
|
||||
"Relocatable relocation targets with SHN_UNDEF or inactive SHT_NULL headers retain their reported section index/offset as unknown; they do not establish an associated target section.",
|
||||
"GOT/PLT are derived upstream convenience maps; PLT inference can emulate selected instructions in Unicorn. They do not establish target runtime behavior; aliases may collapse and warnings may indicate incomplete coverage. Their completeness is unknown.",
|
||||
"Mitigations are upstream static heuristics, not runtime protection. ET_DYN does not prove an executable; absent canary symbols do not prove every function unprotected.",
|
||||
"Kernel configuration/version discovery is not performed; these memory-content scans do not supply the reported structural, linkage or mitigation facts.",
|
||||
"This profile inspects ELF EXEC/DYN/REL layout only. It does not analyze recorded cores, resolve loaded libraries, launch the target as a host process or start a debugger.",
|
||||
"Symbol/relocation inventories reflect original section tables. A sectionless image can still report dynamic dependency names; missing tables do not prove absence of dynamic symbols or relocations.",
|
||||
*(["Absent section-name table: section-dependent RELRO/canary heuristics may be incomplete; protection coverage is unknown."] if all_sections and section_name_index == 0 else []),
|
||||
@@ -328,7 +302,7 @@ def inspect_elf(path, cache):
|
||||
}
|
||||
except LayoutFailure:
|
||||
raise
|
||||
except (ELFError, ValueError, OverflowError, AssertionError, IndexError) as error:
|
||||
except (ELFError, ValueError, AssertionError, IndexError) as error:
|
||||
raise LayoutFailure("format", "Selected ELF failed unchanged upstream structural parsing: " + str(error)) from error
|
||||
|
||||
|
||||
|
||||
@@ -11,12 +11,6 @@ import sys
|
||||
|
||||
PROFILE = "[email protected];[email protected];[email protected]"
|
||||
|
||||
# Linux ET_CORE machine identifiers whose PRSTATUS layout is interpreted with
|
||||
# unchanged pwntools 4.15.0 ctypes. Both share pr_cursig@12, pr_pid@32 and
|
||||
# pr_reg@112; only the register window differs: amd64 is 27 x 8 bytes for a
|
||||
# 336-byte descriptor, AArch64 is 34 x 8 bytes for a 392-byte descriptor.
|
||||
CORE_MACHINES = (62, 183)
|
||||
|
||||
|
||||
class CoreFailure(Exception):
|
||||
def __init__(self, reason, message):
|
||||
@@ -57,27 +51,12 @@ def inspect_core(path, cache):
|
||||
# Other declared OSABIs must never select Linux note ctypes.
|
||||
if content[7] not in (0, 3) or content[8] != 0:
|
||||
raise CoreFailure("unsupported-target", "Initial recorded-core profile requires System V or GNU/Linux OSABI with ABI version zero; selected OSABI=" + str(content[7]) + ", ABI version=" + str(content[8]) + ".")
|
||||
machine = int.from_bytes(content[18:20], "little")
|
||||
if int.from_bytes(content[16:18], "little") != 4 or machine not in CORE_MACHINES:
|
||||
raise CoreFailure("unsupported-target", "Initial recorded-core profile requires ET_CORE for x86-64 or AArch64; selected ET_TYPE=" + str(int.from_bytes(content[16:18], "little")) + ", e_machine=" + str(machine) + ".")
|
||||
if int.from_bytes(content[16:18], "little") != 4 or int.from_bytes(content[18:20], "little") != 62:
|
||||
raise CoreFailure("unsupported-target", "Initial recorded-core profile requires x86-64 ET_CORE.")
|
||||
from elftools.common.exceptions import ELFError
|
||||
from elftools.common.utils import struct_parse
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from pwnlib.elf.datatypes import (
|
||||
elf_prstatus_aarch64,
|
||||
elf_prstatus_amd64,
|
||||
elf_siginfo_64,
|
||||
user_regs_struct_aarch64,
|
||||
user_regs_struct_amd64,
|
||||
)
|
||||
if machine == 62:
|
||||
profile_label = "x86-64"
|
||||
architecture = "x86_64-little-endian"
|
||||
prstatus_type, registers_type = elf_prstatus_amd64, user_regs_struct_amd64
|
||||
else:
|
||||
profile_label = "AArch64"
|
||||
architecture = "aarch64-little-endian"
|
||||
prstatus_type, registers_type = elf_prstatus_aarch64, user_regs_struct_aarch64
|
||||
from pwnlib.elf.datatypes import elf_prstatus_amd64, elf_siginfo_64, user_regs_struct_amd64
|
||||
try:
|
||||
image = ELFFile(io.BytesIO(content))
|
||||
header = image.header
|
||||
@@ -88,18 +67,16 @@ def inspect_core(path, cache):
|
||||
segments, notes, padding, threads, signals = [], [], [], [], []
|
||||
for segment_index, segment in enumerate(image.iter_segments()):
|
||||
h = segment.header
|
||||
header_offset = header.e_phoff + segment_index * header.e_phentsize
|
||||
type_id = int.from_bytes(content[header_offset:header_offset + 4], "little")
|
||||
backing = type_id != 0 and h.p_filesz > 0
|
||||
backing = h.p_type != "PT_NULL" and h.p_filesz > 0
|
||||
if backing:
|
||||
location(h.p_offset, h.p_filesz, len(content))
|
||||
if type_id == 1 and h.p_filesz > h.p_memsz:
|
||||
if h.p_type == "PT_LOAD" and h.p_filesz > h.p_memsz:
|
||||
raise CoreFailure("format", "Recorded PT_LOAD file bytes exceed memory size.")
|
||||
segments.append({"index": segment_index, "type": h.p_type, "type_id": type_id,
|
||||
"header_location": location(header_offset, header.e_phentsize, len(content)),
|
||||
segments.append({"index": segment_index, "type": h.p_type,
|
||||
"header_location": location(header.e_phoff + segment_index * header.e_phentsize, header.e_phentsize, len(content)),
|
||||
"offset": hex(h.p_offset), "file_size": hex(h.p_filesz), "memory_size": hex(h.p_memsz),
|
||||
"virtual_address": hex(h.p_vaddr), "physical_address": hex(h.p_paddr), "alignment": hex(h.p_align), "flags": hex(h.p_flags), "file_backing": "file" if backing else "none"})
|
||||
if type_id != 4 or h.p_filesz == 0:
|
||||
if h.p_type != "PT_NOTE" or h.p_filesz == 0:
|
||||
continue
|
||||
position, end = h.p_offset, h.p_offset + h.p_filesz
|
||||
header_size = image.structs.Elf_Nhdr.sizeof()
|
||||
@@ -111,7 +88,6 @@ def inspect_core(path, cache):
|
||||
padding.append({"segment_index": segment_index, "location": location(position, len(raw), end), "bytes_base64": encoded(raw)})
|
||||
break
|
||||
note = struct_parse(image.structs.Elf_Nhdr, image.stream, stream_pos=position)
|
||||
note_type_id = int.from_bytes(content[position + 8:position + 12], "little")
|
||||
owner_start = position + header_size
|
||||
descriptor_start = owner_start + ((note.n_namesz + 3) // 4) * 4
|
||||
note_end = descriptor_start + ((note.n_descsz + 3) // 4) * 4
|
||||
@@ -122,10 +98,7 @@ def inspect_core(path, cache):
|
||||
display = owner.split(b"\0", 1)[0].decode("utf-8", "replace") if owner else None
|
||||
descriptor = content[descriptor_start:descriptor_start + note.n_descsz]
|
||||
note_index = len(notes)
|
||||
# pyelftools selects ET_CORE labels without owner context. Keep
|
||||
# those labels only for exact CORE owners; other kinds are opaque.
|
||||
note_type = note.n_type if owner == b"CORE\0" else note_type_id
|
||||
notes.append({"index": note_index, "segment_index": segment_index, "type": note_type, "type_id": note_type_id,
|
||||
notes.append({"index": note_index, "segment_index": segment_index, "type": note.n_type,
|
||||
"location": location(position, note_end - position, end),
|
||||
"owner_location": location(owner_start, len(owner), end), "owner_bytes_base64": encoded(owner), "owner_display": display,
|
||||
"descriptor_location": location(descriptor_start, len(descriptor), end), "descriptor_bytes_base64": encoded(descriptor)})
|
||||
@@ -133,18 +106,18 @@ def inspect_core(path, cache):
|
||||
# Only the initial Linux CORE ABI is interpreted. Other owners retain exact bytes.
|
||||
if owner != b"CORE\0":
|
||||
continue
|
||||
if note_type_id == 1:
|
||||
if len(descriptor) < ctypes.sizeof(prstatus_type):
|
||||
raise CoreFailure("format", "Linux " + profile_label + " NT_PRSTATUS descriptor is truncated.")
|
||||
status = prstatus_type.from_buffer_copy(descriptor)
|
||||
if note.n_type == "NT_PRSTATUS":
|
||||
if len(descriptor) < ctypes.sizeof(elf_prstatus_amd64):
|
||||
raise CoreFailure("format", "Linux amd64 NT_PRSTATUS descriptor is truncated.")
|
||||
status = elf_prstatus_amd64.from_buffer_copy(descriptor)
|
||||
registers = []
|
||||
for name, kind in registers_type._fields_:
|
||||
relative = prstatus_type.pr_reg.offset + getattr(registers_type, name).offset
|
||||
for name, kind in user_regs_struct_amd64._fields_:
|
||||
relative = elf_prstatus_amd64.pr_reg.offset + getattr(user_regs_struct_amd64, name).offset
|
||||
registers.append({"name": name, "value": hex(getattr(status.pr_reg, name)), "location": location(descriptor_start + relative, ctypes.sizeof(kind), end)})
|
||||
threads.append({"note_index": note_index, "historical_pid": ctypes.c_int32(status.pr_pid).value, "recorded_current_signal": status.pr_cursig, "registers": registers})
|
||||
elif note_type_id == 0x53494749:
|
||||
elif note.n_type == "NT_SIGINFO":
|
||||
if len(descriptor) < ctypes.sizeof(elf_siginfo_64):
|
||||
raise CoreFailure("format", "Linux " + profile_label + " NT_SIGINFO descriptor is truncated.")
|
||||
raise CoreFailure("format", "Linux amd64 NT_SIGINFO descriptor is truncated.")
|
||||
signal = elf_siginfo_64.from_buffer_copy(descriptor)
|
||||
number = ctypes.c_int32(signal.si_signo).value
|
||||
code = ctypes.c_int32(signal.si_code).value
|
||||
@@ -152,7 +125,7 @@ def inspect_core(path, cache):
|
||||
signals.append({"note_index": note_index, "number": number, "code": code, "errno": ctypes.c_int32(signal.si_errno).value,
|
||||
"fault_address": hex(signal.sigfault_addr) if known else None,
|
||||
"fault_address_meaning": "recorded-sigsegv-address" if known else "unknown", "thread_association": "unknown"})
|
||||
return {"format": "elf-core", "architecture": architecture, "target_execution": "not-performed", "live_process_identity": "unknown",
|
||||
return {"format": "elf-core", "architecture": "x86_64-little-endian", "target_execution": "not-performed", "live_process_identity": "unknown",
|
||||
"segments": segments, "notes": notes, "note_padding": padding, "threads": threads, "signals": signals,
|
||||
"note_interpretation_completeness": "unknown",
|
||||
"limitations": ["The initial Linux note profile accepts System V/unspecified or GNU/Linux OSABI with ABI version zero; an unspecified OSABI alone does not prove operating-system origin.",
|
||||
|
||||
@@ -29,17 +29,6 @@ export default defineConfig({
|
||||
items: [
|
||||
{ text: "Native binaries", link: "/native-investigation" },
|
||||
{ text: "Go build metadata", link: "/go-binaries" },
|
||||
{ text: "IL2CPP metadata", link: "/il2cpp-metadata" },
|
||||
{ text: "Model artifacts", link: "/model-artifacts" },
|
||||
{ text: "Rust dependencies", link: "/rust-binaries" },
|
||||
{ text: "PE certificates", link: "/pe-certificates" },
|
||||
{ text: "PE imports", link: "/pe-imports" },
|
||||
{ text: "PE TLS/load config", link: "/pe-load-config" },
|
||||
{ text: "ELF notes", link: "/elf-notes" },
|
||||
{ text: "Apple IPS crash reports", link: "/apple-ips" },
|
||||
{ text: "Linux kernel modules", link: "/kernel-modules" },
|
||||
{ text: "CPython pyc files", link: "/python-pyc" },
|
||||
{ text: "Erlang BEAM", link: "/erlang-beam" },
|
||||
{
|
||||
text: "JavaScript and Electron",
|
||||
link: "/javascript-artifact-reconstruction",
|
||||
@@ -53,24 +42,12 @@ export default defineConfig({
|
||||
text: "Web network captures",
|
||||
link: "/web-network-captures",
|
||||
},
|
||||
{
|
||||
text: "Build RL environments",
|
||||
link: "/agent-environments-quickstart",
|
||||
},
|
||||
{ text: "Environment reference", link: "/agent-environments" },
|
||||
{ text: "Electron observation", link: "/electron-observation" },
|
||||
{ text: "Electron fuses", link: "/electron-fuses" },
|
||||
{ text: "Process capture", link: "/process-capture" },
|
||||
{ text: ".NET assemblies", link: "/managed-code-analysis" },
|
||||
{
|
||||
text: "Python packaged applications",
|
||||
link: "/python-packaged-applications",
|
||||
},
|
||||
{ text: "Android applications", link: "/android-analysis" },
|
||||
{ text: "Apple applications", link: "/apple-application-analysis" },
|
||||
{ text: "Desktop installations", link: "/desktop-applications" },
|
||||
{ text: "Firmware", link: "/firmware-analysis" },
|
||||
{ text: "iNES containers", link: "/ines-containers" },
|
||||
{ text: "SQLite databases", link: "/sqlite-databases" },
|
||||
{ text: "Packet captures", link: "/packet-captures" },
|
||||
{ text: "Signal recordings", link: "/signal-recordings" },
|
||||
@@ -81,13 +58,10 @@ export default defineConfig({
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "IDA", link: "/ida-provider" },
|
||||
{ text: "Kuna", link: "/kuna-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 PowerPC", link: "/ghidra-ppc" },
|
||||
{ text: "Ghidra GameCube DOL", link: "/ghidra-dol" },
|
||||
{ text: "Ghidra semantics", link: "/ghidra-provider" },
|
||||
],
|
||||
},
|
||||
@@ -97,10 +71,6 @@ export default defineConfig({
|
||||
items: [
|
||||
{ text: "MCP contracts", link: "/mcp-contracts" },
|
||||
{ text: "Tool design", link: "/tool-design" },
|
||||
{
|
||||
text: "Admission limits and recovery",
|
||||
link: "/design/limits-and-recovery",
|
||||
},
|
||||
{ text: "Testing", link: "/testing" },
|
||||
{ text: "Client verification", link: "/client-verification" },
|
||||
{
|
||||
|
||||
+38
-135
@@ -26,9 +26,6 @@ Readiness is `rea inspect-adb-client`: it runs `adb version` and reports the
|
||||
resolved binary, its version, and where it was selected from. A missing
|
||||
binary reports `capability_unavailable` with the `REA_ADB_PATH` remediation
|
||||
rather than attempting an installation.
|
||||
The `binary_session` inventory uses the same bounded client-version probe; it
|
||||
does not query devices, start the adb server, or establish that a requested
|
||||
serial is reachable. Device state is checked by each device operation.
|
||||
|
||||
## Answer an analyst question
|
||||
|
||||
@@ -76,102 +73,42 @@ existing analysis families directly, for example
|
||||
|
||||
## Operations
|
||||
|
||||
| Tool | Kind | What it does |
|
||||
| ----------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `inspect_adb_client` | read | Resolved binary, version, install path; never contacts the server |
|
||||
| `list_adb_devices` | read | Serial, state, transport, model metadata, inferred emulator/physical kind with basis |
|
||||
| `inspect_adb_device` | read | Fixed `getprop` whitelist: build, SDK, patch, fingerprint, product, ABIs, debuggable, `ro.kernel.qemu` |
|
||||
| `inspect_adb_display` | read | `wm size` and `wm density` as declared |
|
||||
| `list_adb_features` | read | `pm list features` (hardware/software features, hex GL versions) |
|
||||
| `list_adb_services` | read | `service list` binder services and interfaces |
|
||||
| `list_adb_processes` | read | `ps -A`: user, pid, ppid, rss, state, name |
|
||||
| `inspect_adb_window` | read | `dumpsys window` focus declarations (what is on screen) |
|
||||
| `read_adb_setting` | read | One `settings get` value; device-side null is reported as such |
|
||||
| `read_adb_logcat` | read | Bounded `logcat -d -t` dump for one buffer, optional pid filter |
|
||||
| `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, and count of listed requested-permission rows (including Unicode names) |
|
||||
| `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 |
|
||||
| `collect_adb_bugreport` | read | Device-generated bugreport zip, digested |
|
||||
| `push_adb_file` | **mutates device** | One local file to a device path (e.g. pushing tools into `/data/local/tmp`); checks for existing targets and refuses them without explicit overwrite (the check is not atomic); compares digests when device `sha256sum` is available |
|
||||
| `install_adb_package` | **mutates device** | Local APK via `adb install [-r]`; preserves its Success/Failure verdict, records `replace_requested`, and leaves actual replacement `null` because prior package state is not observed |
|
||||
| `uninstall_adb_package` | **mutates device** | Requests package removal for user 0 via `adb shell pm uninstall --user 0`, without data retention |
|
||||
| `start_adb_app` | **mutates device** | Resolves the package's launcher activity (`cmd package resolve-activity --brief`) and starts it (`am start -n`) |
|
||||
| `start_adb_activity` | **mutates device** | Starts an intent (`am start -a` with optional URI, component, and up to 16 typed extras); preserves the activity manager's verdict |
|
||||
| `stop_adb_app` | **mutates device** | Force-stops every process of a package, ending a dynamic-analysis run |
|
||||
|
||||
Screen capture validates the PNG signature, IHDR framing, dimensions, and IHDR
|
||||
checksum before publishing. It does not validate encoding fields or decode and
|
||||
validate image contents; that limitation is retained in Evidence.
|
||||
|
||||
When `push_adb_file` receives an existing directory, adb places the local file
|
||||
inside it under the local basename; Evidence reports that effective file path
|
||||
while retaining the caller's directory path in the input parameters. The
|
||||
private read-only staging copy preserves the source file's executable bits.
|
||||
|
||||
For `inspect_adb_window`, `focus_contexts` retains each display header, its exact
|
||||
display ID when recognized, and every focus declaration with its original line.
|
||||
Declarations without a display header are explicitly `unscoped`. A missing
|
||||
declaration differs from a declaration whose `value` is JSON `null`.
|
||||
`source_section` identifies the selected dumpsys section; `observed_lines`
|
||||
preserves its focus declarations and display headers in source order.
|
||||
|
||||
`focus_summary` is `single_context` only for one context with no repeated focus
|
||||
field. The singular `focused_window` and `focused_app` summarize that context;
|
||||
they do not establish device-wide focus. Multiple contexts or repeated fields
|
||||
produce `ambiguous` with null singular fields and all declarations retained.
|
||||
When no focus field was declared, the status is `unavailable`. REA reads the
|
||||
displays section only when the windows section declares neither focus field.
|
||||
|
||||
For `install_adb_package`, `replace_requested` records whether `-r` was sent.
|
||||
The `replaced` result is `null`: the successful install response does not say
|
||||
whether the package was installed fresh or replaced, and REA does not query the
|
||||
device first. The install verdict and host APK digest remain available.
|
||||
|
||||
Push and install consume a private snapshot of the selected local input. The
|
||||
reported size and SHA-256 identify those consumed bytes; `local_path` and
|
||||
`apk_path` retain the original input path, whose contents may change later.
|
||||
Snapshot creation needs temporary disk space proportional to the input size.
|
||||
Failed snapshot cleanup remains owned and is retried before later operations
|
||||
and during shutdown.
|
||||
| Tool | Kind | What it does |
|
||||
| ----------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `inspect_adb_client` | read | Resolved binary, version, install path; never contacts the server |
|
||||
| `list_adb_devices` | read | Serial, state, transport, model metadata, inferred emulator/physical kind with basis |
|
||||
| `inspect_adb_device` | read | Fixed `getprop` whitelist: build, SDK, patch, fingerprint, product, ABIs, debuggable, `ro.kernel.qemu` |
|
||||
| `inspect_adb_display` | read | `wm size` and `wm density` as declared |
|
||||
| `list_adb_features` | read | `pm list features` (hardware/software features, hex GL versions) |
|
||||
| `list_adb_services` | read | `service list` binder services and interfaces |
|
||||
| `list_adb_processes` | read | `ps -A`: user, pid, ppid, rss, state, name |
|
||||
| `inspect_adb_window` | read | `dumpsys window` focus declarations (what is on screen) |
|
||||
| `read_adb_setting` | read | One `settings get` value; device-side null is reported as such |
|
||||
| `read_adb_logcat` | read | Bounded `logcat -d -t` dump for one buffer, optional pid filter |
|
||||
| `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 | 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 |
|
||||
| `collect_adb_bugreport` | read | Device-generated bugreport zip, digested |
|
||||
| `push_adb_file` | **mutates device** | One local file to a device path (e.g. pushing tools into `/data/local/tmp`); refuses existing targets without explicit overwrite; verifies via the device's own sha256sum |
|
||||
| `install_adb_package` | **mutates device** | Local APK via `adb install [-r]`; preserves the device's Success/Failure verdict |
|
||||
| `uninstall_adb_package` | **mutates device** | Removes a package and its data for user 0 |
|
||||
| `start_adb_app` | **mutates device** | Resolves the package's launcher activity (`cmd package resolve-activity --brief`) and starts it (`am start -n`) |
|
||||
| `start_adb_activity` | **mutates device** | Starts an intent (`am start -a` with optional URI, component, and up to 16 typed extras); preserves the activity manager's verdict |
|
||||
| `stop_adb_app` | **mutates device** | Force-stops every process of a package, ending a dynamic-analysis run |
|
||||
|
||||
## Interpret the result
|
||||
|
||||
- **Package numeric declarations retain failures.** `inspect_adb_package`
|
||||
admits the first valid `userId` or `appId` as `user_id`: a nonnegative safe
|
||||
integer, including zero. A null value means no valid declaration was found.
|
||||
Malformed declarations before or after a valid value remain in
|
||||
`numeric_issues`: the first ten issues with command, field, reason and original
|
||||
`raw_line`, plus the exact `total`. Reasons are `invalid_format` or
|
||||
`unsafe_integer`. Any issue makes package coverage partial; existing section
|
||||
omissions can also make it partial. `version_code` remains exact source text.
|
||||
- **Display nulls preserve uncertainty.** `inspect_adb_display` admits positive
|
||||
safe integers. Absent or malformed optional values are null; `numeric_issues`
|
||||
distinguishes malformed declarations from absence. It keeps the first ten
|
||||
issues and exact total, with command, field, original line and reason
|
||||
(`invalid_format`, `unsafe_integer` or `nonpositive`). The last recognized
|
||||
labeled declaration wins, even when malformed. A physical-density label
|
||||
suppresses bare-number fallback even when malformed; without that label, the
|
||||
first unsigned bare number wins, including zero or an unsafe integer that
|
||||
leaves the value null. Display coverage is complete only with known physical
|
||||
density and no numeric issues. Missing or malformed required physical size
|
||||
produces a protocol failure with `details.partial_observation`: an
|
||||
`inspect_adb_display` observation from provider `adb`, reason
|
||||
`physical_size_absent` or `physical_size_malformed`, and a `result` retaining
|
||||
client, serial, optional display facts, numeric issues, null physical size
|
||||
and partial coverage.
|
||||
- **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 derived from the build's
|
||||
`ro.kernel.qemu` property: `true` only when its value is `"1"`, `false` for
|
||||
any other reported value, and `null` when the property is absent. The raw
|
||||
property value remains available in `properties`.
|
||||
`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. A pulled file's host name is
|
||||
@@ -181,34 +118,17 @@ and during shutdown.
|
||||
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.
|
||||
- **Package path refusals are diagnosable.** Refusals from `pull_adb_package`
|
||||
return `unreadable_output` with `retryable: false`, keep the `pm path` output
|
||||
in `details.captured_output`, and name the refusal in `details.reason`.
|
||||
`unsafe_device_path` lists refused entries in
|
||||
`details.diagnostics.rejected_paths` as `{device_path, reason}`;
|
||||
`duplicate_local_destination` reports the shared `file_name` and both
|
||||
`device_paths`; `local_destination_unavailable` reports the `device_path`,
|
||||
`file_name`, and filesystem `error_code`. Diagnostics retain raw device
|
||||
strings, while the caller-facing message escapes invisible and bidirectional
|
||||
characters so they cannot hide text or reorder the 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.
|
||||
- **Partial results keep facts.** Failed individual pulls return successful
|
||||
artifacts with `coverage: "partial"` and per-file `failures`. Listings
|
||||
and package searches that contain unrecognized lines report the first ten
|
||||
as `unparsed_lines`, state the exact total in a limitation when more were
|
||||
seen, and mark partial coverage instead of dropping rows.
|
||||
that contain unrecognized lines report them as `unparsed_lines` and mark
|
||||
partial coverage instead of dropping rows.
|
||||
- **Mutations are explicit.** `push_adb_file`, `install_adb_package`, and
|
||||
`uninstall_adb_package` declare `mutatesTarget` and never run implicitly:
|
||||
pushes check for existing device files before transfer and refuse them
|
||||
unless `overwrite` is set. A failed or unexpected existence probe stops
|
||||
transfer even with `overwrite`; only a clean `test -e` exit of 1 with no
|
||||
output is interpreted as absence. The check is not atomic, so a different
|
||||
process can create the path before `adb push` writes it. When available, the device's
|
||||
`sha256sum` verifies the transfer. A missing command or output without a
|
||||
recognizable digest reports `device_digest_source: "unavailable"` with an
|
||||
unverified-transfer limitation; installs preserve the
|
||||
pushes refuse existing device files unless `overwrite` is set and verify
|
||||
the transfer with the device's own `sha256sum`; installs preserve the
|
||||
device's `Failure [...]` reason verbatim.
|
||||
- **Producer identity stays attached.** Every result embeds the producing
|
||||
adb client (`binary_path`, `path_source`, `version`, `installed_path`).
|
||||
@@ -220,21 +140,14 @@ and during shutdown.
|
||||
The app/activity lifecycle tools use `am start -W` and `am force-stop`.
|
||||
- Per-invocation budgets: client/devices 10-20 s, shell reads 20-60 s with
|
||||
64 KiB-8 MiB capture caps (logcat 4 MiB, screen 32 MiB), transfers and
|
||||
bugreports 600 s. Output capture overflow reports `resource_constraint`
|
||||
and names the byte allowance. Deadline expiration, including the
|
||||
client-version probe, reports `provider_timeout` and names the effective
|
||||
milliseconds. This release has no runtime override for those deadlines or
|
||||
capture caps. Command cancellation preserves the caller's exact reason;
|
||||
a later abort does not replace an already completed command failure.
|
||||
bugreports 600 s. Exceeded budgets report `resource_constraint`; stopped
|
||||
commands report `provider_timeout`.
|
||||
- Pull and capture outputs are additive: the destination must not already
|
||||
exist. Re-running a completed pull refuses the existing directory instead
|
||||
of overwriting evidence.
|
||||
- Cancellation during a package pull preserves caller-selected output. The
|
||||
error includes partial observations identifying completed artifacts and the
|
||||
interrupted local path; remaining APKs were not attempted.
|
||||
- Screen capture checks cancellation before publishing its exclusive output.
|
||||
If cancellation arrives after publication, the error retains the committed
|
||||
file's path, digest and dimensions; the file remains available as evidence.
|
||||
- Bugreports collect into isolated staging and publish with exclusive creation;
|
||||
filename collisions select the next numeric suffix. If publication fails, the
|
||||
completed archive remains in staging. The error's partial observation gives
|
||||
@@ -257,25 +170,15 @@ beyond already-connected devices (`adb connect`) are not exposed yet.
|
||||
|
||||
## Real verification
|
||||
|
||||
`npm run verify:adb` exercises observation and transfer operations against a live device. It
|
||||
`npm run verify:adb` exercises every operation against a live device. It
|
||||
requires `adb` (or `REA_ADB_PATH`) and any attached emulator or phone;
|
||||
`--serial` selects one when several are attached, `--pull PACKAGE` pulls a
|
||||
real package and verifies the returned digests against the files on disk.
|
||||
The optional `--install-apk` lifecycle is unavailable until the selected APK
|
||||
identity and device cleanup ownership can be established safely.
|
||||
real package and verifies the returned digests against the files on disk,
|
||||
and `--install-apk PATH` drives a full install → resolve → uninstall
|
||||
lifecycle with a lane-owned probe.
|
||||
|
||||
Record: verified against adb 34.0.5 on Linux against an Android 14
|
||||
(API 34) x86_64 emulator — all operations above, a 224-package inventory,
|
||||
287 services, 92 features, a real two-APK split set pulled with byte-exact
|
||||
digests, a verified push/pull roundtrip, a 1.3 MB screen capture, and the
|
||||
complete package lifecycle; see `docs/testing.md` for the lane summary.
|
||||
|
||||
Intent integer extras use signed decimal text. The ADB argument encoder removes
|
||||
redundant leading zeros for Android's radix-sensitive `--ei` parser while retained
|
||||
Evidence keeps the caller's original spelling. Package inspection accepts both
|
||||
`userId` and Android 16's `appId` spelling for the legacy `user_id` result field.
|
||||
|
||||
A completed push and device digest verification are separate observations. A
|
||||
verification timeout, cancellation, or output budget failure returns the typed
|
||||
failure with partial Evidence of the completed transfer. An unavailable checksum
|
||||
command or unrecognized checksum output leaves verification explicitly unknown.
|
||||
@@ -1,204 +0,0 @@
|
||||
# Build RL environments with REA
|
||||
|
||||
REA turns a recorded web app session into a runnable environment for training
|
||||
and testing AI agents. An agent can practice tasks such as finding an issue,
|
||||
creating a folder, or editing a record without running the original app.
|
||||
|
||||
REA builds the tools the agent calls, the local data those tools use, and a
|
||||
test that checks the environment starts and responds. It supports changes
|
||||
when the recording confirms their effects. You can then use the environment
|
||||
with your RL framework to set task goals, check rewards, and run training.
|
||||
|
||||
## Before you start
|
||||
|
||||
Install [REA and its agent integration](installation.md). To record an app,
|
||||
you also need a running test instance and a Chrome-family browser. Choose an
|
||||
instance where the agent is allowed to create, edit, and delete data. REA
|
||||
records the app you provide; you start it and prepare its test data.
|
||||
|
||||
To run the exported environment, install the
|
||||
[AgentEnv framework](https://github.com/scaleapi/agentenv-framework#install)
|
||||
and start Docker. REA's [compatibility check](testing.md#exported-environment-bundles)
|
||||
uses AgentEnv version `0.9.1334` with Python 3.12.
|
||||
|
||||
There are two ways to build an environment: ask your agent to record the app,
|
||||
or start from a recording you already have.
|
||||
|
||||
## 1. Record and build with your agent
|
||||
|
||||
Give your agent the app URL, the operations you need, your browser's executable
|
||||
path, and an output directory. For an app with folders, adapt this prompt:
|
||||
|
||||
```text
|
||||
Use REA to build an AgentEnv environment from my test web app at
|
||||
http://127.0.0.1:3000. Cover listing, creating, renaming, and deleting folders.
|
||||
You may create and remove test folders. Leave the existing data unchanged.
|
||||
|
||||
Sign in as admin using the password in APP_PASSWORD. Use the browser at
|
||||
/absolute/path/to/chromium. Save the environment to
|
||||
/absolute/path/to/my-env, which does not exist yet.
|
||||
|
||||
Record the session, check which operations the environment supports, and
|
||||
export it. Report any unsupported operations and what is missing.
|
||||
```
|
||||
|
||||
Replace the URL, account, paths, and folder operations with your own. Set
|
||||
`APP_PASSWORD` in the environment that starts REA's MCP server so REA can use
|
||||
it without putting the password in the prompt.
|
||||
|
||||
Your agent inspects the app, records a session, checks the result, and exports
|
||||
a folder containing the runnable environment. It reports which operations
|
||||
work and which need more evidence. The app is used during recording; the
|
||||
exported environment runs from the saved data.
|
||||
|
||||
After your agent exports the folder, skip to
|
||||
[run the environment](#3-run-the-environment). The next section is an
|
||||
alternative for an existing recording.
|
||||
|
||||
## 2. Or build from a saved recording
|
||||
|
||||
A HAR file records a browser's network requests and responses. Export one
|
||||
with API response bodies from your browser's network panel, or use a
|
||||
recording you already have. To include create, edit, and delete operations,
|
||||
record those actions and the reads that show their results.
|
||||
|
||||
Build the environment into a new or empty directory:
|
||||
|
||||
```sh
|
||||
rea environment-build ./session.har ./my-env --name my-env --json
|
||||
```
|
||||
|
||||
You can also pass capture Evidence JSON saved by REA's
|
||||
`capture-browser-scenario` or `inspect-web-network-capture` commands. Here,
|
||||
Evidence means REA's saved recording with its source information. The build
|
||||
accepts HAR files and those JSON recordings. It reads local data and writes
|
||||
the environment folder without contacting the app.
|
||||
|
||||
Add `--dry-run` to review the build report without writing the folder. For an
|
||||
unreleased REA checkout, use `--package-tarball` with its packed package so
|
||||
the exported environment runs the same implementation.
|
||||
|
||||
The build report tells you what the environment supports:
|
||||
|
||||
| Report field | What to check |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `state` | Whether the environment can answer from local state or uses recorded-response replay. |
|
||||
| `coverage.reads` | Which read operations use state and which replay recorded answers. |
|
||||
| `coverage.writes` | Which create, edit, and delete operations are supported. Each withheld operation includes a reason and a recording hint. |
|
||||
| `warnings` | Recorded content or local paths to review before sharing the folder. |
|
||||
|
||||
A successful build means the folder was created. Check the report to confirm
|
||||
it supports the operations your tasks need. `coverage: null` means the build
|
||||
could not prepare state for this environment and it will use replay.
|
||||
|
||||
## 3. Run the environment
|
||||
|
||||
Whether your agent exported the folder or you built it from a HAR, run it
|
||||
with AgentEnv:
|
||||
|
||||
```sh
|
||||
agent-env run ./my-env
|
||||
```
|
||||
|
||||
Use the folder path your agent reported if it differs. The first run builds
|
||||
the container image, so it needs access to the image and package sources.
|
||||
|
||||
REA includes a basic test, called the smoke task. It checks the expected tool
|
||||
count and, when a suitable read tool exists, calls it and checks its response
|
||||
status. Passing this test shows the environment starts and responds in the
|
||||
tested setup. It does not yet score an agent on an application task.
|
||||
|
||||
The folder contains the recording, a tool manifest describing the available
|
||||
operations, a Dockerfile, environment configuration, and the test. The
|
||||
[export reference](agent-environments.md#export-a-bundle) describes each file.
|
||||
|
||||
## 4. Add tasks and reward checks
|
||||
|
||||
Start with one task the exported tools support. Give it a clear goal and a
|
||||
checkable result. For example, if folder creation is supported, ask your
|
||||
agent:
|
||||
|
||||
```text
|
||||
Use this exported environment to add an AgentEnv task that asks an agent
|
||||
to create a folder named Practice. Check the saved environment state to
|
||||
confirm the folder exists with that name, and use that check to score the
|
||||
result. Reset the environment before each run so attempts start with the
|
||||
same data. Use the AgentEnv task and verifier formats.
|
||||
```
|
||||
|
||||
This asks your agent to help build the task and its grading check around the
|
||||
environment REA generated. A retrieval task might check an answer against a
|
||||
recorded value. A task that changes data should check the resulting state.
|
||||
When state is available, the environment supports reset, load, and read-back
|
||||
operations for repeatable runs.
|
||||
|
||||
Use the [AgentEnv documentation](https://www.agentenvframework.com/docs) for
|
||||
task configuration, agent connections, scoring, and execution. Connect the
|
||||
resulting tasks to your chosen RL training workflow.
|
||||
|
||||
## Improve an operation that is missing
|
||||
|
||||
For REA to support a change, the recording needs to show what happened before
|
||||
and after it. When recording against your test app:
|
||||
|
||||
1. Read the list and an existing item before making changes. Repeat the
|
||||
initial reads to show which values change independently.
|
||||
2. Create a test item, read it back, and read the list before the next change.
|
||||
Repeat with a second item and different text.
|
||||
3. Edit each test item, then read the item and list again. Leave more than a
|
||||
second between creation and the first edit to show time-dependent changes.
|
||||
4. Delete the test items, then read them back and read the list to confirm
|
||||
their removal.
|
||||
|
||||
Also record filters, searches, sorting, and paging if your tasks need them.
|
||||
The agent should check that every recording step completed before using it.
|
||||
The [recording workflow](https://github.com/morluto/rea/blob/main/.agents/skills/reverse-engineer-anything/references/environments.md)
|
||||
has the detailed browser instructions, including timeouts and handling IDs
|
||||
assigned by the app.
|
||||
|
||||
Use the report's hints to decide what to record next:
|
||||
|
||||
| Reason | Next step |
|
||||
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `unconfirmed` | Read the item and its list after the change. |
|
||||
| `no-state-target` | Check the path template and recorded views; record missing reads. The route form may not be supported as a mutation target. |
|
||||
| `no-observation` | Read back a created item. For an edit or delete, also read it before changing it. |
|
||||
| `contradicted` or `side-effect` | Inspect the reported disagreement. The operation may need behavior REA cannot model yet. |
|
||||
|
||||
See the [full reason table](agent-environments.md#build-a-bundle-in-one-step)
|
||||
for the other cases. Rebuild from the new recording and check the report
|
||||
again. A new recording replaces the build input; combining separate recording
|
||||
sessions is not supported.
|
||||
|
||||
## Compare with the original app
|
||||
|
||||
For a stronger check, record a separate sequence of calls against the real
|
||||
app, starting from the same test data as the original recording. This is a
|
||||
probe. Restore that starting data before each probe sequence, then compare
|
||||
the saved probe recordings with the environment:
|
||||
|
||||
```sh
|
||||
rea environment-compare ./my-env/envs/my-env/manifest.json \
|
||||
--evidence ./my-env/envs/my-env/evidence.json \
|
||||
--probe ./probe-create-edit-delete.json --probe ./probe-reads.json --json
|
||||
```
|
||||
|
||||
Each probe file must be capture Evidence JSON from REA. The comparison runs
|
||||
offline and reports differences in responses, unsupported calls, and missing
|
||||
observations. It accounts for supported differences in IDs, times, and
|
||||
modeled generated values. Agreement applies to the calls those probes check.
|
||||
See the [comparison reference](agent-environments.md#measure-fidelity-against-the-application)
|
||||
for the exact rules.
|
||||
|
||||
## Before sharing the environment
|
||||
|
||||
The exported folder includes retained response bodies and local source paths.
|
||||
Standard authentication headers are excluded, but custom headers, query
|
||||
values, and body tokens can remain. Review the content you plan to share.
|
||||
Credential-name warnings point to values worth reviewing; they do not remove
|
||||
those values or establish that the recording contains no credentials.
|
||||
|
||||
For direct MCP serving, replay behavior, state controls, and resource limits,
|
||||
see the [environment reference](agent-environments.md). The
|
||||
[implementation index](specs/agent-environments/README.md) links the contributing
|
||||
PRs and their verification records.
|
||||
Loaded 100 of 6503 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user