[GH-ISSUE #5746] [Bug] Firmware destination precondition failures are attributed to Unblob execution #3492

Closed
opened 2026-10-31 00:41:06 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @coygeek on GitHub (Oct 21, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/5746

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

extract-firmware attributes two documented destination-precondition failures to Unblob: an existing output directory and a destination beneath a missing parent both return execution_failure / execution_failure, retryable: false, and details.provider_id: "unblob". Both omit field issues and input-contract recovery, and recommend rea doctor.

The independent verifier reproduced both with real Unblob. An absent destination beneath an existing parent extracted the same fixture successfully with complete coverage and byte-identical decompressed output. Existing output was safely refused; this report concerns classification and recovery, not overwriting.

Reproduction

From the built source checkout with locked dependencies on Linux x64, provide Unblob 26.6.4, 7z, util-linux prlimit, procps-compatible ps, and Python 3. The following generates the verifier's fixture and exercises both failures and two controls. Run without shell errexit, since refusal commands intentionally exit 1.

work_dir="$(mktemp -d)"
mkdir -p "$work_dir/home" "$work_dir/cfg" "$work_dir/data" "$work_dir/cache" "$work_dir/state" "$work_dir/tmp" "$work_dir/existing"
unblob_command="$(command -v unblob)"
python3 - "$work_dir" <<'PY'
import gzip, pathlib, sys
root = pathlib.Path(sys.argv[1])
payload = b"payload\n" * 20
blob = gzip.compress(payload, compresslevel=9, mtime=0)
(root / "sample.bin").write_bytes(b"\0" * 16 + blob + b"\0" * 16)
(root / "expected.bin").write_bytes(payload)
PY
run_rea() {
  env -i PATH="$PATH" HOME="$work_dir/home" XDG_CONFIG_HOME="$work_dir/cfg" XDG_DATA_HOME="$work_dir/data" XDG_CACHE_HOME="$work_dir/cache" XDG_STATE_HOME="$work_dir/state" TMPDIR="$work_dir/tmp" REA_LOG_LEVEL=silent REA_UNBLOB_COMMAND="$unblob_command" node scripts/rea.mjs "$@"
}
run_rea extract-firmware "$work_dir/sample.bin" "$work_dir/existing" --json > "$work_dir/existing.json"
printf 'existing exit: %s\n' "$?"
run_rea extract-firmware "$work_dir/sample.bin" "$work_dir/missing-parent/child" --json > "$work_dir/missing-parent.json"
printf 'missing-parent exit: %s\n' "$?"
run_rea extract-firmware "$work_dir/sample.bin" "$work_dir/valid-output" --json > "$work_dir/valid.json"
printf 'valid exit: %s\n' "$?"
run_rea extract-firmware "$work_dir/sample.bin" "$work_dir/range-output" --offset 99999 --length 1 --json > "$work_dir/range.json"
printf 'range exit: %s\n' "$?"
cat "$work_dir/existing.json" "$work_dir/missing-parent.json" "$work_dir/range.json"
python3 - "$work_dir" <<'PY'
import json, pathlib, sys
root = pathlib.Path(sys.argv[1])
result = json.loads((root / "valid.json").read_text())["normalized_result"]
print("valid coverage:", result["coverage"])
children = list((root / "valid-output").rglob("gzip.uncompressed"))
assert len(children) == 1
assert children[0].read_bytes() == (root / "expected.bin").read_bytes()
assert list((root / "existing").iterdir()) == []
assert not (root / "missing-parent").exists()
print("decompressed payload matches; refused destinations untouched")
PY

Expected behavior

Identify output_directory as invalid caller input and explain the failed precondition. The input schema and firmware guide require an absolute, absent output directory with an existing parent. Keep refusing to overwrite existing output.

The same tool's out-of-file range control returns the existing caller-input outcome: invalid_request / invalid_input, a field issue, and inspect_input_contract recovery.

Impact and scope

P2: deterministic destination mistakes receive provider attribution and provider-diagnosis guidance while losing machine-readable argument correction. A safe verified workaround is an absent directory beneath an existing parent: the same fixture then extracts completely. Existing output remained empty, the missing parent remained absent, and no data loss or security-boundary crossing was observed. CLI was exercised; MCP extraction was not run. Natural incidence and extraction-time cost were not measured.

REA version

rea-agents 6.7.0, independently verified at source baseline 9d8e7803326a382cbbb3ffdb900f5e93e5442d68. Submission-time main recheck: d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f, rebuilt with npm run build:cached and replayed on Linux x64; both destination failures persisted and the successful extraction and invalid-range controls retained their expected outcomes.

Runtime and platform

Reproduction: Node.js v24.22.0, Linux x86_64 in a disposable linux/amd64 container, real Unblob 26.6.4, and 7z from p7zip-full. HOME, XDG directories, and TMPDIR were isolated. Submission host only: node --version v26.12.0; sw_vers -productVersion 27.0.1; uname -m arm64. Firmware reproduction ran only on Linux, without overriding the platform gate.

Target and artifact context

Generated 63-byte input: 16 zero bytes, deterministic gzip of b"payload\n" * 20, then 16 zero bytes. The 160-byte decompressed control payload has SHA-256 b37575775cf770caffe28d36517f898727b1c9d35c4fafd9bc33ae73f2474314. No extracted program was executed.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

Both invalid destinations exited 1. Selected fields from their JSON errors:

code: execution_failure
category: execution_failure
retryable: false
details.provider_id: unblob
existing directory, details.diagnostics.reason: Extraction output root already exists
missing parent, details.diagnostics.reason: Extraction output parent is unavailable

Both omit recovery and field issues. Their message/remediation says: Analysis could not complete. Run rea doctor and review the reported details; repeating the unchanged request is not expected to succeed. (Backticks around the command omitted here.) The valid destination exits 0 with complete coverage, no extraction gaps or diagnostics, and exact payload bytes. The range control exits 1 with invalid_request / invalid_input, an issue naming range, and recovery: {"kind":"inspect_input_contract","operation":"extract_firmware"}.

Evidence provenance: observed; independently reproduced by the verifier and replayed by the submitter on the main revision above. Source ordering below establishes that destination refusal follows extraction and inventory; it is not a measured timing result.

Typed outcome or recovery expected

Use invalid_request / invalid_input, an issue identifying output_directory, and input-contract recovery explaining an absent destination or existing parent. Preserve the safe refusal and successful extraction control. A proposed restoration check should also preserve partial Evidence with a named dependency gap when an extractor such as 7z is missing; the verifier observed that documented behavior before installing 7z.

Analysis or suspected cause

Source-derived reasoning: FirmwareAnalysisService.ts:38-53 validates schema syntax before dispatch. FirmwareProvider.ts:429-506 checks absolute syntax, runs Unblob and inventories staging before publication. Publication calls SafeOutputTree.create; SafeOutputTree.ts:107-128 rejects these destinations with ArtifactReaderFailure. FirmwareFailures.ts:135-140 converts those failures into the Unblob provider-adapter error. No bundled dependency is identified as the cause.

Additional context

Related #4619 concerns snapshot growth past the input allowance; #5402 concerns missing range members; #1855 concerns contradictory retry advice for genuine execution failures. Those reports do not cover destination admission and its provider attribution. #5659 and open #5672 concern source-stream errors during publication setup; #5672 explicitly leaves destination admission unchanged. Prepared with AI assistance using an independently verified draft. Last known good version is unknown.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5746 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `extract-firmware` attributes two documented destination-precondition failures to Unblob: an existing output directory and a destination beneath a missing parent both return `execution_failure` / `execution_failure`, `retryable: false`, and `details.provider_id: "unblob"`. Both omit field issues and input-contract recovery, and recommend `rea doctor`. The independent verifier reproduced both with real Unblob. An absent destination beneath an existing parent extracted the same fixture successfully with complete coverage and byte-identical decompressed output. Existing output was safely refused; this report concerns classification and recovery, not overwriting. ### Reproduction From the built source checkout with locked dependencies on Linux x64, provide Unblob 26.6.4, `7z`, util-linux `prlimit`, procps-compatible `ps`, and Python 3. The following generates the verifier's fixture and exercises both failures and two controls. Run without shell `errexit`, since refusal commands intentionally exit 1. ```sh work_dir="$(mktemp -d)" mkdir -p "$work_dir/home" "$work_dir/cfg" "$work_dir/data" "$work_dir/cache" "$work_dir/state" "$work_dir/tmp" "$work_dir/existing" unblob_command="$(command -v unblob)" python3 - "$work_dir" <<'PY' import gzip, pathlib, sys root = pathlib.Path(sys.argv[1]) payload = b"payload\n" * 20 blob = gzip.compress(payload, compresslevel=9, mtime=0) (root / "sample.bin").write_bytes(b"\0" * 16 + blob + b"\0" * 16) (root / "expected.bin").write_bytes(payload) PY run_rea() { env -i PATH="$PATH" HOME="$work_dir/home" XDG_CONFIG_HOME="$work_dir/cfg" XDG_DATA_HOME="$work_dir/data" XDG_CACHE_HOME="$work_dir/cache" XDG_STATE_HOME="$work_dir/state" TMPDIR="$work_dir/tmp" REA_LOG_LEVEL=silent REA_UNBLOB_COMMAND="$unblob_command" node scripts/rea.mjs "$@" } run_rea extract-firmware "$work_dir/sample.bin" "$work_dir/existing" --json > "$work_dir/existing.json" printf 'existing exit: %s\n' "$?" run_rea extract-firmware "$work_dir/sample.bin" "$work_dir/missing-parent/child" --json > "$work_dir/missing-parent.json" printf 'missing-parent exit: %s\n' "$?" run_rea extract-firmware "$work_dir/sample.bin" "$work_dir/valid-output" --json > "$work_dir/valid.json" printf 'valid exit: %s\n' "$?" run_rea extract-firmware "$work_dir/sample.bin" "$work_dir/range-output" --offset 99999 --length 1 --json > "$work_dir/range.json" printf 'range exit: %s\n' "$?" cat "$work_dir/existing.json" "$work_dir/missing-parent.json" "$work_dir/range.json" python3 - "$work_dir" <<'PY' import json, pathlib, sys root = pathlib.Path(sys.argv[1]) result = json.loads((root / "valid.json").read_text())["normalized_result"] print("valid coverage:", result["coverage"]) children = list((root / "valid-output").rglob("gzip.uncompressed")) assert len(children) == 1 assert children[0].read_bytes() == (root / "expected.bin").read_bytes() assert list((root / "existing").iterdir()) == [] assert not (root / "missing-parent").exists() print("decompressed payload matches; refused destinations untouched") PY ``` ### Expected behavior Identify `output_directory` as invalid caller input and explain the failed precondition. The [input schema](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/domain/firmware/firmwareAnalysis.ts#L19-L20) and [firmware guide](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/docs/firmware-analysis.md#L127-L132) require an absolute, absent output directory with an existing parent. Keep refusing to overwrite existing output. The same tool's out-of-file range control returns the existing caller-input outcome: `invalid_request` / `invalid_input`, a field issue, and `inspect_input_contract` recovery. ### Impact and scope P2: deterministic destination mistakes receive provider attribution and provider-diagnosis guidance while losing machine-readable argument correction. A safe verified workaround is an absent directory beneath an existing parent: the same fixture then extracts completely. Existing output remained empty, the missing parent remained absent, and no data loss or security-boundary crossing was observed. CLI was exercised; MCP extraction was not run. Natural incidence and extraction-time cost were not measured. ### REA version `rea-agents` 6.7.0, independently verified at source baseline `9d8e7803326a382cbbb3ffdb900f5e93e5442d68`. Submission-time main recheck: `d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f`, rebuilt with `npm run build:cached` and replayed on Linux x64; both destination failures persisted and the successful extraction and invalid-range controls retained their expected outcomes. ### Runtime and platform Reproduction: Node.js v24.22.0, Linux x86_64 in a disposable linux/amd64 container, real Unblob 26.6.4, and `7z` from p7zip-full. HOME, XDG directories, and TMPDIR were isolated. Submission host only: `node --version` v26.12.0; `sw_vers -productVersion` 27.0.1; `uname -m` arm64. Firmware reproduction ran only on Linux, without overriding the platform gate. ### Target and artifact context Generated 63-byte input: 16 zero bytes, deterministic gzip of `b"payload\n" * 20`, then 16 zero bytes. The 160-byte decompressed control payload has SHA-256 `b37575775cf770caffe28d36517f898727b1c9d35c4fafd9bc33ae73f2474314`. No extracted program was executed. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence Both invalid destinations exited 1. Selected fields from their JSON errors: ```text code: execution_failure category: execution_failure retryable: false details.provider_id: unblob existing directory, details.diagnostics.reason: Extraction output root already exists missing parent, details.diagnostics.reason: Extraction output parent is unavailable ``` Both omit `recovery` and field issues. Their message/remediation says: `Analysis could not complete. Run rea doctor and review the reported details; repeating the unchanged request is not expected to succeed.` (Backticks around the command omitted here.) The valid destination exits 0 with complete coverage, no extraction gaps or diagnostics, and exact payload bytes. The range control exits 1 with `invalid_request` / `invalid_input`, an issue naming `range`, and `recovery: {"kind":"inspect_input_contract","operation":"extract_firmware"}`. Evidence provenance: observed; independently reproduced by the verifier and replayed by the submitter on the main revision above. Source ordering below establishes that destination refusal follows extraction and inventory; it is not a measured timing result. ### Typed outcome or recovery expected Use `invalid_request` / `invalid_input`, an issue identifying `output_directory`, and input-contract recovery explaining an absent destination or existing parent. Preserve the safe refusal and successful extraction control. A proposed restoration check should also preserve partial Evidence with a named dependency gap when an extractor such as `7z` is missing; the verifier observed that documented behavior before installing `7z`. ### Analysis or suspected cause Source-derived reasoning: [FirmwareAnalysisService.ts:38-53](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/application/firmware/FirmwareAnalysisService.ts#L38-L53) validates schema syntax before dispatch. [FirmwareProvider.ts:429-506](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/firmware/FirmwareProvider.ts#L429-L506) checks absolute syntax, runs Unblob and inventories staging before publication. Publication calls `SafeOutputTree.create`; [SafeOutputTree.ts:107-128](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/artifacts/SafeOutputTree.ts#L107-L128) rejects these destinations with `ArtifactReaderFailure`. [FirmwareFailures.ts:135-140](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/firmware/FirmwareFailures.ts#L135-L140) converts those failures into the Unblob provider-adapter error. No bundled dependency is identified as the cause. ### Additional context Related #4619 concerns snapshot growth past the input allowance; #5402 concerns missing range members; #1855 concerns contradictory retry advice for genuine execution failures. Those reports do not cover destination admission and its provider attribution. #5659 and open #5672 concern source-stream errors during publication setup; #5672 explicitly leaves destination admission unchanged. Prepared with AI assistance using an independently verified draft. Last known good version is unknown.
Author
Owner

@XinwangTao commented on GitHub (Oct 21, 2026):

This is a failure-classification bug rather than an execution one — Unblob never ran in either failing case, so blaming provider_id unblob and routing the user to rea doctor hides the actual input-contract violation (output path preconditions). The verifier's controls make the boundary crisp: the same fixture extracts cleanly to a valid destination, so the existing-output and missing-parent cases are purely destination-validation failures that should surface as a distinct precondition error with the offending path in field issues. One suggestion for the fix: check the destination before invoking Unblob (SafeOutputTree is already doing the validation per the links), and emit the precondition error with a corrective action (choose an empty/nonexistent path or create the parent) instead of delegating to doctor.

<!-- gh-comment-id:6311359111 --> @XinwangTao commented on GitHub (Oct 21, 2026): This is a failure-classification bug rather than an execution one — Unblob never ran in either failing case, so blaming provider_id unblob and routing the user to rea doctor hides the actual input-contract violation (output path preconditions). The verifier's controls make the boundary crisp: the same fixture extracts cleanly to a valid destination, so the existing-output and missing-parent cases are purely destination-validation failures that should surface as a distinct precondition error with the offending path in field issues. One suggestion for the fix: check the destination before invoking Unblob (SafeOutputTree is already doing the validation per the links), and emit the precondition error with a corrective action (choose an empty/nonexistent path or create the parent) instead of delegating to doctor.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

FirmwareProvider now checks destination admission before selecting an engine, maps typed SafeOutputTree destination refusals to AnalysisInputError on output_directory, and firmwareFailure preserves existing AnalysisError values. Shared projection supplies invalid_request/input-contract recovery; publication also applies the same mapping for a destination that becomes refused later, while cleanup-bearing failures retain artifact/cleanup semantics. The parameterized service regression uses existing output, missing parent and relative output, explicitly expects AnalysisInputError/output_directory and no engine launch; the exclusive-acquisition test verifies preflight creates nothing and later create still refuses occupied output.

Implementation: FirmwareProvider.ts, SafeOutputTreeAdmission.ts.

The relevant regression checks in firmwarePublicationPaths.test.ts, SafeOutputTreeAdmission.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380694243 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. FirmwareProvider now checks destination admission before selecting an engine, maps typed SafeOutputTree destination refusals to AnalysisInputError on output_directory, and firmwareFailure preserves existing AnalysisError values. Shared projection supplies invalid_request/input-contract recovery; publication also applies the same mapping for a destination that becomes refused later, while cleanup-bearing failures retain artifact/cleanup semantics. The parameterized service regression uses existing output, missing parent and relative output, explicitly expects AnalysisInputError/output_directory and no engine launch; the exclusive-acquisition test verifies preflight creates nothing and later create still refuses occupied output. Implementation: [FirmwareProvider.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/firmware/FirmwareProvider.ts#L349), [SafeOutputTreeAdmission.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/artifacts/SafeOutputTreeAdmission.ts#L24). The relevant regression checks in [firmwarePublicationPaths.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/firmware/firmwarePublicationPaths.test.ts), [SafeOutputTreeAdmission.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/artifacts/SafeOutputTreeAdmission.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3492
No description provided.