Files

207 lines
6.2 KiB
TypeScript

import {
dependencyDiffArtifactSchema,
dependencyInventoryArtifactSchema,
licenseReportArtifactSchema,
supplyChainVerificationArtifactSchema,
vulnerabilityReportArtifactSchema,
} from "../contracts/release-artifacts.ts";
import type { DistOutput } from "./release-candidate.ts";
import {
diffDependencyInventories,
supplyChainDigest,
validateDependencyReview,
validateLicensePolicy,
} from "./supply-chain.ts";
type Document = Record<string, unknown>;
export const LOCAL_SUPPLY_CHAIN_UNVERIFIED_DEFAULTS = Object.freeze({
promotionStatus: "FAIL_UNVERIFIED" as const,
vulnerabilityStatus: "FAIL_UNVERIFIED" as const,
provenanceAttestationStatus: "FAIL_UNVERIFIED" as const,
});
export function verifyLocalSupplyChainDefaults(stored: unknown): string[] {
const parsed = supplyChainVerificationArtifactSchema.safeParse(stored);
if (
!parsed.success ||
parsed.data.promotionStatus !==
LOCAL_SUPPLY_CHAIN_UNVERIFIED_DEFAULTS.promotionStatus ||
parsed.data.vulnerabilityStatus !==
LOCAL_SUPPLY_CHAIN_UNVERIFIED_DEFAULTS.vulnerabilityStatus ||
parsed.data.provenanceAttestationStatus !==
LOCAL_SUPPLY_CHAIN_UNVERIFIED_DEFAULTS.provenanceAttestationStatus
) {
return [
"supply-chain verification provider defaults are not local FAIL_UNVERIFIED",
];
}
return [];
}
export function createLocalVulnerabilityReport(lockfileSha256: string) {
return vulnerabilityReportArtifactSchema.parse({
schemaVersion: 1,
provider: "UNCONFIGURED",
scannedLockfileSha256: lockfileSha256,
status: "FAIL_UNVERIFIED",
findings: [],
exceptionsApplied: [],
failures: ["external vulnerability provider report is missing"],
blocking: [],
});
}
export function compareStoredLocalVulnerabilityReport(
lockfileSha256: string,
stored: unknown,
): string[] {
const parsed = vulnerabilityReportArtifactSchema.safeParse(stored);
if (
!parsed.success ||
supplyChainDigest(parsed.data) !==
supplyChainDigest(createLocalVulnerabilityReport(lockfileSha256))
) {
return [
"local vulnerability report does not match exact unconfigured defaults",
];
}
return [];
}
export function recomputeDependencyEvidence(input: Readonly<{
inventory: unknown;
baseline: unknown;
baselineApproval: unknown;
dependencyChangeEvidence: unknown;
skipBaseline?: boolean;
}>) {
const inventory = dependencyInventoryArtifactSchema.parse(input.inventory);
const baseline = asDocument(input.baseline);
const approval = asDocument(input.baselineApproval);
const dependencyChangeEvidence = asDocument(input.dependencyChangeEvidence);
const failures: string[] = [];
const skipBaseline = input.skipBaseline === true;
let diff: ReturnType<typeof diffDependencyInventories> = Object.freeze({
added: Object.freeze([]),
removed: Object.freeze([]),
changed: Object.freeze([]),
upgrades: Object.freeze([]),
});
let review: ReturnType<typeof validateDependencyReview> = Object.freeze({
passed: skipBaseline,
highRisk: Object.freeze([]),
failures: Object.freeze(
skipBaseline ? [] : ["dependency baseline unavailable"],
),
});
const baselineDigest = baseline ? supplyChainDigest(baseline) : null;
if (baseline && approval) {
diff = diffDependencyInventories(baseline, inventory);
review = validateDependencyReview(
diff,
inventory,
dependencyChangeEvidence ?? {},
);
if (
approval.schemaVersion !== 1 ||
approval.snapshotDigest !== baselineDigest ||
typeof approval.owner !== "string" ||
approval.owner.trim().length === 0
) {
failures.push("dependency baseline approval digest mismatch");
}
} else if (!skipBaseline) {
failures.push("dependency baseline and approval are required");
}
failures.push(...review.failures);
const report = dependencyDiffArtifactSchema.parse({
schemaVersion: 2,
baselineDigest,
currentDigest: supplyChainDigest(inventory),
...diff,
highRisk: review.highRisk,
reviewFailures: review.failures,
});
return Object.freeze({
report,
dependencyDiff: diff,
highRisk: review.highRisk,
failures: Object.freeze(failures),
});
}
export function compareStoredDependencyEvidence(
recomputed: ReturnType<typeof recomputeDependencyEvidence>,
stored: unknown,
): string[] {
const parsed = dependencyDiffArtifactSchema.safeParse(stored);
if (
!parsed.success ||
supplyChainDigest(parsed.data) !== supplyChainDigest(recomputed.report)
) {
return ["stored dependency diff does not match recomputed policy evidence"];
}
return [...recomputed.failures];
}
export function recomputeLicenseEvidence(input: Readonly<{
inventory: unknown;
policy: unknown;
}>) {
const inventory = dependencyInventoryArtifactSchema.parse(input.inventory);
const policy = asDocument(input.policy) ?? {};
const result = validateLicensePolicy(inventory, policy);
const report = licenseReportArtifactSchema.parse({
schemaVersion: 1,
status: result.passed ? "PASS" : "FAIL",
dependencyCount: inventory.dependencyCount,
results: result.results,
failures: result.failures,
});
return Object.freeze({
report,
failures: result.failures,
});
}
export function compareStoredLicenseEvidence(
recomputed: ReturnType<typeof recomputeLicenseEvidence>,
stored: unknown,
): string[] {
const parsed = licenseReportArtifactSchema.safeParse(stored);
if (
!parsed.success ||
supplyChainDigest(parsed.data) !== supplyChainDigest(recomputed.report)
) {
return ["stored license report does not match recomputed policy evidence"];
}
return [...recomputed.failures];
}
export function distChecksumsText(outputs: readonly DistOutput[]): string {
return `${[...outputs]
.sort((left, right) => left.path.localeCompare(right.path))
.map((output) => `${output.sha256} ${output.path}`)
.join("\n")}\n`;
}
export function verifyStoredDistChecksums(
outputs: readonly DistOutput[],
stored: string,
): string[] {
return stored === distChecksumsText(outputs)
? []
: ["stored dist checksums do not match current outputs"];
}
function asDocument(value: unknown): Document | null {
return value !== null &&
typeof value === "object" &&
!Array.isArray(value)
? (value as Document)
: null;
}