[GH-ISSUE #7433] [Bug] APK signing inspection ignores classic ZIP disk metadata #4440

Open
opened 2026-10-31 00:49:48 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

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

Area

Native analysis or decompilation

Observed behavior

The source CLI accepts a selected ZIP whose classic end-of-central-directory record identifies the current disk as 1 and the central-directory disk as 0. inspect-apk-signature returns exit 0 and observed Evidence with all signing schemes absent and strongest_scheme: "none". Its limitations do not disclose the disk mismatch. Running inspect-artifact on that same file returns exit 1 and identifies an unsupported split ZIP. The unchanged single-disk control succeeds in signing inspection.

Reproduction

Use Node.js 24.22.1 and a fresh checkout at commit f8f7ac96de8a66a4ab9a23d87e04779bfcd11045, with locked dependencies installed using npm ci. Run the following from the checkout root. It creates an unsigned two-entry ZIP, changes only the current-disk field, and invokes the production CLI registration through REA's source loader. This avoids requiring generated runtime files. The verifier ran this source CLI path, not the compiled package dispatcher.

work="$(mktemp -d)"
mkdir -p "$work/home"
FIXTURE_DIR="$work" python3 - <<'PY'
import os, struct, zipfile
from pathlib import Path
root = Path(os.environ['FIXTURE_DIR'])
with zipfile.ZipFile(root / 'single.zip', 'w') as archive:
    archive.writestr('classes.dex', b'dex\n035\0' + b'A' * 100)
    archive.writestr('AndroidManifest.xml', b'<manifest/>')
data = bytearray((root / 'single.zip').read_bytes())
eocd = data.rfind(b'PK\x05\x06')
struct.pack_into('<H', data, eocd + 4, 1)
(root / 'split.zip').write_bytes(data)
print('EOCD offset:', eocd, 'disk:', 1, 'directory disk:', 0)
PY
cat > "$work/cli-source.mjs" <<'JS'
import { pathToFileURL } from 'node:url';
const root = process.cwd();
const { importVerifierSource } = await import(
  pathToFileURL(`${root}/scripts/lib/verifier-source-loader.mjs`)
);
const { createCli, closeCliJavaScriptResources } = await importVerifierSource(
  pathToFileURL(`${root}/src/cli.ts`).href, import.meta.url
);
try {
  await createCli(process.env).serve(process.argv.slice(2), {
    stdout: (text) => process.stdout.write(text),
    exit: (code) => { process.exitCode = code; },
  });
} finally {
  await closeCliJavaScriptResources();
}
JS
HOME="$work/home" node "$work/cli-source.mjs" inspect-apk-signature "$work/split.zip" --json
printf 'signing exit: %s\n' "$?"
HOME="$work/home" node "$work/cli-source.mjs" inspect-artifact "$work/split.zip" --json
printf 'inventory exit: %s\n' "$?"
HOME="$work/home" node "$work/cli-source.mjs" inspect-apk-signature "$work/single.zip" --json
printf 'control exit: %s\n' "$?"

Observed twice for each split-input command and twice for the control, including a replay of this reproduction script. The signing and control exits are 0; the inventory exit is 1. The fixture is 353 bytes, with its EOCD at byte 331. No multi-volume ZIP set or signed APK is required to demonstrate this unchecked metadata.

Expected behavior

Refuse classic ZIP metadata that requires resolving a directory on another disk, or preserve that unresolved structure explicitly instead of returning definite scheme absence. ZIP input is supported by docs/apk-signature.md:3. The unknown-versus-absent invariant is stated in docs/specs/apk-signature/spec.md:71. REA already recognizes that multi-disk offsets cannot be interpreted as local spans for Zip64 in src/domain/apkSignature/apkSigningBlock.ts:140, and its inventory reader classifies split ZIPs as unavailable in src/artifacts/ZipArtifactReader.ts:476. The Zip64 documentation supplies precedent; it does not explicitly specify classic EOCD handling.

Impact and scope

P2: this is a bounded analysis-correctness defect. A caller receives successful, observed signing Evidence from a file whose directory placement the reader has not resolved. The demonstrated practical preflight is inspect-artifact, which rejects the same file. The reproduction establishes acceptance of inconsistent split-declaring metadata, not an actual omitted signer or unsigned classification of a real signed APK. Frequency on real inputs is unknown. No Android execution, signature verification, authenticity decision, or security boundary crossing was demonstrated.

REA version

6.8.0 from package.json; baseline commit f8f7ac96de.

Runtime and platform

Node.js v24.22.1; macOS / Darwin 27.0.0; arm64. No Android SDK or external analysis engine was used.

Target and artifact context

Synthetic ZIP with stored classes.dex and AndroidManifest.xml entries. Its classic EOCD declares current disk 1 and directory disk 0, while the original entries, directory bytes, counts and offsets remain in the single selected file. This is deliberately inconsistent metadata rather than a genuine assembled split archive.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The source CLI's signing response contains these selected fields:

{
  "operation": "inspect_apk_signature",
  "normalized_result": {
    "schemes": {
      "v1": {"present": false},
      "v2": {"present": false},
      "v3": {"present": false},
      "v3.1": {"present": false}
    },
    "posture": {"strongest_scheme": "none"}
  },
  "confidence": "observed"
}

The inventory response on identical bytes contains:

{
  "code": "artifact_operation_failed",
  "category": "unavailable",
  "details": {
    "operation": "inspect_artifact",
    "reason": "unavailable",
    "detail": "ZIP archive uses an unsupported feature: Split zip file"
  }
}

Source-derived cause: src/domain/zipEndOfCentralDirectory.ts:38 exposes the disk fields, but the signing parser uses the candidate without checking them at src/domain/apkSignature/apkSigningBlock.ts:215. Its nonsaturated classic-offset path returns the offset directly at src/domain/apkSignature/apkSigningBlock.ts:123. The production provider uses that tail at src/apkSignature/ApkSignatureProvider.ts:408. The source CLI reproduction exercises the real registered command and production service/provider, with no mocks.

Typed outcome or recovery expected

An unsupported-target or malformed-input outcome explaining the classic EOCD disk inconsistency, without a complete signing report asserting absent schemes.

Additional context

Compiled-dispatcher behavior, genuine split ZIP sets and real signed-APK consequences were not tested. Related, distinct: #5072 (closed) added read-only inventory of split-APK bundles.

Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7433 ### 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 Native analysis or decompilation ### Observed behavior The source CLI accepts a selected ZIP whose classic end-of-central-directory record identifies the current disk as 1 and the central-directory disk as 0. `inspect-apk-signature` returns exit 0 and observed Evidence with all signing schemes absent and `strongest_scheme: "none"`. Its limitations do not disclose the disk mismatch. Running `inspect-artifact` on that same file returns exit 1 and identifies an unsupported split ZIP. The unchanged single-disk control succeeds in signing inspection. ### Reproduction Use Node.js 24.22.1 and a fresh checkout at commit `f8f7ac96de8a66a4ab9a23d87e04779bfcd11045`, with locked dependencies installed using `npm ci`. Run the following from the checkout root. It creates an unsigned two-entry ZIP, changes only the current-disk field, and invokes the production CLI registration through REA's source loader. This avoids requiring generated runtime files. The verifier ran this source CLI path, not the compiled package dispatcher. ```sh work="$(mktemp -d)" mkdir -p "$work/home" FIXTURE_DIR="$work" python3 - <<'PY' import os, struct, zipfile from pathlib import Path root = Path(os.environ['FIXTURE_DIR']) with zipfile.ZipFile(root / 'single.zip', 'w') as archive: archive.writestr('classes.dex', b'dex\n035\0' + b'A' * 100) archive.writestr('AndroidManifest.xml', b'<manifest/>') data = bytearray((root / 'single.zip').read_bytes()) eocd = data.rfind(b'PK\x05\x06') struct.pack_into('<H', data, eocd + 4, 1) (root / 'split.zip').write_bytes(data) print('EOCD offset:', eocd, 'disk:', 1, 'directory disk:', 0) PY cat > "$work/cli-source.mjs" <<'JS' import { pathToFileURL } from 'node:url'; const root = process.cwd(); const { importVerifierSource } = await import( pathToFileURL(`${root}/scripts/lib/verifier-source-loader.mjs`) ); const { createCli, closeCliJavaScriptResources } = await importVerifierSource( pathToFileURL(`${root}/src/cli.ts`).href, import.meta.url ); try { await createCli(process.env).serve(process.argv.slice(2), { stdout: (text) => process.stdout.write(text), exit: (code) => { process.exitCode = code; }, }); } finally { await closeCliJavaScriptResources(); } JS HOME="$work/home" node "$work/cli-source.mjs" inspect-apk-signature "$work/split.zip" --json printf 'signing exit: %s\n' "$?" HOME="$work/home" node "$work/cli-source.mjs" inspect-artifact "$work/split.zip" --json printf 'inventory exit: %s\n' "$?" HOME="$work/home" node "$work/cli-source.mjs" inspect-apk-signature "$work/single.zip" --json printf 'control exit: %s\n' "$?" ``` Observed twice for each split-input command and twice for the control, including a replay of this reproduction script. The signing and control exits are 0; the inventory exit is 1. The fixture is 353 bytes, with its EOCD at byte 331. No multi-volume ZIP set or signed APK is required to demonstrate this unchecked metadata. ### Expected behavior Refuse classic ZIP metadata that requires resolving a directory on another disk, or preserve that unresolved structure explicitly instead of returning definite scheme absence. ZIP input is supported by `docs/apk-signature.md:3`. The unknown-versus-absent invariant is stated in `docs/specs/apk-signature/spec.md:71`. REA already recognizes that multi-disk offsets cannot be interpreted as local spans for Zip64 in `src/domain/apkSignature/apkSigningBlock.ts:140`, and its inventory reader classifies split ZIPs as unavailable in `src/artifacts/ZipArtifactReader.ts:476`. The Zip64 documentation supplies precedent; it does not explicitly specify classic EOCD handling. ### Impact and scope P2: this is a bounded analysis-correctness defect. A caller receives successful, observed signing Evidence from a file whose directory placement the reader has not resolved. The demonstrated practical preflight is `inspect-artifact`, which rejects the same file. The reproduction establishes acceptance of inconsistent split-declaring metadata, not an actual omitted signer or unsigned classification of a real signed APK. Frequency on real inputs is unknown. No Android execution, signature verification, authenticity decision, or security boundary crossing was demonstrated. ### REA version 6.8.0 from package.json; baseline commit f8f7ac96de8a66a4ab9a23d87e04779bfcd11045. ### Runtime and platform Node.js v24.22.1; macOS / Darwin 27.0.0; arm64. No Android SDK or external analysis engine was used. ### Target and artifact context Synthetic ZIP with stored classes.dex and AndroidManifest.xml entries. Its classic EOCD declares current disk 1 and directory disk 0, while the original entries, directory bytes, counts and offsets remain in the single selected file. This is deliberately inconsistent metadata rather than a genuine assembled split archive. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The source CLI's signing response contains these selected fields: ```json { "operation": "inspect_apk_signature", "normalized_result": { "schemes": { "v1": {"present": false}, "v2": {"present": false}, "v3": {"present": false}, "v3.1": {"present": false} }, "posture": {"strongest_scheme": "none"} }, "confidence": "observed" } ``` The inventory response on identical bytes contains: ```json { "code": "artifact_operation_failed", "category": "unavailable", "details": { "operation": "inspect_artifact", "reason": "unavailable", "detail": "ZIP archive uses an unsupported feature: Split zip file" } } ``` Source-derived cause: `src/domain/zipEndOfCentralDirectory.ts:38` exposes the disk fields, but the signing parser uses the candidate without checking them at `src/domain/apkSignature/apkSigningBlock.ts:215`. Its nonsaturated classic-offset path returns the offset directly at `src/domain/apkSignature/apkSigningBlock.ts:123`. The production provider uses that tail at `src/apkSignature/ApkSignatureProvider.ts:408`. The source CLI reproduction exercises the real registered command and production service/provider, with no mocks. ### Typed outcome or recovery expected An unsupported-target or malformed-input outcome explaining the classic EOCD disk inconsistency, without a complete signing report asserting absent schemes. ### Additional context Compiled-dispatcher behavior, genuine split ZIP sets and real signed-APK consequences were not tested. Related, distinct: #5072 (closed) added read-only inventory of split-APK bundles.
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#4440
No description provided.