Spaces:
Runtime error
Runtime error
| // scripts/check/check-lockfile.mjs | |
| // Gate de política de lockfile (CLAUDE.md — extensão Hard Rule #1). | |
| // | |
| // Objetivo: detectar supply-chain poisoning no package-lock.json antes que código | |
| // malicioso entre no repo. Verifica: | |
| // --validate-https → toda URL "resolved" deve usar HTTPS (bloqueia http://) | |
| // --validate-integrity → todo pacote deve ter hash de integridade sha512 | |
| // --allowed-hosts npm → apenas registry.npmjs.org é host permitido | |
| // | |
| // Complementa check-deps (Fase 2 / allowlist de nomes): aquele garante que só | |
| // nomes aprovados entram; este garante que os pacotes instalados vieram do registry | |
| // legítimo com integridade verificável. | |
| // | |
| // Referência: PLANO-QUALITY-GATES-FASE7.md, Task 7.7. | |
| // Tool: lockfile-lint v5 (node_modules/.bin/lockfile-lint). | |
| import { execFileSync } from "node:child_process"; | |
| import path from "node:path"; | |
| import fs from "node:fs"; | |
| import { pathToFileURL } from "node:url"; | |
| const ROOT = process.cwd(); | |
| /** | |
| * Returns the canonical lockfile-lint configuration used by this gate. | |
| * Exporting this object makes the policy auditable and unit-testable without | |
| * spawning a child process. | |
| * | |
| * @returns {{ | |
| * lockfilePath: string, | |
| * type: string, | |
| * validateHttps: boolean, | |
| * validateIntegrity: boolean, | |
| * allowedHosts: string[], | |
| * }} | |
| */ | |
| export function getLockfileLintConfig() { | |
| return { | |
| lockfilePath: path.join(ROOT, "package-lock.json"), | |
| type: "npm", | |
| validateHttps: true, | |
| validateIntegrity: true, | |
| // Only the official npm registry is permitted. | |
| // registry.npmjs.org resolves to the "npm" shorthand in lockfile-lint. | |
| // If the project ever adopts a scoped/private registry, add its hostname here | |
| // and document the justification. | |
| allowedHosts: ["npm"], | |
| }; | |
| } | |
| /** | |
| * Builds the argv array to pass to the lockfile-lint binary, derived from | |
| * the config returned by getLockfileLintConfig(). | |
| * | |
| * @param {ReturnType<typeof getLockfileLintConfig>} cfg | |
| * @returns {string[]} | |
| */ | |
| export function buildLockfileLintArgs(cfg) { | |
| const args = [ | |
| "--path", cfg.lockfilePath, | |
| "--type", cfg.type, | |
| ]; | |
| if (cfg.validateHttps) args.push("--validate-https"); | |
| if (cfg.validateIntegrity) args.push("--validate-integrity"); | |
| if (cfg.allowedHosts.length) { | |
| args.push("--allowed-hosts", ...cfg.allowedHosts); | |
| } | |
| return args; | |
| } | |
| function main() { | |
| const cfg = getLockfileLintConfig(); | |
| if (!fs.existsSync(cfg.lockfilePath)) { | |
| console.error( | |
| `[check-lockfile] FAIL — lockfile not found: ${cfg.lockfilePath}\n` + | |
| " → Run `npm install` to generate package-lock.json" | |
| ); | |
| process.exit(1); | |
| } | |
| const bin = path.join(ROOT, "node_modules", ".bin", "lockfile-lint"); | |
| if (!fs.existsSync(bin)) { | |
| console.error( | |
| `[check-lockfile] FAIL — lockfile-lint binary not found at:\n ${bin}\n` + | |
| " → Run `npm install` to install dev dependencies" | |
| ); | |
| process.exit(1); | |
| } | |
| const args = buildLockfileLintArgs(cfg); | |
| try { | |
| const output = execFileSync(bin, args, { encoding: "utf8" }); | |
| // lockfile-lint outputs a green ✔ message on success | |
| console.log("[check-lockfile] OK —", output.trim()); | |
| } catch (err) { | |
| const stdout = err.stdout ?? ""; | |
| const stderr = err.stderr ?? ""; | |
| console.error("[check-lockfile] FAIL — lockfile-lint found policy violations:"); | |
| if (stdout) console.error(stdout); | |
| if (stderr) console.error(stderr); | |
| console.error( | |
| "\n Possible causes:\n" + | |
| " • A package was resolved from a non-HTTPS URL (http:// poisoning attempt)\n" + | |
| " • A package is missing its integrity hash (tampered or legacy entry)\n" + | |
| " • A package was resolved from a host other than registry.npmjs.org\n" + | |
| " If a scoped/private registry is intentionally used, add its hostname\n" + | |
| " to getLockfileLintConfig().allowedHosts in scripts/check/check-lockfile.mjs" | |
| ); | |
| process.exit(1); | |
| } | |
| } | |
| if (import.meta.url === pathToFileURL(process.argv[1] || "").href) main(); | |