mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
ci(security): add payload-guard to verify chain against supply-chain loader
This commit is contained in:
parent
78ed8571a0
commit
2ae76a7a1b
2 changed files with 77 additions and 1 deletions
|
|
@ -16,7 +16,8 @@
|
|||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"governance": "node scripts/governance-check.mjs",
|
||||
"verify": "npm run format:check && npm run lint && npm run build && npm test && npm run governance",
|
||||
"payload-guard": "node scripts/payload-guard.mjs",
|
||||
"verify": "npm run payload-guard && npm run format:check && npm run lint && npm run build && npm test && npm run governance",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
|
|
|||
75
scripts/payload-guard.mjs
Normal file
75
scripts/payload-guard.mjs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// 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.");
|
||||
Loading…
Add table
Reference in a new issue