diff --git a/src/artifacts/javascript/JavaScriptArtifactFiles.ts b/src/artifacts/javascript/JavaScriptArtifactFiles.ts index e3849f814..78d8cf750 100644 --- a/src/artifacts/javascript/JavaScriptArtifactFiles.ts +++ b/src/artifacts/javascript/JavaScriptArtifactFiles.ts @@ -37,6 +37,7 @@ interface ExpectedFile { interface ExpectedContainer { readonly hash_status: ArtifactOccurrence["hash_status"]; + readonly format: ArtifactOccurrence["artifact_format"]; readonly inventory: JavaScriptArtifactContainer | null; } @@ -219,6 +220,9 @@ const visitReader = async ( "integrity", `Nested ASAR was not integrity-verified: ${path}`, ); + // Inventory expands an ASAR-named file only when its bytes are an + // ASAR. One it recorded as ordinary bytes has no members to read. + if (container.format !== "asar") continue; const inventory = container.inventory; if (inventory === null) throw new ArtifactReaderFailure( @@ -395,6 +399,7 @@ const expectedInventory = ( if (isNestedAsar) containers.set(occurrence.logical_path, { hash_status: occurrence.hash_status, + format: occurrence.artifact_format, inventory: null, }); continue; @@ -412,6 +417,7 @@ const expectedInventory = ( if (isNestedAsar) containers.set(occurrence.logical_path, { hash_status: occurrence.hash_status, + format: occurrence.artifact_format, inventory: { path: occurrence.logical_path, sha256: node.sha256, diff --git a/tests/boundary/filesystem/javascriptIntegrityPolicy.test.ts b/tests/boundary/filesystem/javascriptIntegrityPolicy.test.ts index 47b77114a..67b167bdf 100644 --- a/tests/boundary/filesystem/javascriptIntegrityPolicy.test.ts +++ b/tests/boundary/filesystem/javascriptIntegrityPolicy.test.ts @@ -105,6 +105,34 @@ it("keeps a contradicted nested ASAR opaque during JavaScript reconstruction", a expect(result.graph.coverage.omitted_count).toBeNull(); }); +it("keeps an ASAR-named file without an ASAR header opaque during JavaScript reconstruction", async () => { + const root = await createTestTempDirectory("rea-opaque-asar-name-"); + await writeFile(join(root, "main.js"), "export const outer = true;\n"); + // Inventory records this file as ordinary bytes and does not expand it. + await writeFile(join(root, "ordinary.asar"), "ordinary bytes"); + + const snapshot = await scanCanonicalArtifactInventory(root, {}); + const reader = createJavaScriptArtifactReader(root, "directory"); + try { + const read = await readJavaScriptArtifactFiles(reader, snapshot); + expect(read.files.map(({ path }) => path)).toEqual(["main.js"]); + expect(read.containers).toEqual([]); + } finally { + await reader.close(); + } + + const analyzed = await analyzeJavaScriptApplication({ + input_path: root, + format: "directory", + }); + if (!analyzed.ok) throw analyzed.error; + const result = javascriptApplicationAnalysisResultSchema.parse( + analyzed.value.normalized_result, + ); + expect(result.statistics.nested_asar_containers).toBe(0); + expect(result.integrity_contradictions).toEqual([]); +}); + it("retains semantic resource limits alongside recorded integrity contradictions", async () => { const { archive } = await createStrippedAsarAddon([ { path: "app.js", contents: primitiveCandidateExpansionSource() },