[GH-ISSUE #4252] [Bug] ADB install and push report a digest for different bytes than adb consumes #1676

Closed
opened 2026-10-16 21:08:54 +00:00 by bsakaguchi · 1 comment
Owner

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

[Bug] ADB install and push report a digest for a different source than adb consumes

Area: Evidence, artifacts, or reconstruction
Interface: CLI and MCP share AdbProvider; boundary probe called the provider directly
Earliest failure stage: Capture or observation

Observed behavior

At source revision 0e1f0bf2d06943e59253b8a66923a1ae6005e81e, install_adb_package and push_adb_file hash the caller's path, then pass that mutable path to adb, which opens it later. A controlled adb stub replaced the file between those steps and consumed the replacement. Both calls returned success while reporting the digest of the earlier bytes.

The relevant code is src/adb/AdbProvider.ts:1721-1769 for push and :1846-1887 for install. Push may compare a later device-side sha256sum, but when that command is unavailable it returns success with device_sha256: null and device_digest_source: "unavailable"; it still associates the pre-push host digest with the transfer. Install has no device-side byte verification.

Reproduction

Run the focused disposable probe from the repository root:

PATH=/opt/homebrew/opt/node@24/bin:$PATH node_modules/.bin/vitest run --config .cache/codebase-audit/artifacts/vitest.audit.config.mts --reporter=verbose

The install trigger calls AdbProvider.execute({ operation: "install_adb_package", input: { serial: "device", apk_path: <temp>/app.apk, replace: false } }). The simulated adb execution boundary replaces original-apk with replacement-apk when adb receives the install command and records the digest of the bytes it consumes, then reports Success. REA reports the SHA-256 of original-apk (af90ccbb...e6637f) while the consumed bytes hash to 28b57efa...1e34fe.

The push trigger calls AdbProvider.execute({ operation: "push_adb_file", input: { serial: "device", local_path: <temp>/payload.bin, device_path: "/data/local/tmp/payload.bin", overwrite: false } }). The stub reports the target absent, replaces original-payload with replacement-payload while handling push, records the consumed digest, and makes sha256sum unavailable. REA returns success with sha256 equal to the original bytes (59ab8fdf...5cd24b), device_sha256: null, and device_digest_source: "unavailable"; the consumed replacement hashes to e0c60eba...02b6e.

Stable-path controls for both operations report the digest of the same bytes the stub consumes. The six-probe suite passed on Node v24.18.0, macOS arm64. Probe file: .cache/codebase-audit/artifacts/adbAudit.test.ts. The adb execution seam is stubbed while temporary files use the real filesystem; this establishes the production boundary's pathname handoff behavior, not a real-device transfer.

Expected behavior

Bind the bytes analyzed to the bytes consumed by adb. Pass adb an operation-owned immutable snapshot whose identity and digest were verified before handoff, and retain its cleanup owner if cleanup fails. A second pathname hash alone leaves the same replacement window. Report the digest of those exact snapshot bytes; keep device-side verification explicitly separate.

Impact and scope

Concurrent replacement of a caller-selected APK or payload can make the result's sha256 describe bytes other than those installed or pushed. For push, the mismatch is not detected when device checksum collection is unavailable. The issue is limited to upload/install source identity in the inspected ADB provider; no real Android device was contacted.

Relevant evidence and dedupe

  • Producer boundary: src/adb/AdbProvider.ts:1721-1769 and :1846-1887.
  • Probe command above passed four relevant cases: stable and replaced-path install, stable and replaced-path push. The replacement controls returned success with differing reported and consumed digests.
  • Dedupe search repo:morluto/rea ADB source digest push install surfaced open #3369, which concerns classifying checksum output overflow as device unavailability; it does not cover source-path replacement between hashing and adb consumption. Searches for capture_adb_screen cancel / ADB late cancellation found no focused report. Broad lifecycle audit #4227 is related context.
  • Stubbed provider boundary only; no actual device transfer or install was run.
Originally created by @morluto on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4252 # [Bug] ADB install and push report a digest for a different source than adb consumes **Area:** Evidence, artifacts, or reconstruction **Interface:** CLI and MCP share `AdbProvider`; boundary probe called the provider directly **Earliest failure stage:** Capture or observation ## Observed behavior At source revision `0e1f0bf2d06943e59253b8a66923a1ae6005e81e`, `install_adb_package` and `push_adb_file` hash the caller's path, then pass that mutable path to adb, which opens it later. A controlled adb stub replaced the file between those steps and consumed the replacement. Both calls returned success while reporting the digest of the earlier bytes. The relevant code is `src/adb/AdbProvider.ts:1721-1769` for push and `:1846-1887` for install. Push may compare a later device-side `sha256sum`, but when that command is unavailable it returns success with `device_sha256: null` and `device_digest_source: "unavailable"`; it still associates the pre-push host digest with the transfer. Install has no device-side byte verification. ## Reproduction Run the focused disposable probe from the repository root: ```sh PATH=/opt/homebrew/opt/node@24/bin:$PATH node_modules/.bin/vitest run --config .cache/codebase-audit/artifacts/vitest.audit.config.mts --reporter=verbose ``` The install trigger calls `AdbProvider.execute({ operation: "install_adb_package", input: { serial: "device", apk_path: <temp>/app.apk, replace: false } })`. The simulated adb execution boundary replaces `original-apk` with `replacement-apk` when adb receives the `install` command and records the digest of the bytes it consumes, then reports `Success`. REA reports the SHA-256 of `original-apk` (`af90ccbb...e6637f`) while the consumed bytes hash to `28b57efa...1e34fe`. The push trigger calls `AdbProvider.execute({ operation: "push_adb_file", input: { serial: "device", local_path: <temp>/payload.bin, device_path: "/data/local/tmp/payload.bin", overwrite: false } })`. The stub reports the target absent, replaces `original-payload` with `replacement-payload` while handling `push`, records the consumed digest, and makes `sha256sum` unavailable. REA returns success with `sha256` equal to the original bytes (`59ab8fdf...5cd24b`), `device_sha256: null`, and `device_digest_source: "unavailable"`; the consumed replacement hashes to `e0c60eba...02b6e`. Stable-path controls for both operations report the digest of the same bytes the stub consumes. The six-probe suite passed on Node `v24.18.0`, macOS arm64. Probe file: `.cache/codebase-audit/artifacts/adbAudit.test.ts`. The adb execution seam is stubbed while temporary files use the real filesystem; this establishes the production boundary's pathname handoff behavior, not a real-device transfer. ## Expected behavior Bind the bytes analyzed to the bytes consumed by adb. Pass adb an operation-owned immutable snapshot whose identity and digest were verified before handoff, and retain its cleanup owner if cleanup fails. A second pathname hash alone leaves the same replacement window. Report the digest of those exact snapshot bytes; keep device-side verification explicitly separate. ## Impact and scope Concurrent replacement of a caller-selected APK or payload can make the result's `sha256` describe bytes other than those installed or pushed. For push, the mismatch is not detected when device checksum collection is unavailable. The issue is limited to upload/install source identity in the inspected ADB provider; no real Android device was contacted. ## Relevant evidence and dedupe - Producer boundary: `src/adb/AdbProvider.ts:1721-1769` and `:1846-1887`. - Probe command above passed four relevant cases: stable and replaced-path install, stable and replaced-path push. The replacement controls returned success with differing reported and consumed digests. - Dedupe search `repo:morluto/rea ADB source digest push install` surfaced open #3369, which concerns classifying checksum output overflow as device unavailability; it does not cover source-path replacement between hashing and adb consumption. Searches for `capture_adb_screen cancel` / `ADB late cancellation` found no focused report. Broad lifecycle audit [#4227](https://github.com/morluto/rea/issues/4227) is related context. - Stubbed provider boundary only; no actual device transfer or install was run.
bsakaguchi 2026-10-16 21:08:54 +00:00
  • closed this issue
  • added the
    triage
    bug
    labels
Author
Owner

@morluto commented on GitHub (Oct 16, 2026):

Fixed on main in 1342127e9. ADB push and install now consume private snapshots whose stable digest identifies the transferred bytes, preserving caller paths and side-effect receipts.

Path-replacement controls passed through subprocess stubs and the public CLI, with capacity/cancellation/cleanup coverage. Real Android devices and Windows private-root behavior were not exercised.

Hosted main CI is still pending; this closure is based on the implemented fix and the scoped regression evidence.

<!-- gh-comment-id:6206017365 --> @morluto commented on GitHub (Oct 16, 2026): Fixed on `main` in [1342127e9](https://github.com/morluto/rea/commit/1342127e91d4db8b4113a7a4e85be909bc96e1db). ADB push and install now consume private snapshots whose stable digest identifies the transferred bytes, preserving caller paths and side-effect receipts. Path-replacement controls passed through subprocess stubs and the public CLI, with capacity/cancellation/cleanup coverage. Real Android devices and Windows private-root behavior were not exercised. Hosted main CI is still pending; this closure is based on the implemented fix and the scoped regression evidence.
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#1676
No description provided.