shoc-frontend-new/scripts/payload-guard.mjs

75 lines
2.4 KiB
JavaScript

// Supply-chain guard.
//
// Fails the build if the known malicious loader signature is reintroduced into
// tracked source (e.g. a poisoned eslint.config.js). Added after the 2026
// force-push supply-chain incident. Runs as the first step of `npm run verify`,
// so it executes in the repo-owned governance CI job on every PR and push.
//
// Signatures are the stable markers of the obfuscated, credential-stealing
// loader observed in the attack. Keep the list append-only. Patterns are built
// from fragments so this file never matches itself.
import { execSync } from "node:child_process";
import { readFileSync } from "node:fs";
const SELF = "scripts/payload-guard.mjs";
const CODE_EXT = new Set([".js", ".cjs", ".mjs", ".ts", ".tsx", ".jsx"]);
const seed = "global" + ".o";
const shim = "createRequire(import" + ".meta.url)";
/** @type {{name:string, test:(path:string, text:string)=>boolean}[]} */
const RULES = [
{
name: "loader seed (global.o='8-…')",
test: (_p, t) => new RegExp(seed + "\\s*=\\s*['\"]8-").test(t),
},
{
name: "permutation variable (_$_…)",
test: (_p, t) => /_\$_/.test(t),
},
{
name: "delimiter trick (String.fromCharCode(127))",
test: (_p, t) => /String\.fromCharCode\(\s*127\s*\)/.test(t),
},
{
name: "ESM→CJS shim injected into eslint flat config",
test: (p, t) => /(^|\/)eslint\.config\.(js|mjs|cjs)$/.test(p) && t.includes(shim),
},
];
function trackedFiles() {
const out = execSync("git ls-files", { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
return out.split("\n").map((s) => s.trim()).filter(Boolean);
}
function extOf(path) {
const i = path.lastIndexOf(".");
return i === -1 ? "" : path.slice(i);
}
const findings = [];
for (const file of trackedFiles()) {
if (file === SELF) continue;
if (!CODE_EXT.has(extOf(file))) continue;
let text;
try {
text = readFileSync(file, "utf8");
} catch {
continue;
}
for (const rule of RULES) {
if (rule.test(file, text)) findings.push({ file, rule: rule.name });
}
}
if (findings.length > 0) {
console.error("payload-guard: known supply-chain markers detected:\n");
for (const f of findings) console.error(` ✗ ${f.file} — ${f.rule}`);
console.error("\nThis matches the obfuscated loader from the 2026 supply-chain incident.");
console.error("Do not merge. Inspect the file(s) and remove the injected payload.");
process.exit(1);
}
console.log("payload-guard: no known payload markers found.");