import { createHash } from "node:crypto"; import { readdirSync, readFileSync } from "node:fs"; import { basename, join, relative } from "node:path"; export interface StagedFileIntegrity { path: string; bytes: number; sha256: string; } export interface StagedPackageIntegrity { name: string; version: string; files: StagedFileIntegrity[]; } const forbiddenStageName = /^(?:\.env(?:\..*)?|focus-shims\.d\.ts|tsconfig\.focus\.json|id_rsa|id_ed25519|.*\.(?:pem|key|p12|pfx))$/i; const highConfidenceSecret = /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|\bAKIA[0-9A-Z]{16}\b|\bnpm_[A-Za-z0-9]{30,}\b|\bgh[pousr]_[A-Za-z0-9]{30,}\b/; function listFiles(root: string): string[] { const files: string[] = []; for (const entry of readdirSync(root, { recursive: true, withFileTypes: true })) { if (!entry.isFile()) continue; files.push(join(entry.parentPath, entry.name)); } return files.sort((a, b) => { const left = relative(root, a).replace(/\\/g, "/"); const right = relative(root, b).replace(/\\/g, "/"); return left < right ? -1 : left > right ? 1 : 0; }); } export function validateAndHashStage( stage: string, manifest: Record, ): StagedPackageIntegrity { const name = String(manifest.name ?? "unknown package"); const files = listFiles(stage); if (!files.length) throw new Error(`${name} staged no files.`); const integrity: StagedFileIntegrity[] = []; for (const file of files) { const path = relative(stage, file).replace(/\\/g, "/"); const filename = basename(file); if (forbiddenStageName.test(filename)) { throw new Error(`${name} staged forbidden secret-like file ${path}.`); } if (path.endsWith(".map")) { throw new Error(`${name} staged source map ${path}; package source maps are disabled.`); } if (path.endsWith(".ts") && !path.endsWith(".d.ts")) { throw new Error(`${name} staged TypeScript source ${path}.`); } const value = readFileSync(file); if (value.byteLength <= 2 * 1024 * 1024) { const source = value.toString("utf8"); if (highConfidenceSecret.test(source)) { throw new Error(`${name} staged content matching a high-confidence secret in ${path}.`); } } integrity.push({ path, bytes: value.byteLength, sha256: createHash("sha256").update(value).digest("hex"), }); } return { name, version: String(manifest.version ?? "unknown"), files: integrity, }; }