diff --git a/src/artifacts/ReleaseExposureReader.test.ts b/src/artifacts/ReleaseExposureReader.test.ts index 91e0849c0a..5a37ae887c 100644 --- a/src/artifacts/ReleaseExposureReader.test.ts +++ b/src/artifacts/ReleaseExposureReader.test.ts @@ -1260,6 +1260,34 @@ describe("ReleaseExposureReader member allowances", () => { }); }); +describe("ReleaseExposureReader nested packages in a host", () => { + it("leaves coverage unknown for an ASAR a PyInstaller host carries", async () => { + const root = await createTestTempDirectory("rea-release-exposure-nest-"); + await writeFile( + join(root, "app.exe"), + cArchiveExecutable({ + carrier: "pe", + layout: "2.1", + pythonVersion: 310, + members: [ + { + name: "resources", + typeCode: "x", + compress: true, + payload: asarArchive([ + { name: "app.js.map", bytes: Buffer.from("{}") }, + ]), + }, + ], + }), + ); + const scanned = await inspect(root); + expect(scanned.coverage).toBe("unknown"); + expect(scanned.extractable.formats).toContain("asar"); + expect(scanned.limitations.join("\n")).toContain("was not expanded"); + }); +}); + describe("ReleaseExposureReader source-map count", () => { it.each([ [ diff --git a/src/artifacts/ReleaseExposureReader.ts b/src/artifacts/ReleaseExposureReader.ts index 02916e16c6..0e41e89670 100644 --- a/src/artifacts/ReleaseExposureReader.ts +++ b/src/artifacts/ReleaseExposureReader.ts @@ -27,6 +27,7 @@ import { } from "./ArtifactFileOperation.js"; import { decodeBinaryPlistText } from "./apple/BinaryPlistMaterializer.js"; import { ArtifactEmbeddedFormatProbes } from "./ArtifactEmbeddedFormatProbes.js"; +import { createAsarHeaderSignatureProbe } from "./AsarHeader.js"; import { ArtifactTraversalObservation } from "./ArtifactTraversalObservation.js"; import { classifyEmbeddedExecutablePackage } from "./inventory/classify.js"; import { inventoryArtifact } from "./inventory/ArtifactInventory.js"; @@ -546,13 +547,11 @@ const scanOccurrences = async ( packageKind !== null && !parentsWithChildren.has(occurrence.occurrence_id) ) { - state.formats.add(packageKind); - noteUnexaminedPackage(state); + noteNestedPackage(state, packageKind, location); if (!occurrence.encrypted) { state.readable += 1; pushSample(state.memberSamples, location); } - pushLimitation(state, `Nested package ${location} was not expanded.`); continue; } await examineMember(state, { @@ -1491,6 +1490,10 @@ const examinePackagedBinary = async ( notePeNotExamined(state, member.location); } } + if (native === null && header !== null && holdsAsarHeader(header)) { + noteNestedPackage(state, "asar", member.location); + return true; + } if (native !== null) state.packagedNative += 1; else await examineProbedText(state, member, probe); if (native !== "pe") return true; @@ -1841,6 +1844,20 @@ const noteUnexaminedPackage = (state: ScanState): void => { state.coverageUnknown = true; }; +const noteNestedPackage = ( + state: ScanState, + format: PackageFormat, + location: string, +): void => { + state.formats.add(format); + noteUnexaminedPackage(state); + pushLimitation(state, `Nested package ${location} was not expanded.`); +}; + +/** The ASAR owner's signature test. A match does not establish a valid archive. */ +const holdsAsarHeader = (bytes: Buffer): boolean => + createAsarHeaderSignatureProbe().write(bytes).state === "matched"; + const readListedEntry = async ( container: OpenContainer | undefined, entry: ArtifactEntry,