fix(deploy): patch audited frontend dependencies
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
export function evaluateAudit(report, lock, now = Date.now()) {
|
||||
if (report?.error || !report?.metadata?.vulnerabilities || !report?.vulnerabilities || !lock?.packages) {
|
||||
return { accepted: [], failures: ['Missing or invalid audit/lock data'] };
|
||||
}
|
||||
const accepted = [], failures = [];
|
||||
const allowedChain = new Set(['braces', 'micromatch', 'fast-glob', '@next/eslint-plugin-next', 'eslint-config-next']);
|
||||
const devOnlyApprovedOrigin = (name, visited = new Set()) => {
|
||||
const finding = report.vulnerabilities[name];
|
||||
if (!allowedChain.has(name) || visited.has(name) || finding?.severity !== 'high' ||
|
||||
!Array.isArray(finding.nodes) || !finding.nodes.length ||
|
||||
finding.nodes.some(node => lock.packages[node]?.dev !== true || name === 'braces' && lock.packages[node]?.version !== '3.0.3') ||
|
||||
!Array.isArray(finding.via) || !finding.via.length) return false;
|
||||
const path = new Set(visited).add(name);
|
||||
return finding.via.every(origin => typeof origin === 'string' ? devOnlyApprovedOrigin(origin, path) :
|
||||
origin?.name === 'braces' && origin.dependency === 'braces' && origin.severity === 'high' && origin.url === 'https://github.com/advisories/GHSA-vfj7-8cjw-p6xm');
|
||||
};
|
||||
for (const [name, finding] of Object.entries(report.vulnerabilities)) {
|
||||
if (!['info', 'low', 'moderate', 'high', 'critical'].includes(finding.severity)) {
|
||||
failures.push(`${name}: invalid severity`);
|
||||
} else if (['high', 'critical'].includes(finding.severity)) {
|
||||
// Owner-approved on 17 October: no upstream fix; trusted lint patterns only, never production dependencies.
|
||||
if (Number.isFinite(now) && now < Date.parse('2026-11-01T00:00:00Z') && devOnlyApprovedOrigin(name)) accepted.push(name);
|
||||
else failures.push(`${name}: ${finding.severity}`);
|
||||
}
|
||||
}
|
||||
return { accepted, failures };
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
try {
|
||||
const result = spawnSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['audit', '--json', '--audit-level=high'], {
|
||||
encoding: 'utf8', maxBuffer: 8 * 1024 * 1024, shell: process.platform === 'win32',
|
||||
});
|
||||
fs.mkdirSync('.artifacts', { recursive: true });
|
||||
fs.writeFileSync('.artifacts/frontend-audit.json', result.stdout || '');
|
||||
if (result.error || result.signal || ![0, 1].includes(result.status)) throw new Error('npm audit did not complete');
|
||||
const report = JSON.parse(result.stdout);
|
||||
const decision = evaluateAudit(report, JSON.parse(fs.readFileSync('package-lock.json', 'utf8')));
|
||||
console.log(JSON.stringify({ report, ...decision,
|
||||
temporaryException: decision.accepted.length ? 'GHSA-vfj7-8cjw-p6xm: dev-only; expires after 2026-10-31 UTC' : null }, null, 2));
|
||||
process.exitCode = decision.failures.length ? 1 : 0;
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user