Public source validation / verify (push) Successful in 1m20s
Public changes compared with the previous published export: 4 files changed. Affected areas: - dependencies: 1 file - deployment and CI: 1 file - documentation: 1 file - scripts: 1 file Changed files: - Added: ".gitea/workflows/ci.yml" - Updated: "SECURITY.md" - Updated: "package-lock.json" - Added: "scripts/audit-dependencies.mjs" Export provenance/manifests refreshed; excluded from the change summary. Publish curated CertScope source from 9cd536d4e2dd485794e5f5accd70220de4a081de
155 lines
4.4 KiB
JavaScript
155 lines
4.4 KiB
JavaScript
import fs from "node:fs";
|
|
import { spawnSync } from "node:child_process";
|
|
|
|
const ADVISORY_URL = "https://github.com/advisories/GHSA-vfj7-8cjw-p6xm";
|
|
const EXPECTED_VULNERABILITIES = [
|
|
"@next/eslint-plugin-next",
|
|
"braces",
|
|
"eslint-config-next",
|
|
"fast-glob",
|
|
"micromatch",
|
|
].sort();
|
|
|
|
const EXPECTED_VERSIONS = new Map([
|
|
["next", "16.3.8"],
|
|
["eslint-config-next", "16.3.8"],
|
|
["@next/eslint-plugin-next", "16.3.8"],
|
|
["fast-glob", "3.3.1"],
|
|
["micromatch", "4.0.8"],
|
|
["braces", "3.0.3"],
|
|
]);
|
|
|
|
function fail(message) {
|
|
console.error(`Dependency audit exception rejected: ${message}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
function packageVersion(lock, name) {
|
|
const entry = lock.packages?.[`node_modules/${name}`];
|
|
return typeof entry?.version === "string" ? entry.version : null;
|
|
}
|
|
|
|
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
const audit = spawnSync(
|
|
npmCommand,
|
|
["audit", "--audit-level=moderate", "--json"],
|
|
{
|
|
encoding: "utf8",
|
|
maxBuffer: 8 * 1024 * 1024,
|
|
shell: process.platform === "win32",
|
|
windowsHide: true,
|
|
},
|
|
);
|
|
|
|
if (audit.error) {
|
|
fail(`npm audit could not start: ${audit.error.name}`);
|
|
}
|
|
if (audit.status !== 0 && audit.status !== 1) {
|
|
fail(`npm audit returned unexpected exit code ${audit.status}`);
|
|
}
|
|
|
|
let report;
|
|
try {
|
|
report = JSON.parse(audit.stdout);
|
|
} catch {
|
|
fail("npm audit returned invalid JSON");
|
|
}
|
|
|
|
if (report.auditReportVersion !== 2 || typeof report.vulnerabilities !== "object") {
|
|
fail("npm audit report schema changed");
|
|
}
|
|
|
|
const names = Object.keys(report.vulnerabilities).sort();
|
|
if (audit.status === 0) {
|
|
if (names.length !== 0) {
|
|
fail("npm audit exited cleanly but still reported vulnerabilities");
|
|
}
|
|
console.log("Full dependency audit is clean.");
|
|
process.exit(0);
|
|
}
|
|
|
|
if (JSON.stringify(names) !== JSON.stringify(EXPECTED_VULNERABILITIES)) {
|
|
fail(`unexpected vulnerability set: ${names.join(", ")}`);
|
|
}
|
|
|
|
const counts = report.metadata?.vulnerabilities;
|
|
if (
|
|
!counts ||
|
|
counts.info !== 0 ||
|
|
counts.low !== 0 ||
|
|
counts.moderate !== 0 ||
|
|
counts.high !== 5 ||
|
|
counts.critical !== 0 ||
|
|
counts.total !== 5
|
|
) {
|
|
fail("vulnerability counts differ from the reviewed exception");
|
|
}
|
|
|
|
const lock = JSON.parse(fs.readFileSync(new URL("../package-lock.json", import.meta.url), "utf8"));
|
|
for (const [name, expected] of EXPECTED_VERSIONS) {
|
|
const actual = packageVersion(lock, name);
|
|
if (actual !== expected) {
|
|
fail(`${name} changed from reviewed version ${expected} to ${actual ?? "missing"}`);
|
|
}
|
|
}
|
|
|
|
const vulnerabilities = report.vulnerabilities;
|
|
for (const name of EXPECTED_VULNERABILITIES) {
|
|
const item = vulnerabilities[name];
|
|
if (!item || item.severity !== "high" || !Array.isArray(item.via)) {
|
|
fail(`${name} no longer matches the reviewed high-severity audit shape`);
|
|
}
|
|
}
|
|
|
|
const directNames = EXPECTED_VULNERABILITIES.filter(
|
|
(name) => vulnerabilities[name].isDirect === true,
|
|
);
|
|
if (JSON.stringify(directNames) !== JSON.stringify(["eslint-config-next"])) {
|
|
fail(`unexpected direct dependency findings: ${directNames.join(", ") || "none"}`);
|
|
}
|
|
|
|
const expectedChain = new Map([
|
|
["eslint-config-next", "@next/eslint-plugin-next"],
|
|
["@next/eslint-plugin-next", "fast-glob"],
|
|
["fast-glob", "micromatch"],
|
|
["micromatch", "braces"],
|
|
]);
|
|
for (const [name, expectedVia] of expectedChain) {
|
|
if (!vulnerabilities[name].via.includes(expectedVia)) {
|
|
fail(`${name} no longer resolves through ${expectedVia}`);
|
|
}
|
|
}
|
|
|
|
const bracesAdvisories = vulnerabilities.braces.via.filter(
|
|
(entry) => entry && typeof entry === "object",
|
|
);
|
|
if (bracesAdvisories.length !== 1) {
|
|
fail("braces advisory count changed");
|
|
}
|
|
const advisory = bracesAdvisories[0];
|
|
if (
|
|
String(advisory.source) !== "1240992" ||
|
|
advisory.name !== "braces" ||
|
|
advisory.severity !== "high" ||
|
|
advisory.range !== "<=3.0.3" ||
|
|
advisory.url !== ADVISORY_URL
|
|
) {
|
|
fail("braces advisory identity or affected range changed");
|
|
}
|
|
|
|
const fix = vulnerabilities["eslint-config-next"].fixAvailable;
|
|
if (
|
|
!fix ||
|
|
typeof fix !== "object" ||
|
|
fix.name !== "eslint-config-next" ||
|
|
fix.version !== "14.2.35" ||
|
|
fix.isSemVerMajor !== true
|
|
) {
|
|
fail("npm now reports a different remediation; review and remove/update this exception");
|
|
}
|
|
|
|
console.log(
|
|
"Accepted reviewed dev-tooling exception only: GHSA-vfj7-8cjw-p6xm via eslint-config-next -> @next/eslint-plugin-next -> fast-glob -> micromatch -> braces@3.0.3.",
|
|
);
|
|
console.log("Production dependencies remain covered by the separate strict production audit.");
|