From 08168e868f024e4728044b719985589f4e2aaa04 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Tue, 3 Nov 2026 21:24:49 -0800 Subject: [PATCH] fix(audit): keep coverage unknown for an unread unclassified member A packaged member with no format of its own, such as a PyInstaller payload.bin, recorded only a skipped text member when its read failed before the header. Member coverage then stayed complete, though the member could be a native image or carry a nested package. Leave coverage unknown and say which member could not be read, as an unread native member already does. --- src/artifacts/ReleaseExposureReader.test.ts | 24 +++++++++++++++++++++ src/artifacts/ReleaseExposureReader.ts | 16 ++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/artifacts/ReleaseExposureReader.test.ts b/src/artifacts/ReleaseExposureReader.test.ts index 0c1a1cf60d..e40fb0f36b 100644 --- a/src/artifacts/ReleaseExposureReader.test.ts +++ b/src/artifacts/ReleaseExposureReader.test.ts @@ -2482,6 +2482,30 @@ describe("ReleaseExposureReader unexpanded package PE reads", () => { } }); + it("leaves member coverage unknown when an unclassified packaged member cannot be read", async () => { + const root = await bundled([ + { path: "payload.bin", bytes: Buffer.from([0x7f, 0x45, 0x4c, 0x46]) }, + ]); + createdReaders.wrap = (reader, path) => { + if (!path.endsWith("app.dll")) return reader; + const open = reader.open.bind(reader); + reader.open = (entry, signal) => + entry.path.endsWith("payload.bin") + ? Promise.reject(new Error("member read failed")) + : open(entry, signal); + return reader; + }; + try { + const scanned = await inspect(root); + expect(scanned.coverage).toBe("unknown"); + expect(scanned.limitations).toContain( + `Could not read ${join(root, "app.dll", "payload.bin")} to rule out a native image or package.`, + ); + } finally { + createdReaders.wrap = undefined; + } + }); + it("leaves member coverage unknown when a package cannot be listed", async () => { const root = await bundled([{ path: "app.js", bytes: Buffer.from("1") }]); createdReaders.wrap = (reader, path) => { diff --git a/src/artifacts/ReleaseExposureReader.ts b/src/artifacts/ReleaseExposureReader.ts index 116196e749..235d78e3c3 100644 --- a/src/artifacts/ReleaseExposureReader.ts +++ b/src/artifacts/ReleaseExposureReader.ts @@ -1011,9 +1011,10 @@ const noteUnreadMember = (state: ScanState, member: MemberView): void => { /** * A member whose bytes were not read. A native one may carry a package that * nothing here can clear, so member coverage stays unknown, as it does for a - * packaged image the embedded-package probe could not read. A caller that - * counts the failed read itself passes `countUnreadable: false`, so the member - * is counted once. + * packaged image the embedded-package probe could not read. An unclassified + * one whose header was never read may be such an image too, so it leaves + * coverage unknown as well. A caller that counts the failed read itself passes + * `countUnreadable: false`, so the member is counted once. */ const noteUnreadBinary = ( state: ScanState, @@ -1029,7 +1030,14 @@ const noteUnreadBinary = ( state, `Did not probe ${member.location} for an embedded package.`, ); - } else state.skippedText += 1; + } else { + state.skippedText += 1; + state.coverageUnknown = true; + pushLimitation( + state, + `Could not read ${member.location} to rule out a native image or package.`, + ); + } if (native === "pe" || PE_MEMBER_NAME.test(member.logical)) notePeNotExamined(state, member.location); };