Files
rea/tests/boundary/filesystem/javascriptIntegrityPolicy.test.ts
T
morluto 684199ba0b fix(javascript)!: retain nested artifact readers in caller scopes
Require the caller artifact resource scope when reading JavaScript artifacts. Transfer failed nested ASAR cleanup to that scope so later work retries the same owner before admission. Preserve traversal failures and attempt every remaining iterator during unwind.

Validation: reconstruction, integrity, content identity, cancellation and nested-reader cleanup boundary checks.

BREAKING CHANGE: readJavaScriptArtifactFiles now requires ArtifactResourceScope as its third argument; the optional abort signal is fourth.
2026-10-11 05:33:15 +08:00

161 lines
6.6 KiB
TypeScript

import {
primitiveCandidateExpansionSource,
primitiveByteExpansionSource,
} from "../../fixtures/javascriptPrimitiveExpansion.js";
import { createHash } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { createPackage, createPackageWithOptions } from "@electron/asar";
import { expect, it } from "vitest";
import { analyzeJavaScriptApplication } from "../../support/javascriptApplicationScope.js";
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
import { createJavaScriptArtifactReader } from "../../../src/artifacts/javascript/JavaScriptArtifactReader.js";
import { readJavaScriptArtifactFiles } from "../../support/javascriptApplicationScope.js";
import { scanCanonicalArtifactInventory } from "../../fixtures/artifactInventory.js";
import { createStrippedAsarAddon } from "../../fixtures/strippedAsarAddon.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
it("records an explicitly continued Electron addon mismatch as untrusted evidence", async () => {
const { archive, addon, original, stripped } =
await createStrippedAsarAddon();
const rejected = await analyzeJavaScriptApplication({ input_path: archive });
if (rejected.ok) throw new Error("Expected strict integrity failure");
expect(projectAnalysisError(rejected.error)).toMatchObject({
code: "artifact_integrity_mismatch",
details: {
logical_path: addon,
declared_sha256: expect.any(String),
calculated_sha256: expect.any(String),
unpacked: true,
},
remediation: {
action: expect.stringContaining("integrity_policy=record-and-continue"),
},
});
const continued = await analyzeJavaScriptApplication({
input_path: archive,
integrity_policy: "record-and-continue",
});
if (!continued.ok) throw new Error("Expected explicit continuation");
const result = javascriptApplicationAnalysisResultSchema.parse(
continued.value.normalized_result,
);
const contradiction = result.integrity_contradictions.find(
({ logical_path }) => logical_path === addon,
);
expect(contradiction).toMatchObject({
logical_path: addon,
declared_sha256: createHash("sha256").update(original).digest("hex"),
observed_sha256: createHash("sha256").update(stripped).digest("hex"),
trust: "observed-untrusted",
});
expect(result.graph.coverage.status).toBe("partial");
expect(result.graph.coverage.omitted_count).toBe(0);
expect(result.graph.limitations).toContain(
`Artifact integrity metadata contradicts observed bytes at ${addon}; the observed bytes are untrusted.`,
);
expect(continued.value.parameters).toMatchObject({
format: "auto",
integrity_policy: "record-and-continue",
});
expect(result.statistics.parsed_javascript_files).toBeGreaterThan(0);
expect(JSON.stringify(result.graph)).toContain(
createHash("sha256").update(stripped).digest("hex"),
);
});
it("keeps a contradicted nested ASAR opaque during JavaScript reconstruction", async () => {
const root = await createTestTempDirectory("rea-mismatched-nested-asar-");
const nestedSource = join(root, "nested-source");
const outerSource = join(root, "outer-source");
const archive = join(root, "app.asar");
const nestedArchive = join(outerSource, "nested.asar");
const nestedSecret = "export const nestedOnly = 'not inventoried';\n";
await mkdir(nestedSource);
await mkdir(outerSource);
await writeFile(join(nestedSource, "main.js"), nestedSecret);
await writeFile(join(outerSource, "main.js"), "export const outer = true;\n");
await createPackage(nestedSource, nestedArchive);
await createPackageWithOptions(outerSource, archive, {
unpack: "nested.asar",
});
const unpackedNested = join(`${archive}.unpacked`, "nested.asar");
const nestedBytes = await readFile(unpackedNested);
nestedBytes[0] = (nestedBytes[0] ?? 0) ^ 0xff;
await writeFile(unpackedNested, nestedBytes);
const analyzed = await analyzeJavaScriptApplication({
input_path: archive,
integrity_policy: "record-and-continue",
});
if (!analyzed.ok) throw new Error("Expected explicit continuation");
const result = javascriptApplicationAnalysisResultSchema.parse(
analyzed.value.normalized_result,
);
expect(result.integrity_contradictions).toMatchObject([
{ logical_path: "nested.asar", trust: "observed-untrusted" },
]);
expect(result.statistics.nested_asar_containers).toBe(0);
expect(JSON.stringify(result)).not.toContain(nestedSecret);
expect(result.graph.coverage.status).toBe("partial");
expect(result.graph.coverage.omitted_count).toBeNull();
});
it("retains semantic resource limits alongside recorded integrity contradictions", async () => {
const { archive } = await createStrippedAsarAddon([
{ path: "app.js", contents: primitiveCandidateExpansionSource() },
{ path: "growth.js", contents: primitiveByteExpansionSource() },
]);
const analyzed = await analyzeJavaScriptApplication({
input_path: archive,
integrity_policy: "record-and-continue",
});
if (!analyzed.ok) throw new Error("Expected explicit continuation");
const result = javascriptApplicationAnalysisResultSchema.parse(
analyzed.value.normalized_result,
);
expect(result.integrity_contradictions).toHaveLength(1);
expect(result.graph.coverage).toMatchObject({
status: "partial",
omitted_count: null,
limits: expect.arrayContaining([
expect.objectContaining({
name: "javascript_semantic_primitive_candidates",
unit: "items",
}),
expect.objectContaining({
name: "javascript_semantic_primitive_bytes",
unit: "bytes",
}),
]),
});
});
it("rejects a nested ASAR introduced after inventory as artifact drift", async () => {
const root = await createTestTempDirectory("rea-new-nested-asar-");
const nestedSource = join(root, "nested-source");
await mkdir(nestedSource);
await writeFile(join(root, "main.js"), "export const outer = true;\n");
await writeFile(
join(nestedSource, "main.js"),
"export const nested = true;\n",
);
const snapshot = await scanCanonicalArtifactInventory(root, {});
await createPackage(nestedSource, join(root, "new.asar"));
const reader = createJavaScriptArtifactReader(root, "directory");
try {
await expect(
readJavaScriptArtifactFiles(reader, snapshot),
).rejects.toMatchObject({
reason: "integrity",
message: "Nested ASAR was not present in inventory: new.asar",
});
} finally {
await reader.close();
}
});