fix: support native package verifier scratch
CI / verify (pull_request) Successful in 1m10s
CI / infrastructure-smoke (pull_request) Successful in 37s
CI / migration-smoke (pull_request) Successful in 1m54s
CI / package-release (pull_request) Successful in 36s
CI / image-build (pull_request) Successful in 52s
CI / deploy-smoke (same-origin) (pull_request) Successful in 51s
CI / deploy-smoke (split) (pull_request) Successful in 1m31s
CI / package-publication (pull_request) Successful in 51s

This commit is contained in:
ekklesia
2026-10-09 18:05:04 -05:00
parent c5a6cdaf6a
commit 4be046c8d0
12 changed files with 330 additions and 13 deletions
+53 -1
View File
@@ -1,4 +1,4 @@
import { mkdtemp, readdir, rm, stat, writeFile } from "node:fs/promises";
import { mkdir, mkdtemp, readdir, rm, stat, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
@@ -48,6 +48,12 @@ async function archiveFile(options: PackageFixtureOptions = {}): Promise<{
return { archivePath, sha256: fixture.sha256 };
}
async function executionScratch(): Promise<string> {
const directory = path.join(await scratch(), "execution");
await mkdir(directory);
return directory;
}
async function rejection(
options: PackageFixtureOptions,
code: ModulePackageVerificationErrorCode,
@@ -100,6 +106,52 @@ describe("Module Package v1 verification (ADR 0049)", () => {
});
});
it("uses an explicit execution scratch for pure JavaScript and cleans concurrent runs", async () => {
const { archivePath } = await archiveFile();
const executionScratchDirectory = await executionScratch();
const runs = Array.from({ length: 4 }, () =>
verifyModulePackage({ archivePath, executionScratchDirectory, platform }),
);
await expect(Promise.all(runs)).resolves.toHaveLength(4);
expect(await readdir(executionScratchDirectory)).toEqual([]);
});
it("cleans its execution scratch after a backend load failure", async () => {
const { archivePath } = await archiveFile({
serverSource:
'import "dependency-not-in-this-package";\nexport default { id: "example", version: "0.1.0" };\n',
});
const executionScratchDirectory = await executionScratch();
await expect(
verifyModulePackage({ archivePath, executionScratchDirectory, platform }),
).rejects.toMatchObject({ code: "MODULE_PACKAGE_ENTRYPOINT_LOAD_FAILED" });
expect(await readdir(executionScratchDirectory)).toEqual([]);
});
it("reports a missing execution scratch as a verifier environment error", async () => {
const { archivePath } = await archiveFile();
const executionScratchDirectory = path.join(await scratch(), "missing");
await expect(
verifyModulePackage({ archivePath, executionScratchDirectory, platform }),
).rejects.toMatchObject({
code: "MODULE_PACKAGE_VERIFIER_SCRATCH_UNAVAILABLE",
message:
"The verifier execution scratch directory is missing, not writable, or mounted noexec.",
});
await expect(stat(executionScratchDirectory)).rejects.toMatchObject({ code: "ENOENT" });
});
it("rejects an unsafe archive before consulting the execution scratch", async () => {
const { archivePath } = await archiveFile(
withEntry({ path: "example/../attacker-chosen", data: "x" }),
);
const executionScratchDirectory = path.join(await scratch(), "missing");
await expect(
verifyModulePackage({ archivePath, executionScratchDirectory, platform }),
).rejects.toMatchObject({ code: "MODULE_PACKAGE_UNSAFE_PATH" });
await expect(stat(executionScratchDirectory)).rejects.toMatchObject({ code: "ENOENT" });
});
it("extracts only a verified package, with normalized modes, into a new directory", async () => {
const { archivePath } = await archiveFile();
const target = path.join(await scratch(), "example");
@@ -3,6 +3,8 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import {
MODULE_VERIFIER_SCRATCH_ENV,
moduleVerifierScratchDirectory,
parseSha256File,
parseVerifyModulePackageArguments,
readModulePackagePlatform,
@@ -57,6 +59,21 @@ describe("verify-module-package digest files", () => {
});
});
describe("verify-module-package execution scratch", () => {
it("accepts an absolute operator-configured directory and otherwise leaves the library default", () => {
expect(moduleVerifierScratchDirectory({})).toBeUndefined();
expect(
moduleVerifierScratchDirectory({ [MODULE_VERIFIER_SCRATCH_ENV]: "/run/ekklesia-verifier" }),
).toBe("/run/ekklesia-verifier");
});
it.each(["", "relative/path"])("refuses the invalid configured path %j", (configured) => {
expect(() =>
moduleVerifierScratchDirectory({ [MODULE_VERIFIER_SCRATCH_ENV]: configured }),
).toThrow(`${MODULE_VERIFIER_SCRATCH_ENV} must be an absolute path.`);
});
});
describe("verify-module-package platform facts", () => {
it("reads this platform's versions and its transitional database runtime from the repository", async () => {
const root = JSON.parse(await readFile(path.join(repositoryRoot, "package.json"), "utf8"));