import { createHash } from "node:crypto"; import { readFileSync, readdirSync, statSync } from "node:fs"; import { extname, join, relative } from "node:path"; const SHA40 = /^[a-f0-9]{40}$/i; const SHA64 = /^[a-f0-9]{64}$/i; const VERSION = /^[1-9][0-9]*\.[0-9]+\.[0-9]+\.[0-9]+$/; const ABSOLUTE_PATH = /(?:[A-Za-z]:[\\/](?:Users|Documents)[\\/][^\\/"'\s]+[\\/]|\/Users\/[^/"'\s]+\/|\/home\/[^/"'\s]+\/)/; const CREDENTIAL = /\b(?:sk|key)-[A-Za-z0-9_-]{16,}\b|-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/i; const TEXT_EXTENSIONS = new Set([".cjs", ".cs", ".css", ".html", ".js", ".json", ".mjs", ".ts", ".tsx", ".txt", ".xml", ".yaml", ".yml"]); export const DEFERRED_EXTERNAL_TASKS = Object.freeze(["TASK-WP7-03", "TASK-WP7-04"]); export function buildFinalReleaseRecord({ appVersion, browsers, buildCommit, frozenFromCommit, recordedAt, windows }) { const record = { appVersion, browsers: browsers.map(({ brand, fullVersion }) => ({ brand, fullVersion })), buildCommit: buildCommit.toLowerCase(), deferredExternalTasks: [...DEFERRED_EXTERNAL_TASKS], finalRelease: true, fixedPort: 43121, frozenFromCommit: frozenFromCommit.toLowerCase(), recordedAt, releaseStatus: "first_version_internal", schemaVersion: "1.0", windows: { arch: windows.arch, build: windows.build, displayVersion: windows.displayVersion }, }; return validateFinalReleaseRecord(record); } export function validateFinalReleaseRecord(record) { const errors = []; if (record?.schemaVersion !== "1.0") errors.push("schemaVersion"); if (record?.releaseStatus !== "first_version_internal") errors.push("releaseStatus"); if (record?.finalRelease !== true) errors.push("finalRelease"); if (record?.fixedPort !== 43121) errors.push("fixedPort"); if (!SHA40.test(record?.buildCommit ?? "")) errors.push("buildCommit"); if (!SHA40.test(record?.frozenFromCommit ?? "")) errors.push("frozenFromCommit"); if (!Number.isFinite(Date.parse(record?.recordedAt ?? ""))) errors.push("recordedAt"); if (!Array.isArray(record?.deferredExternalTasks) || record.deferredExternalTasks.join("|") !== DEFERRED_EXTERNAL_TASKS.join("|")) errors.push("deferredExternalTasks"); if (record?.windows?.arch !== "x64" || !/^\d+\.\d+$/.test(record?.windows?.build ?? "")) errors.push("windows"); if (!Array.isArray(record?.browsers) || record.browsers.length !== 2) { errors.push("browsers"); } else { const brands = record.browsers.map(({ brand }) => brand).sort(); if (brands.join("|") !== "Google Chrome|Microsoft Edge") errors.push("browserBrands"); for (const browser of record.browsers) { if (!VERSION.test(browser.fullVersion ?? "")) errors.push(`${browser.brand}.fullVersion`); if ("path" in browser || "executablePath" in browser || "executableSha256" in browser) errors.push(`${browser.brand}.privateMetadata`); } } const serialized = JSON.stringify(record); if (ABSOLUTE_PATH.test(serialized) || CREDENTIAL.test(serialized)) errors.push("sensitiveValue"); if (errors.length > 0) throw new Error(`WP7_07_RELEASE_INVALID:${[...new Set(errors)].join(",")}`); return record; } export function sha256File(path) { return createHash("sha256").update(readFileSync(path)).digest("hex").toUpperCase(); } export function scanReleaseFiles({ roots, allowedFixturePaths = [] }) { const allowed = new Set(allowedFixturePaths.map((value) => value.replaceAll("\\", "/"))); const findings = []; let scannedFiles = 0; function visit(root, current = root) { for (const entry of readdirSync(current, { withFileTypes: true })) { if ([".git", ".pnpm-store", "node_modules", "bin", "obj"].includes(entry.name)) continue; const path = join(current, entry.name); if (entry.isDirectory()) { visit(root, path); continue; } if (!entry.isFile()) continue; scannedFiles += 1; if (!TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase())) continue; const logicalPath = relative(root, path).replaceAll("\\", "/"); const content = readFileSync(path, "utf8"); if (!allowed.has(logicalPath) && ABSOLUTE_PATH.test(content)) findings.push({ path: logicalPath, rule: "absolute_user_path" }); if (!allowed.has(logicalPath) && CREDENTIAL.test(content)) findings.push({ path: logicalPath, rule: "credential_shape" }); } } for (const root of roots) { if (!statSync(root).isDirectory()) throw new Error(`WP7_07_SCAN_ROOT_INVALID:${root}`); visit(root); } return { findings, scanned_files: scannedFiles, status: findings.length === 0 ? "passed" : "failed" }; } export function validateFinalEvidence({ packageManifest, release, releaseSha256, scan }) { validateFinalReleaseRecord(release); if (!SHA64.test(releaseSha256 ?? "")) throw new Error("WP7_07_RELEASE_HASH_INVALID"); if (packageManifest?.release_status !== release.releaseStatus || !SHA64.test(packageManifest?.zip_sha256 ?? "")) throw new Error("WP7_07_PACKAGE_MANIFEST_INVALID"); if (scan?.status !== "passed" || scan.findings?.length !== 0) throw new Error("WP7_07_LEAK_SCAN_FAILED"); return { release_sha256: releaseSha256.toUpperCase(), status: "passed", zip_sha256: packageManifest.zip_sha256.toUpperCase() }; }