diff --git a/build-tools/apply-finding-overrides/apply-finding-overrides.ts b/build-tools/apply-finding-overrides/apply-finding-overrides.ts new file mode 100644 index 0000000..127f40a --- /dev/null +++ b/build-tools/apply-finding-overrides/apply-finding-overrides.ts @@ -0,0 +1,706 @@ +// apply-finding-overrides.ts +// +// Consumes AWS Inspector SBOM scan results and generates quilt patches +// to override vulnerable npm packages. Also prunes stale patches after +// upstream updates. +// +// Usage: +// npx ts-node apply-finding-overrides.ts apply [--dry-run] ... +// npx ts-node apply-finding-overrides.ts prune [--dry-run] + +import { readFileSync, writeFileSync, readdirSync, unlinkSync, existsSync } from "fs"; +import { join, dirname, relative, resolve } from "path"; +import { execSync } from "child_process"; +import semver from "semver"; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +interface ScanAffect { + fixed_version: string; + installed_version: string; // purl like "pkg:npm/undici@7.19.0" +} + +interface ScanVulnerability { + id: string; + severity: string; + related?: string[]; + affects: ScanAffect[]; +} + +interface ScanResult { + sbom: { + vulnerabilities?: ScanVulnerability[]; + }; +} + +interface ConsolidatedFinding { + packageName: string; + currentVersion: string; + fixedVersion: string; + findingIds: string[]; // CVE + GHSA ids with severity + highestSeverity: string; +} + +interface AffectedFile { + packageJsonPath: string; // relative to code-editor-src/ + depType: "direct" | "transitive"; + depSection?: "dependencies" | "devDependencies"; +} + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const ROOT = resolve(__dirname, "../.."); +const PATCHED_SRC = join(ROOT, "code-editor-src"); +const UPSTREAM_SRC = join(ROOT, "third-party-src"); +const PATCHES_DIR = join(ROOT, "patches"); +const COMMON_PATCHES = join(PATCHES_DIR, "common"); +const SCRIPT_PATH = "build-tools/apply-finding-overrides/apply-finding-overrides.ts"; + +const SEVERITY_RANK: Record = { + critical: 4, + high: 3, + medium: 2, + other: 1, + low: 0, +}; + +const PATCH_PREFIX = "finding-override-"; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/** Parse purl like "pkg:npm/undici@7.19.0" or "pkg:npm/%40scope/name@1.0.0" */ +function parsePurl(purl: string): { name: string; version: string } | null { + const match = purl.match(/^pkg:npm\/(.+)@(.+)$/); + if (!match) return null; + const name = decodeURIComponent(match[1]); + return { name, version: match[2] }; +} + +/** Sanitize package name for use in filenames: @scope/name → scope-name */ +function sanitizePkgName(name: string): string { + return name.replace(/^@/, "").replace(/\//g, "-"); +} + +/** Strip version range prefix: "^7.24.0" → "7.24.0" */ +function stripRange(spec: string): string { + const coerced = semver.coerce(spec); + return coerced ? coerced.version : spec.replace(/^[\^~>=<\s]+/, ""); +} + +function findAllLockfiles(baseDir: string): string[] { + const results: string[] = []; + function walk(dir: string) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + if (entry.name === "node_modules") continue; + const full = join(dir, entry.name); + if (entry.isDirectory()) { + walk(full); + } else if (entry.name === "package-lock.json") { + results.push(full); + } + } + } + walk(baseDir); + return results; +} + +function getSeriesFiles(): string[] { + return readdirSync(PATCHES_DIR) + .filter((f) => f.endsWith(".series")) + .map((f) => join(PATCHES_DIR, f)); +} + +function readJson(path: string): any { + return JSON.parse(readFileSync(path, "utf-8")); +} + +/** Insert line into series file after the last common/finding-overrides.diff or last common/ line */ +function insertIntoSeries(seriesPath: string, patchEntry: string) { + const lines = readFileSync(seriesPath, "utf-8").split("\n"); + let insertIdx = -1; + for (let i = 0; i < lines.length; i++) { + if (lines[i] === "common/finding-overrides.diff") { + insertIdx = i + 1; + break; + } + } + if (insertIdx === -1) { + for (let i = lines.length - 1; i >= 0; i--) { + if (lines[i].startsWith("common/")) { + insertIdx = i + 1; + break; + } + } + } + if (insertIdx === -1) insertIdx = lines.length; + if (lines.includes(patchEntry)) return; + lines.splice(insertIdx, 0, patchEntry); + writeFileSync(seriesPath, lines.join("\n")); +} + +function removeFromSeries(seriesPath: string, patchEntry: string) { + const content = readFileSync(seriesPath, "utf-8"); + const lines = content.split("\n").filter((l) => l !== patchEntry); + writeFileSync(seriesPath, lines.join("\n")); +} + +// --------------------------------------------------------------------------- +// Apply command +// --------------------------------------------------------------------------- + +function parseFindings(scanFiles: string[]): Map { + const findings = new Map(); + + for (const file of scanFiles) { + const data: ScanResult = readJson(file); + const vulns = data.sbom?.vulnerabilities ?? []; + + for (const vuln of vulns) { + if (SEVERITY_RANK[vuln.severity] === undefined) continue; + if (SEVERITY_RANK[vuln.severity] < SEVERITY_RANK["medium"]) continue; + + for (const affect of vuln.affects) { + const parsed = parsePurl(affect.installed_version); + if (!parsed) continue; + + const ids = [vuln.id, ...(vuln.related ?? [])]; + const existing = findings.get(parsed.name); + + if (existing) { + for (const id of ids) { + const entry = `${id} (${vuln.severity})`; + if (!existing.findingIds.includes(entry)) { + existing.findingIds.push(entry); + } + } + if (!semver.gte(existing.fixedVersion, affect.fixed_version)) { + existing.fixedVersion = affect.fixed_version; + } + if ( + SEVERITY_RANK[vuln.severity] > + SEVERITY_RANK[existing.highestSeverity] + ) { + existing.highestSeverity = vuln.severity; + } + } else { + findings.set(parsed.name, { + packageName: parsed.name, + currentVersion: parsed.version, + fixedVersion: affect.fixed_version, + findingIds: ids.map((id) => `${id} (${vuln.severity})`), + highestSeverity: vuln.severity, + }); + } + } + } + } + + return findings; +} + +function discoverAffectedFiles( + packageName: string, + lockfiles: string[] +): AffectedFile[] { + const affected: AffectedFile[] = []; + + for (const lockfile of lockfiles) { + const lock = readJson(lockfile); + const packages = lock.packages ?? {}; + + const found = Object.keys(packages).some( + (key) => + key === `node_modules/${packageName}` || + key.endsWith(`/node_modules/${packageName}`) + ); + if (!found) continue; + + const pkgJsonPath = join(dirname(lockfile), "package.json"); + if (!existsSync(pkgJsonPath)) continue; + + const pkgJson = readJson(pkgJsonPath); + const relPath = relative(PATCHED_SRC, pkgJsonPath); + + if (pkgJson.dependencies?.[packageName]) { + affected.push({ + packageJsonPath: relPath, + depType: "direct", + depSection: "dependencies", + }); + } else if (pkgJson.devDependencies?.[packageName]) { + affected.push({ + packageJsonPath: relPath, + depType: "direct", + depSection: "devDependencies", + }); + } else { + affected.push({ packageJsonPath: relPath, depType: "transitive" }); + } + } + + return affected; +} + +function isAlreadyFixed( + pkgJsonPath: string, + packageName: string, + fixedVersion: string +): boolean { + const pkgJson = readJson(pkgJsonPath); + + for (const section of ["dependencies", "devDependencies"] as const) { + const spec = pkgJson[section]?.[packageName]; + if (spec && semver.gte(stripRange(spec), fixedVersion)) return true; + } + + const overrideSpec = pkgJson.overrides?.[packageName]; + if (overrideSpec && typeof overrideSpec === "string") { + if (semver.gte(stripRange(overrideSpec), fixedVersion)) return true; + } + + return false; +} + +function applyFix( + pkgJsonAbsPath: string, + af: AffectedFile, + packageName: string, + fixedVersion: string +) { + const content = readFileSync(pkgJsonAbsPath, "utf-8"); + const versionSpec = `^${fixedVersion}`; + + const indentMatch = content.match(/^(\t+|\s{2,})"/m); + const indent = indentMatch ? indentMatch[1] : " "; + const indent2 = indent + indent; + + if (af.depType === "direct" && af.depSection) { + const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const re = new RegExp(`("${escaped}"\\s*:\\s*)"[^"]*"`); + const updated = content.replace(re, `$1"${versionSpec}"`); + if (updated === content) { + console.error( + ` WARNING: Could not find ${packageName} in ${af.depSection} of ${af.packageJsonPath}` + ); + return; + } + writeFileSync(pkgJsonAbsPath, updated); + } else { + const pkgJson = JSON.parse(content); + + if (!pkgJson.overrides) { + const insertAfter = + content.lastIndexOf('"devDependencies"') !== -1 + ? '"devDependencies"' + : '"dependencies"'; + const sectionStart = content.indexOf(insertAfter); + if (sectionStart === -1) { + console.error( + ` WARNING: Cannot find insertion point in ${af.packageJsonPath}` + ); + return; + } + let depth = 0; + let i = content.indexOf("{", sectionStart); + for (; i < content.length; i++) { + if (content[i] === "{") depth++; + if (content[i] === "}") depth--; + if (depth === 0) break; + } + const insertPoint = i + 1; + const overridesBlock = `,\n${indent}"overrides": {\n${indent2}"${packageName}": "${versionSpec}"\n${indent}}`; + const updated = + content.slice(0, insertPoint) + + overridesBlock + + content.slice(insertPoint); + writeFileSync(pkgJsonAbsPath, updated); + } else if (pkgJson.overrides[packageName]) { + const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const re = new RegExp(`("${escaped}"\\s*:\\s*)"[^"]*"`); + const updated = content.replace(re, `$1"${versionSpec}"`); + writeFileSync(pkgJsonAbsPath, updated); + } else { + const overridesIdx = content.indexOf('"overrides"'); + const braceIdx = content.indexOf("{", overridesIdx); + const insertPoint = braceIdx + 1; + const newEntry = `\n${indent2}"${packageName}": "${versionSpec}",`; + const updated = + content.slice(0, insertPoint) + newEntry + content.slice(insertPoint); + writeFileSync(pkgJsonAbsPath, updated); + } + } +} + +function runApply(scanFiles: string[], dryRun: boolean) { + if (!existsSync(PATCHED_SRC)) { + console.error( + "Error: code-editor-src/ does not exist. Run prepare-src.sh first." + ); + process.exit(1); + } + + const findings = parseFindings(scanFiles); + if (findings.size === 0) { + console.log("No actionable findings found in scan results."); + return; + } + + const lockfiles = findAllLockfiles(PATCHED_SRC); + const seriesFiles = getSeriesFiles(); + let patchCount = 0; + + const primarySeries = seriesFiles[0]; + if (!primarySeries) { + console.error("Error: No series files found in patches/"); + process.exit(1); + } + + for (const [, finding] of findings) { + console.log(`\nPackage: ${finding.packageName}`); + console.log( + ` Current: ${finding.currentVersion} → Fixed: >= ${finding.fixedVersion}` + ); + console.log(` Findings: ${finding.findingIds.join(", ")}`); + + const affected = discoverAffectedFiles(finding.packageName, lockfiles); + if (affected.length === 0) { + console.log( + " WARNING: Package not found in any package-lock.json, skipping" + ); + continue; + } + + const needsFix = affected.filter( + (af) => + !isAlreadyFixed( + join(PATCHED_SRC, af.packageJsonPath), + finding.packageName, + finding.fixedVersion + ) + ); + + if (needsFix.length === 0) { + console.log(" Already fixed in all affected files, skipping"); + continue; + } + + for (const af of needsFix) { + const action = + af.depType === "direct" + ? `${af.depSection} → version update` + : "transitive → override"; + console.log(` ${af.packageJsonPath} (${action})`); + } + + const sanitized = sanitizePkgName(finding.packageName); + const patchName = `common/${PATCH_PREFIX}${sanitized}.diff`; + const patchFile = join(PATCHES_DIR, patchName); + + if (dryRun) { + console.log(` Would create patch: ${patchName}`); + patchCount++; + continue; + } + + const patchExists = existsSync(patchFile); + + const quiltEnv = { + ...process.env, + QUILT_PATCHES: PATCHES_DIR, + QUILT_SERIES: primarySeries, + }; + const execOpts = { cwd: PATCHED_SRC, env: quiltEnv }; + + if (patchExists) { + try { + execSync(`quilt pop -q common/finding-override-${sanitized}.diff`, { + ...execOpts, + stdio: "pipe", + }); + execSync(`quilt push -q common/finding-override-${sanitized}.diff`, { + ...execOpts, + stdio: "pipe", + }); + } catch { + // Best effort + } + } else { + try { + execSync("quilt pop -q common/finding-overrides.diff", { + ...execOpts, + stdio: "pipe", + }); + } catch { + try { + execSync("quilt pop -qa", { ...execOpts, stdio: "pipe" }); + } catch { + // Already fully popped + } + } + execSync(`quilt new ${patchName}`, { ...execOpts, stdio: "pipe" }); + } + + const findingIdsClean = finding.findingIds + .map((f) => f.replace(/ \([^)]+\)/, "")) + .filter((v, i, a) => a.indexOf(v) === i); + const header = [ + `Auto-generated by ${SCRIPT_PATH}`, + `Affected package: ${finding.packageName}`, + `Fixed version: >= ${finding.fixedVersion}`, + `Findings: ${findingIdsClean.join(", ")}`, + `Removal condition: Upstream updates ${finding.packageName} to >= ${finding.fixedVersion}`, + ].join("\n"); + + for (const af of needsFix) { + const absPath = join(PATCHED_SRC, af.packageJsonPath); + try { + execSync(`quilt add ${af.packageJsonPath}`, { + ...execOpts, + stdio: "pipe", + }); + } catch { + // File may already be tracked by this patch + } + applyFix(absPath, af, finding.packageName, finding.fixedVersion); + } + + execSync(`quilt refresh -p ab --no-timestamps`, { + ...execOpts, + stdio: "pipe", + }); + + const patchContent = readFileSync(patchFile, "utf-8"); + writeFileSync(patchFile, header + "\n\n" + patchContent); + + try { + execSync("quilt push -a", { ...execOpts, stdio: "pipe" }); + } catch { + // May already be fully applied + } + + if (!patchExists) { + for (const sf of seriesFiles) { + if (sf === primarySeries) continue; + insertIntoSeries(sf, patchName); + } + } + + console.log(` Patch: ${patchName}`); + patchCount++; + } + + console.log(`\n=== Summary ===`); + console.log( + `${dryRun ? "Would create" : "Created"} ${patchCount} patch(es)` + ); + if (!dryRun && patchCount > 0) { + console.log(`\nNext steps:`); + console.log( + ` 1. Run update-package-locks.sh in Docker to regenerate lock files` + ); + console.log( + ` 2. Verify with: ./scripts/security-scan.sh scan-main-dependencies ` + ); + } +} + +// --------------------------------------------------------------------------- +// Prune command +// --------------------------------------------------------------------------- + +interface PatchMeta { + file: string; + patchName: string; + packageName: string; + fixedVersion: string; +} + +function parsePatchHeader(patchFile: string): PatchMeta | null { + const content = readFileSync(patchFile, "utf-8"); + const pkgMatch = content.match(/^Affected package:\s*(.+)$/m); + const verMatch = content.match(/^Fixed version:\s*>=\s*(.+)$/m); + if (!pkgMatch || !verMatch) return null; + + const basename = relative(COMMON_PATCHES, patchFile); + return { + file: patchFile, + patchName: `common/${basename}`, + packageName: pkgMatch[1].trim(), + fixedVersion: verMatch[1].trim(), + }; +} + +function getUpstreamVersions( + packageName: string, + lockfiles: string[] +): { lockfile: string; version: string }[] { + const results: { lockfile: string; version: string }[] = []; + + for (const lockfile of lockfiles) { + const lock = readJson(lockfile); + const packages = lock.packages ?? {}; + + for (const [key, value] of Object.entries(packages) as [string, any][]) { + if ( + key === `node_modules/${packageName}` || + key.endsWith(`/node_modules/${packageName}`) + ) { + if (value.version) { + results.push({ + lockfile: relative(ROOT, lockfile), + version: value.version, + }); + } + } + } + } + + return results; +} + +function runPrune(dryRun: boolean) { + if (!existsSync(UPSTREAM_SRC)) { + console.error("Error: third-party-src/ does not exist."); + process.exit(1); + } + + const patchFiles = existsSync(COMMON_PATCHES) + ? readdirSync(COMMON_PATCHES) + .filter( + (f) => f.startsWith(PATCH_PREFIX) && f.endsWith(".diff") + ) + .map((f) => join(COMMON_PATCHES, f)) + : []; + + if (patchFiles.length === 0) { + console.log("No auto-generated finding override patches found."); + return; + } + + const upstreamLockfiles = findAllLockfiles(UPSTREAM_SRC); + const seriesFiles = getSeriesFiles(); + let removedCount = 0; + let keptCount = 0; + + console.log("=== Finding Override Patch Status ===\n"); + + for (const patchFile of patchFiles) { + const meta = parsePatchHeader(patchFile); + if (!meta) { + console.log( + `SKIP: ${relative(PATCHES_DIR, patchFile)} (missing header fields)\n` + ); + continue; + } + + const upstreamVersions = getUpstreamVersions( + meta.packageName, + upstreamLockfiles + ); + + console.log(`${meta.patchName}`); + console.log(` Affected package: ${meta.packageName}`); + console.log(` Required fixed version: >= ${meta.fixedVersion}`); + + if (upstreamVersions.length === 0) { + console.log(` Upstream: package no longer present (removed upstream)`); + if (dryRun) { + console.log(` → Would remove (package dropped upstream)\n`); + } else { + unlinkSync(patchFile); + for (const sf of seriesFiles) { + removeFromSeries(sf, meta.patchName); + } + console.log( + ` → Removed patch, updated ${seriesFiles.length} series files\n` + ); + } + removedCount++; + continue; + } + + let allFixed = true; + for (const uv of upstreamVersions) { + const fixed = semver.gte(uv.version, meta.fixedVersion); + const icon = fixed ? "✅" : "❌"; + console.log( + ` ${uv.lockfile}: ${uv.version} (${icon} ${fixed ? ">=" : "<"} ${meta.fixedVersion})` + ); + if (!fixed) allFixed = false; + } + + if (allFixed) { + if (dryRun) { + console.log(` → Would remove (upstream is fixed)\n`); + } else { + unlinkSync(patchFile); + for (const sf of seriesFiles) { + removeFromSeries(sf, meta.patchName); + } + console.log( + ` → Removed patch, updated ${seriesFiles.length} series files\n` + ); + } + removedCount++; + } else { + console.log(` → Kept (still needed)\n`); + keptCount++; + } + } + + console.log(`=== Summary ===`); + console.log( + `${dryRun ? "Would remove" : "Removed"} ${removedCount} stale patch(es), kept ${keptCount} current patch(es).` + ); + if (!dryRun && removedCount > 0) { + console.log(`\nNext steps:`); + console.log( + ` 1. Run update-package-locks.sh in Docker to regenerate lock files` + ); + console.log(` 2. Verify build: ./scripts/prepare-src.sh `); + } +} + +// --------------------------------------------------------------------------- +// CLI +// --------------------------------------------------------------------------- + +function main() { + const args = process.argv.slice(2); + const command = args[0]; + + if (!command || !["apply", "prune"].includes(command)) { + console.error( + `Usage:\n npx ts-node ${SCRIPT_PATH} apply [--dry-run] ...\n npx ts-node ${SCRIPT_PATH} prune [--dry-run]` + ); + process.exit(1); + } + + const rest = args.slice(1); + const dryRun = rest.includes("--dry-run"); + const files = rest.filter((a) => a !== "--dry-run"); + + if (command === "apply") { + if (files.length === 0) { + console.error("Error: At least one scan result file is required."); + process.exit(1); + } + for (const f of files) { + if (!existsSync(f)) { + console.error(`Error: File not found: ${f}`); + process.exit(1); + } + } + runApply(files, dryRun); + } else { + runPrune(dryRun); + } +} + +main(); diff --git a/build-tools/apply-finding-overrides/package-lock.json b/build-tools/apply-finding-overrides/package-lock.json new file mode 100644 index 0000000..60cf144 --- /dev/null +++ b/build-tools/apply-finding-overrides/package-lock.json @@ -0,0 +1,238 @@ +{ + "name": "apply-finding-overrides", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "apply-finding-overrides", + "version": "1.0.0", + "dependencies": { + "semver": "^7.7.1", + "ts-node": "^10.9.2", + "typescript": "^5.7.3" + }, + "devDependencies": { + "@types/node": "^22.13.10", + "@types/semver": "^7.5.8" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "license": "MIT" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/build-tools/apply-finding-overrides/package.json b/build-tools/apply-finding-overrides/package.json new file mode 100644 index 0000000..e46c8cb --- /dev/null +++ b/build-tools/apply-finding-overrides/package.json @@ -0,0 +1,20 @@ +{ + "name": "apply-finding-overrides", + "version": "1.0.0", + "private": true, + "description": "Consumes AWS Inspector SBOM scan results and generates quilt patches to override vulnerable npm packages", + "type": "module", + "scripts": { + "apply": "ts-node apply-finding-overrides.ts apply", + "prune": "ts-node apply-finding-overrides.ts prune" + }, + "dependencies": { + "semver": "^7.7.1", + "ts-node": "^10.9.2", + "typescript": "^5.7.3" + }, + "devDependencies": { + "@types/node": "^22.13.10", + "@types/semver": "^7.5.8" + } +} diff --git a/build-tools/apply-finding-overrides/tsconfig.json b/build-tools/apply-finding-overrides/tsconfig.json new file mode 100644 index 0000000..9825761 --- /dev/null +++ b/build-tools/apply-finding-overrides/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true + } +}