Skip to content

Commit 7a54844

Browse files
committed
ci: add axios/plain-crypto guard
1 parent 19830f4 commit 7a54844

2 files changed

Lines changed: 89 additions & 1 deletion

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"private": true,
55
"type": "module",
66
"scripts": {
7-
"cli": "tsx cli/index.ts"
7+
"cli": "tsx cli/index.ts",
8+
"ci:guard": "node scripts/check-axios.js"
89
},
910
"devDependencies": {
1011
"commander": "^11.1.0",

scripts/check-axios.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env node
2+
/**
3+
* CI guard for axios supply-chain incident (malicious 1.14.1 / 0.30.4) and plain-crypto-js dropper.
4+
* Fails if any package-lock.json contains disallowed axios versions or plain-crypto-js.
5+
*/
6+
7+
import fs from "fs/promises";
8+
import path from "path";
9+
import { fileURLToPath } from "url";
10+
11+
const __filename = fileURLToPath(import.meta.url);
12+
const __dirname = path.dirname(__filename);
13+
const repoRoot = path.resolve(__dirname, "..");
14+
15+
const DISALLOWED_AXIOS = new Set(["1.14.1", "0.30.4"]);
16+
const MAX_SAFE_AXIOS = "1.14.0";
17+
18+
async function* walk(dir) {
19+
const entries = await fs.readdir(dir, { withFileTypes: true });
20+
for (const entry of entries) {
21+
const full = path.join(dir, entry.name);
22+
if (entry.isDirectory()) {
23+
// skip node_modules to keep this fast
24+
if (entry.name === "node_modules" || entry.name === ".git") continue;
25+
yield* walk(full);
26+
} else {
27+
yield full;
28+
}
29+
}
30+
}
31+
32+
function versionGreater(v, max) {
33+
const toNum = (s) => s.split(".").map((n) => Number(n));
34+
const [a1, a2, a3] = toNum(v);
35+
const [b1, b2, b3] = toNum(max);
36+
if (a1 !== b1) return a1 > b1;
37+
if (a2 !== b2) return a2 > b2;
38+
return a3 > b3;
39+
}
40+
41+
async function checkLock(file) {
42+
const txt = await fs.readFile(file, "utf8");
43+
const problems = [];
44+
if (txt.includes("plain-crypto-js")) {
45+
problems.push("plain-crypto-js present");
46+
}
47+
// naive scan for axios@x.y.z tokens
48+
const regex = /axios@([0-9]+\\.[0-9]+\\.[0-9]+)/g;
49+
let m;
50+
while ((m = regex.exec(txt)) !== null) {
51+
const ver = m[1];
52+
if (DISALLOWED_AXIOS.has(ver) || versionGreater(ver, MAX_SAFE_AXIOS)) {
53+
problems.push(`axios ${ver} in ${file}`);
54+
}
55+
}
56+
return problems;
57+
}
58+
59+
async function main() {
60+
const lockFiles = [];
61+
for await (const f of walk(repoRoot)) {
62+
if (path.basename(f) === "package-lock.json") lockFiles.push(f);
63+
}
64+
if (lockFiles.length === 0) {
65+
console.log("No package-lock.json files found; skipping.");
66+
return;
67+
}
68+
69+
const allProblems = [];
70+
for (const lock of lockFiles) {
71+
const issues = await checkLock(lock);
72+
for (const p of issues) allProblems.push(p);
73+
}
74+
75+
if (allProblems.length) {
76+
console.error("❌ Supply-chain guard failed:");
77+
for (const p of allProblems) console.error(" -", p);
78+
process.exit(1);
79+
} else {
80+
console.log("✅ Axios/plain-crypto-js guard passed");
81+
}
82+
}
83+
84+
main().catch((err) => {
85+
console.error("Guard failed to run:", err);
86+
process.exit(1);
87+
});

0 commit comments

Comments
 (0)