176 lines
8.1 KiB
JavaScript
176 lines
8.1 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { DEFAULT_POLICY } from "../lib/constants.js";
|
|
import { verifyDirectPackage } from "../lib/verify.js";
|
|
import { jsonResponse, packageDocument } from "./helpers.js";
|
|
|
|
const item = { name: "alpha", version: "1.2.3", dev: false };
|
|
const now = () => Date.parse("2025-01-01T00:00:00Z");
|
|
|
|
test("verifies SLSA source repository, tag ref, and existing commit", async () => {
|
|
const commit = "abcdef1234567890abcdef1234567890abcdef12";
|
|
const statement = {
|
|
predicate: {
|
|
buildDefinition: {
|
|
externalParameters: { workflow: { repository: "https://github.com/example/alpha", ref: "refs/tags/v1.2.3" } },
|
|
resolvedDependencies: [{ uri: "git+https://github.com/example/alpha@refs/tags/v1.2.3", digest: { gitCommit: commit } }],
|
|
},
|
|
},
|
|
};
|
|
const document = packageDocument({ version: { dist: {
|
|
signatures: [{ keyid: "key", sig: "sig" }],
|
|
attestations: {
|
|
url: "https://registry.npmjs.org/-/npm/v1/attestations/alpha@1.2.3",
|
|
provenance: { predicateType: "https://slsa.dev/provenance/v1" },
|
|
},
|
|
} } });
|
|
const fetch = async (url) => {
|
|
if (url.includes("attestations")) return jsonResponse({ attestations: [{ predicateType: "https://slsa.dev/provenance/v1", bundle: { dsseEnvelope: { payload: Buffer.from(JSON.stringify(statement)).toString("base64") } } }] });
|
|
throw new Error(`unexpected URL ${url}`);
|
|
};
|
|
const result = await verifyDirectPackage(item, document, structuredClone(DEFAULT_POLICY), {
|
|
fetchImpl: fetch,
|
|
remoteRefs: async () => [{ sha: commit, ref: "refs/tags/v1.2.3" }],
|
|
now,
|
|
timeoutMs: 100,
|
|
});
|
|
assert.deepEqual(result.failures, []);
|
|
assert.equal(result.provenance, "slsa");
|
|
});
|
|
|
|
test("accepts branch-built provenance only when a version tag pins its commit", async () => {
|
|
const commit = "abcdef1234567890abcdef1234567890abcdef12";
|
|
const statement = {
|
|
predicate: {
|
|
buildDefinition: {
|
|
externalParameters: { workflow: { repository: "https://github.com/example/alpha", ref: "refs/heads/main" } },
|
|
resolvedDependencies: [{ uri: "git+https://github.com/example/alpha@refs/heads/main", digest: { gitCommit: commit } }],
|
|
},
|
|
},
|
|
};
|
|
const document = packageDocument({ version: { dist: {
|
|
signatures: [{ keyid: "key", sig: "sig" }],
|
|
attestations: {
|
|
url: "https://registry.npmjs.org/-/npm/v1/attestations/alpha@1.2.3",
|
|
provenance: { predicateType: "https://slsa.dev/provenance/v1" },
|
|
},
|
|
} } });
|
|
const result = await verifyDirectPackage(item, document, structuredClone(DEFAULT_POLICY), {
|
|
fetchImpl: async () => jsonResponse({ attestations: [{ predicateType: "https://slsa.dev/provenance/v1", bundle: { dsseEnvelope: { payload: Buffer.from(JSON.stringify(statement)).toString("base64") } } }] }),
|
|
remoteRefs: async () => [{ sha: commit, ref: "refs/tags/v1.2.3" }],
|
|
now,
|
|
timeoutMs: 100,
|
|
});
|
|
assert.deepEqual(result.failures, []);
|
|
});
|
|
|
|
test("rejects branch-built provenance when no version tag pins its commit", async () => {
|
|
const commit = "abcdef1234567890abcdef1234567890abcdef12";
|
|
const statement = {
|
|
predicate: {
|
|
buildDefinition: {
|
|
externalParameters: { workflow: { repository: "https://github.com/example/alpha", ref: "refs/heads/main" } },
|
|
resolvedDependencies: [{ uri: "git+https://github.com/example/alpha@refs/heads/main", digest: { gitCommit: commit } }],
|
|
},
|
|
},
|
|
};
|
|
const document = packageDocument({ version: { dist: {
|
|
signatures: [{ keyid: "key", sig: "sig" }],
|
|
attestations: {
|
|
url: "https://registry.npmjs.org/-/npm/v1/attestations/alpha@1.2.3",
|
|
provenance: { predicateType: "https://slsa.dev/provenance/v1" },
|
|
},
|
|
} } });
|
|
const result = await verifyDirectPackage(item, document, structuredClone(DEFAULT_POLICY), {
|
|
fetchImpl: async () => jsonResponse({ attestations: [{ predicateType: "https://slsa.dev/provenance/v1", bundle: { dsseEnvelope: { payload: Buffer.from(JSON.stringify(statement)).toString("base64") } } }] }),
|
|
remoteRefs: async () => [],
|
|
now,
|
|
timeoutMs: 100,
|
|
});
|
|
assert.ok(result.failures.some((message) => message.includes("no Git tag containing 1.2.3")));
|
|
});
|
|
|
|
test("fallback accepts an annotated version tag dereferenced to gitHead", async () => {
|
|
const document = packageDocument();
|
|
const gitHead = document.versions["1.2.3"].gitHead;
|
|
const result = await verifyDirectPackage(item, document, structuredClone(DEFAULT_POLICY), {
|
|
remoteRefs: async () => [{ sha: gitHead, ref: "refs/tags/v1.2.3^{}" }],
|
|
now,
|
|
timeoutMs: 100,
|
|
});
|
|
assert.deepEqual(result.failures, []);
|
|
assert.equal(result.provenance, "gitHead-tag");
|
|
});
|
|
|
|
test("rejects direct lifecycle scripts and untrusted maintainers", async () => {
|
|
const document = packageDocument({ version: { scripts: { postinstall: "node setup.js" }, maintainers: [{ name: "mallory" }] } });
|
|
const policy = structuredClone(DEFAULT_POLICY);
|
|
policy.trustedMaintainers.alpha = ["alice"];
|
|
const result = await verifyDirectPackage(item, document, policy, {
|
|
remoteRefs: async () => [{ sha: document.versions["1.2.3"].gitHead, ref: "refs/tags/alpha-1.2.3" }],
|
|
now,
|
|
timeoutMs: 100,
|
|
});
|
|
assert.ok(result.failures.some((message) => message.includes("lifecycle hook")));
|
|
assert.ok(result.failures.some((message) => message.includes("registry maintainer set changed")));
|
|
});
|
|
|
|
test("permits a direct lifecycle script only for the reviewed version", async () => {
|
|
const document = packageDocument({ version: { scripts: { postinstall: "node setup.js" } } });
|
|
const policy = structuredClone(DEFAULT_POLICY);
|
|
policy.allowDirectLifecycleScripts.alpha = {
|
|
versions: ["1.2.3"],
|
|
reason: "Reviewed package setup script required by this exact release.",
|
|
expiresAt: "2025-02-01T00:00:00Z",
|
|
};
|
|
const dependencies = {
|
|
remoteRefs: async () => [{ sha: document.versions["1.2.3"].gitHead, ref: "refs/tags/v1.2.3" }],
|
|
now,
|
|
timeoutMs: 100,
|
|
};
|
|
const accepted = await verifyDirectPackage(item, document, policy, dependencies);
|
|
assert.ok(!accepted.failures.some((message) => message.includes("lifecycle hook")));
|
|
policy.allowDirectLifecycleScripts.alpha.versions = ["1.2.2"];
|
|
const rejected = await verifyDirectPackage(item, document, policy, dependencies);
|
|
assert.ok(rejected.failures.some((message) => message.includes("lifecycle hook")));
|
|
});
|
|
|
|
test("rejects a missing previously trusted maintainer", async () => {
|
|
const document = packageDocument({ version: { maintainers: [{ name: "alice" }] } });
|
|
const policy = structuredClone(DEFAULT_POLICY);
|
|
policy.trustedMaintainers.alpha = ["alice", "bob"];
|
|
const gitHead = document.versions["1.2.3"].gitHead;
|
|
const result = await verifyDirectPackage(item, document, policy, {
|
|
remoteRefs: async () => [{ sha: gitHead, ref: "refs/tags/v1.2.3" }],
|
|
now,
|
|
timeoutMs: 100,
|
|
});
|
|
assert.ok(result.failures.some((message) => message.includes("registry maintainer set changed")));
|
|
});
|
|
|
|
test("accepts only a version-pinned unexpired source evidence exception", async () => {
|
|
const document = packageDocument({ version: { gitHead: null } });
|
|
const policy = structuredClone(DEFAULT_POLICY);
|
|
policy.sourceEvidenceExceptions.alpha = {
|
|
version: "1.2.3",
|
|
reason: "Upstream does not publish version tags for this package.",
|
|
expiresAt: "2025-02-01T00:00:00Z",
|
|
};
|
|
const result = await verifyDirectPackage(item, document, policy, { now, timeoutMs: 100 });
|
|
assert.deepEqual(result.failures, []);
|
|
assert.equal(result.provenance, "policy-exception");
|
|
assert.ok(result.warnings.some((message) => message.includes("reviewed sourceEvidenceExceptions")));
|
|
});
|
|
|
|
test("rejects an expired source evidence exception", async () => {
|
|
const document = packageDocument({ version: { gitHead: null } });
|
|
const policy = structuredClone(DEFAULT_POLICY);
|
|
policy.sourceEvidenceExceptions.alpha = {
|
|
version: "1.2.3",
|
|
reason: "Upstream does not publish version tags for this package.",
|
|
expiresAt: "2024-12-31T00:00:00Z",
|
|
};
|
|
const result = await verifyDirectPackage(item, document, policy, { now, timeoutMs: 100 });
|
|
assert.ok(result.failures.some((message) => message.includes("gitHead is missing")));
|
|
});
|