Spaces:
Running
Running
File size: 6,501 Bytes
1a6d0d2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | #!/usr/bin/env node
/**
* Native/WASM module supply-chain gate.
*
* Verifies the trust invariants documented in .github/SECURITY.md for the
* project's native and WebAssembly dependencies:
*
* 1. Every tracked module is pinned to an exact version in package.json
* (no caret/tilde range a later publish could slide into).
* 2. package-lock.json resolves it to that exact version with an
* integrity hash.
* 3. Every package in the lockfile that ships an install script has an
* explicit entry in package.json#allowScripts β no unreviewed script,
* whether allowed or denied.
* 4. `npm audit signatures` reports no invalid or missing registry
* signature for a tracked module.
*
* Fails loudly (non-zero exit, one line per problem) on a missing or
* mismatched entry.
*/
const { spawnSync } = require("node:child_process");
const fs = require("node:fs");
const path = require("node:path");
/**
* Direct dependencies whose payload is native code or WebAssembly rather
* than auditable JavaScript. Kept in sync with the inventory table in
* .github/SECURITY.md.
*/
const NATIVE_MODULES = [
"@huggingface/tokenizers",
"@moonshine-ai/moonshine-wasm",
"@wllama/wllama",
"hash-wasm",
"onnxruntime-node",
];
// A single exact version, optionally with a prerelease tag. Anything with a
// range operator is rejected: native/WASM deps must be reviewed per version.
const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
// The two key shapes `npm approve-scripts` / `npm deny-scripts` write:
// a bare name (covers every version) or an exact `name@version` pin.
function policyCovers(allowScripts, name, version) {
return (
Object.hasOwn(allowScripts, name) ||
Object.hasOwn(allowScripts, `${name}@${version}`)
);
}
function checkPins(pkg, problems) {
for (const name of NATIVE_MODULES) {
const spec = pkg.dependencies?.[name];
if (spec === undefined) {
problems.push(`${name}: not a direct dependency of this project`);
} else if (!EXACT_VERSION.test(spec)) {
problems.push(
`${name}: "${spec}" is not an exact pin; native/WASM deps must declare a single version`,
);
}
}
}
function checkLock(lock, pkg, problems) {
for (const name of NATIVE_MODULES) {
const spec = pkg.dependencies?.[name];
if (typeof spec !== "string" || !EXACT_VERSION.test(spec)) {
continue; // already reported by checkPins
}
const entry = lock.packages?.[`node_modules/${name}`];
if (!entry) {
problems.push(`${name}@${spec}: missing from package-lock.json`);
continue;
}
if (entry.version !== spec) {
problems.push(
`${name}: package.json pins ${spec} but the lockfile resolves ${entry.version}`,
);
}
if (!entry.integrity) {
problems.push(
`${name}@${entry.version}: lockfile entry has no integrity hash`,
);
}
}
}
function checkInstallScriptPolicy(lock, allowScripts, problems) {
for (const [key, entry] of Object.entries(lock.packages ?? {})) {
if (key === "" || !entry.hasInstallScript) continue;
const name = key.split("node_modules/").pop();
if (!policyCovers(allowScripts, name, entry.version)) {
problems.push(
`${name}@${entry.version}: ships an install script with no entry in package.json#allowScripts ` +
`(review it, then \`npm approve-scripts ${name}\` or \`npm deny-scripts ${name}\`)`,
);
}
}
}
function checkRegistrySignatures(rootDir, problems) {
const res = spawnSync("npm", ["audit", "signatures", "--json"], {
cwd: rootDir,
encoding: "utf8",
maxBuffer: 64 * 1024 * 1024,
});
if (res.error) {
problems.push(
`npm audit signatures could not be run: ${res.error.message}`,
);
return;
}
let report;
try {
report = JSON.parse(res.stdout);
} catch {
problems.push(
`npm audit signatures produced no parseable report (exit ${res.status}); ` +
"the registry may be unreachable β treat as unverified",
);
return;
}
for (const [kind, items] of [
["invalid", report.invalid ?? []],
["missing", report.missing ?? []],
]) {
for (const item of items) {
if (NATIVE_MODULES.includes(item.name)) {
problems.push(
`${item.name}@${item.version}: ${kind} registry signature`,
);
} else {
console.log(
`note: ${item.name}@${item.version} has a ${kind} registry signature ` +
"(outside this gate's native/WASM scope; see `npm audit signatures`)",
);
}
}
}
}
/**
* Runs every check against a project root and returns the process exit code.
*
* @param {string} rootDir Project root containing package.json,
* package-lock.json and an installed node_modules tree.
* @returns {number} 0 when all invariants hold, 1 otherwise.
*/
function run(rootDir) {
const pkg = JSON.parse(
fs.readFileSync(path.join(rootDir, "package.json"), "utf8"),
);
const lock = JSON.parse(
fs.readFileSync(path.join(rootDir, "package-lock.json"), "utf8"),
);
const problems = [];
checkPins(pkg, problems);
checkLock(lock, pkg, problems);
checkInstallScriptPolicy(lock, pkg.allowScripts ?? {}, problems);
if (fs.existsSync(path.join(rootDir, "node_modules"))) {
checkRegistrySignatures(rootDir, problems);
} else {
problems.push(
"node_modules is missing; run `npm ci` before the integrity check",
);
}
if (problems.length > 0) {
for (const problem of problems) {
console.log(`β ${problem}`);
}
console.log(
`\n${problems.length} native-module integrity problem(s) found (of ${NATIVE_MODULES.length} tracked modules)`,
);
return 1;
}
console.log(
`β
All ${NATIVE_MODULES.length} tracked native/WASM modules are exactly pinned, locked with integrity hashes, covered by the install-script policy, and free of invalid or missing registry signatures`,
);
return 0;
}
if (require.main === module) {
const rootDirArg = process.argv[2];
const usage = "Usage: node scripts/native-module-integrity.cjs [rootDir]";
if (rootDirArg?.startsWith("-")) {
console.error(`β Unknown option: ${rootDirArg}\n ${usage}`);
process.exit(1);
}
if (rootDirArg && !fs.existsSync(rootDirArg)) {
console.error(`β Directory not found: ${rootDirArg}`);
process.exit(1);
}
process.exit(run(rootDirArg || path.resolve(__dirname, "..")));
}
module.exports = { run, NATIVE_MODULES };
|