* fix(pcap): report a refused tool launch as an unavailable tool When a capture read's tool launch was refused with EPERM, pcap reported it as unreadable tool output (AnalysisProtocolError) instead of an unavailable tool. Pcap also never told "not executable" apart from "missing". Add classifyLaunchFailure in src/process as the one owner of which spawn errnos mean missing (ENOENT, ENOTDIR) and which mean present but not executable (EACCES, EPERM, ENOEXEC, EISDIR). Pcap, apktool and gdb used three different errno lists; all three now use it. * refactor(process): classify owned command outcomes through one owner Add ProviderOutcome, read once from the supervisor's facts, and classifyProviderOutcome, the one mapping from an outcome to a typed error. Migrate pcap and the shared artifact producer command failure (TShark, sigrok). Add the rea/provider-failure-classifier lint rule with a shrink-only baseline of per-site (cause: unknown) => AnalysisError functions. * refactor(process): classify Java and adb launch refusals with the shared owner (#10598) The Ghidra and JADX Java probes and adb's version probe now take missing-versus-not-executable from classifyLaunchFailure instead of their own errno checks. ELOOP and ENAMETOOLONG join the owner's missing set, so adb keeps its existing classification and both Java probes now treat a non-directory path component as a missing runtime. * fix(pcap): preserve command launch attribution * test(pcap): expect an unknown failing stage when a limited capture tool exits unsuccessfully * refactor(process): classify owned command outcomes through one owner Add ProviderOutcome, read once from the supervisor's facts, and classifyProviderOutcome, the one mapping from an outcome to a typed error. Migrate pcap and the shared artifact producer command failure (TShark, sigrok). Add the rea/provider-failure-classifier lint rule with a shrink-only baseline of per-site (cause: unknown) => AnalysisError functions. * test(lint): cover baselined files in the provider-failure classifier rule * fix(apktool): report the decoder JVM's heap exhaustion as memory, not a target refusal The supervisor now watches for a managed runtime's out-of-memory report while output streams (JVM java.lang.OutOfMemoryError, V8 'JavaScript heap out of memory'), so a retention budget cannot hide it. ProviderOutcome gains a runtime-exhausted kind and classifyProviderOutcome maps it once. Apktool decode opts in and moves to the shared classifier; Ghidra's single-purpose output token becomes the JVM runtime option and its area matching reuses the shared table. * fix(process): keep the runtime's out-of-memory report on the memory error it supports * fix(pcap): attribute a limited capture's exit through the prlimit wrapper * fix(process): keep a shortened out-of-memory line marked as shortened * fix(process): identify the refused executable for every launcher shape Name the executable on errnos Node throws synchronously from spawn (ENOEXEC, ENOTDIR, ...) and read the Windows native launcher's executable-launch constraint, so pcap attributes those refusals to the selected tool or limiter instead of an unknown stage. * test(pcap): prove synchronous launch naming with ENOTDIR on every POSIX host * fix(apktool): advise the JVM limit that governs the reported memory area * fix(ghidra): read the supervisor's parsed OOM area instead of reparsing lines * fix(sqlite,capture): read V8 heap exhaustion from streamed output, not retained stderr (#10724) The SQLite worker and the Node capture decoders now declare runtime: "v8", so the supervisor observes V8's heap report as output streams. Both classifiers read the runtime-exhausted outcome instead of matching retained stderr, so a report beyond the retention budget, or one whose process then overflowed its output budget, is still reported as memory exhaustion. * refactor(evm): classify EVMole worker outcomes through the shared provider classifier (#10716) * refactor(evm): classify EVMole worker outcomes through the shared provider classifier The EVMole worker's cancellation, deadline, output budget and CPU/file-size signals now go through classifyProviderOutcome; only the prlimit launch refusal and the reserved EFBIG exit remain in its dialect. The worker is opted into V8 exhaustion scanning so a heap report names its fixed 256 MiB heap. providerOutcomeOf now reads a launch refusal only from a spawn errno, so a private-storage or carrier read errno is no longer reported as a limiter launch failure. * fix(evm): keep a stream failure's exit status and update Linux worker expectations * refactor(process): read launch refusals through the launcher identity owner * refactor(wasm): classify WABT command outcomes through the shared provider classifier (#10727) * refactor(wasm): classify WABT command outcomes through the shared provider classifier WABT's cancellation, command deadline, output budget, and inherited-limit signals now go through classifyProviderOutcome; its dialect keeps only the configuration-probe and launch-refusal unavailability and the unclassified-exit output error. A tool ended by SIGXCPU/SIGXFSZ or the memory limit is now a resource constraint instead of an output error, and a refused launch in the producer phase is reported as unavailable. The shared timeout context gains the analysis setting that selected the deadline, and the output-limit context may name its exceeded budget. * fix(wasm): keep setup guidance when a WABT version probe's output cannot be collected * refactor(providers): classify raw provider failures through one owner * refactor(process): build classified cancellations with cancelledBy and the carried caller reason * chore(lint): count runOwned's close-failure mapper from main in the classifier baseline * fix(process): carry the runtime's exhaustion report on every memory error that claims it SQLite and historical-capture memory errors recognised a streamed V8 heap report but dropped it, so a report outside retained output reached callers without its source line or omission flags. One projection, runtimeExhaustionReport, now owns that shape for the shared classifier and both consumers. Also lowers the EVMole and WABT cancellation-constructor baselines. * fix(process): keep the provider's recovery for a native-thread exhaustion report The shared classifier replaced the provider-supplied recovery with generic capacity advice whenever the JVM reported it could not create a native thread, dropping Apktool's launcher guidance and flipping retryable to true. The provider's recovery now wins; the capacity advice remains the fallback for providers that supply none. * refactor(pwntools): classify pwntools failures through the shared provider classifier (#10782) * refactor(pwntools): classify pwntools failures through the shared provider classifier pwntools' per-site classifier is replaced by pwntoolsFailureDialect for classifyProviderFailure. Command outcomes go through classifyProviderOutcome, launch refusals through launcherRefusalOf instead of a 'spawn ' syscall prefix check, the input limit through the file dialect's fileSize, and the cleanup report through commandCleanup. The shared read cancellation now carries the reader's partial observation, and resource-signal reports accept provider detail. * fix(pwntools): report a failed command's output once and expect resource_constraint for overflow The shared outcome owner already attaches the command's output as capturedOutput, so the pwntools adapter fallback no longer copies the same text into diagnostics.stdout, diagnostics.stderr and diagnostics.captured_output; diagnostics keep the exit facts. Update the overflow expectations the dialect migration missed (layout provider cleanup and over-budget rows, pwntools cleanup) to resource_constraint. * refactor(wasm): leave WABT cancellation to the shared owners WabtFailures matched any Error named AbortError as a cancellation. No WABT path produces one: reads, path checks and owned commands report cancellation as ArtifactReaderFailure or OwnedCommandFailure, which classifyProviderFailure and classifyProviderOutcome already classify with the caller's reason. The provider's two signal checks now build the cancellation with cancelledBy. Lowers the AnalysisCancelledError constructor baseline for both WABT files to zero. * fix(wasm): report a cancelled snapshot write as the caller's cancellation The private snapshot write passed the caller's signal to Node's writeFile, whose abort envelope reached the dialect's unclassified output failure once the name-based AbortError guess was removed. The write now turns the envelope carrying its own signal's reason into cancelledBy at the producer. * refactor(firmware): classify firmware failures through the shared provider classifier (#10810) Firmware producers now report their own deadline expiry: the start check, the output inventory and launcher admission throw the phase timeout where they observe the abort, and a read the phase signal cancelled reports it through the dialect's interruption. The classifier no longer rereads a cancellation's reason as a timeout. A new readCleanup hook keeps firmware's cleanup report shape. * refactor(captures): classify TShark, sigrok and pcap failures through the shared provider classifier TsharkCaptureProvider and SigrokRecordingProvider replace #failure with a dialect, and PcapProvider replaces analysisFailure and the tool-identity classifier. ArtifactAnalysisResources exposes its command context and a readCleanupFailure composer so the readCleanup hook keeps each provider's cleanup report shape. * refactor(jeb): classify JEB failures through the shared provider classifier JEB's raw MCP failures go through classifyProviderFailure with a JEB dialect; a typed attribution step reports a shared phase's failure under the REA operation. Each MCP phase deadline now reports its own interruption when it aborts, deciding a cancellation's owner from the caller's own signal instead of comparing abort reasons. A refusal carrying the JVM's OutOfMemoryError report becomes a resource constraint naming JEB's Java heap. * fix(jeb): advise the JVM limit that governs the reported memory area * refactor(ghidra): classify Ghidra failures through the shared provider classifier (#10836) * refactor(ghidra): classify Ghidra failures through the shared provider classifier PSP extension inspection reports through classifyProviderFailure with a Ghidra dialect. The startup deadline decides at abort time whether the shared startup owner closed it, so Ghidra and Hopper no longer compare the abort reason with the owner's sentinel, and Ghidra's startup no longer compares a producer cancellation's reason with the deadline's. Three helpers that only construct one error kind now name that kind instead of AnalysisError. * refactor(hopper): classify original-source read failures through the shared provider classifier (#10838) * test(ghidra): pin the aborted startup deadline over a producer cancellation * refactor(kuna): classify Kuna failures through the shared provider classifier (#10843) Session operations, the workspace copy and the project export report raw failures through classifyProviderFailure with Kuna dialects; the copy and export keep their cleanup report shapes through the readCleanup hook. * chore(guards): shrink the cancellation construction baseline after merging the base
8.9 KiB
JEB instrumentation and analysis
REA exposes JEB-backed project, unit, and decompilation inspection through the CLI and MCP, served by JEB's own MCP interface. JEB is a bring-your-own engine: REA never installs it, launches it, or opens its project databases itself.
Bring-your-own engine
Start a JEB client that serves MCP and point REA at it:
-
Default endpoint:
http://127.0.0.1:8425/mcp. Override withREA_JEB_MCP_URL(absolutehttp/httpsURL with literal host127.0.0.1or[::1]; redirects, remote hosts, credentials, query strings, and fragments are rejected rather than silently dropped). -
In the JEB GUI client, start the MCP server from the File menu (or via the VIBRE assistant).
-
Headless, the official Linux CE build
5.48.0.202610071539supports-c --mcp. From the JEB installation directory, select JDK 21 or newer throughJAVA_HOME, choose your target and bind explicitly to loopback:"$JAVA_HOME/bin/java" -jar bin/app/jeb.jar -c --mcp --interface=127.0.0.1 \ --port=8425 --infile=/absolute/path/to/targetComplete the vendor's first-use EULA and license-key activation when prompted. The Linux audit used an isolated installation, a 1536 MiB Java heap and one active processor inside a separately bounded process family; JVM heap limits alone do not bound the whole family.
-
JEB's documented programmatic route (
JebMcpServerInstancefrom the JEB API) remains an alternative. The earlier licensed 5.48 headless verification used that route because its CLI did not accept--mcp. Check your installed build's-c --helpand live tool catalog; the version number alone does not establish the launcher options or supported operations.
For setup registration, set REA_JEB_MCP_URL in the REA process environment
before running rea setup. The approved setup plan preserves the exact selected
URL after validating its syntax. That validation does not establish that the
JEB service is reachable or that a project is open; use the existing
inspect-jeb-client command for a read-only service query.
MCP binary_session probes the live JEB service and its advertised tool catalog.
Availability is reported per operation: a reachable headless client can support
inspection and decompilation while refusing open_jeb_project. Catalog discovery
is one deadline across connection and pagination; a responsive server cannot keep
discovery running indefinitely by returning more pages.
Catalog discovery and each tools/call share REA_JEB_MCP_TIMEOUT_MS. Set it
in REA's launch environment to an integer from 1 to 2147483647 milliseconds,
for example REA_JEB_MCP_TIMEOUT_MS=300000 for a five-minute budget. Omitted
means the pinned MCP client SDK default of 60000 ms (@modelcontextprotocol/client
2.3.1). Change the budget for subsequent operations with
update_analysis_settings using jeb.mcp_timeout_ms. To change the launch
value, update REA's environment and restart. Raising the setting raises both
phases. It does not make one end-to-end deadline across a whole CLI or MCP
operation: discovery and each tools/call each get that budget. A request that
reaches its budget returns a typed timeout naming the phase and the effective
milliseconds, while caller cancellation remains a cancellation. Allow execution,
cleanup, and delivery headroom in the outer MCP client deadline.
Earlier verification used JEB 5.48.0 (Linux x64, licensed build) serving jeb-mcp-server 1.3.0 over streamable HTTP. The official Linux x64 CE build 5.48.0.202610071539 was also verified through real CLI and production stdio MCP native-code workflows; see the Linux CE audit for measurements and limits. CE verification does not establish licensed/Demo Android decompilation or GUI behavior.
Operations
| MCP tool / CLI command | Purpose |
|---|---|
inspect_jeb_client / inspect-jeb-client |
Engine version, GUI or headless mode, startup time |
open_jeb_project / open-jeb-project |
Open or create a project from an artifact or .jdb2 on the engine host; returns input-file SHA-256 digests and top-level units |
list_jeb_units / list-jeb-units |
Page units by stable path and type (filter, parent-unit-path, index, count) |
decompile_jeb_item / decompile-jeb-item |
Decompile one type or method to engine pseudo-code |
All four preserve target-file bytes. open_jeb_project selects or creates an engine-side project; analysis queries can populate computed analysis within it. Evidence records the endpoint, caller-selected parameters, and available engine-reported digests. Mutation workflows the engine also offers (renames, comments, save_project, close_project) are intentionally not exposed yet.
Engine coverage note: The earlier licensed 5.48.0 headless verification did not advertise open_project; it selected the target when the engine client started. For that build, use --infile on the programmatic launcher or open the file in the GUI. The audited CE headless catalog also omitted open_project; the verified CLI route above selects its input at startup. Use binary_session to inspect the current engine's advertised operations. REA preserves an unavailable tool's refusal; open_jeb_project requires an engine that advertises open_project. The CE audit does not establish GUI project-opening support.
Semantics
- Paths in
open_jeb_projectare resolved by the JEB process, not by REA; REA does not require the artifact to exist locally. - The engine caps
list_unitspages at 100; a full page is reported ascoverage: "partial"because more units may exist. An empty remainder iscompleteonly when fewer units than requested were returned. - When
decompile_jeb_itemomitsunit_path, the engine implicitly selects the project's first code unit; REA reportsunit_path: nulland a limitation instead of guessing the effective unit. item_addressis a JEB code-item reference, such as a value returned by the engine'slist_code_items. Preserve that representation and select its code unit explicitly. For the audited CE SQLite unit, the exported method reference wassqlite3_libversion_number; using its raw ELF symbol address did not produce a successful decompilation. File offsets, ELF virtual addresses and engine item references have different semantics. REA currently exposes unit listing, while code-item discovery remains an upstream JEB operation.- Valid engine refusals (
success: falseenvelopes) returnexecution_failurewith the engine's message when available. Diagnostics preserve the original envelope, including any recovery advice, and the inputs of a refused primary request. If client metadata fails after a primary reply, that reply and the caller's inputs remain in partial Evidence. REA does not infer an unsupported target from a refusal, invoke a recovery tool, or retry the request. A refusal whose message carries the JVM's ownjava.lang.OutOfMemoryErrorreport is a resource constraint instead (capacityfor native-thread exhaustion), with recovery naming the JEB client's Java heap; the envelope stays in diagnostics. - Successfully parsed client information is reused within the current MCP session, including when the engine omits its version. Explicit client inspection and availability probes refresh it; reconnecting invalidates the cached information.
- An unreachable endpoint is a provider-availability failure with recovery guidance; REA does not spawn the engine or automatically replay failed operations.
- After a transport or RPC failure (including a request or catalog timeout), the next explicit call attempts a new MCP connection after cleanup succeeds. Engine version and GUI mode are refreshed for that connection. Caller cancellation and decoded engine refusal envelopes preserve the existing connection.
Real-engine verification lives in the JEB lane described in testing.md; mock transport tests do not establish real-engine compatibility.
A project-open refusal retains the selected engine-host artifact path separately from the service endpoint. Discovery of an unreachable service includes the same start/configuration recovery as a direct request. If a primary reply completes before a later client-information lookup fails or is cancelled, the typed failure retains that reply as partial Evidence. Engine metadata stays unknown; this is not a claim that the full operation completed.