autoremediator 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/api/change-request/index.d.ts +2 -1
- package/dist/api/change-request/index.d.ts.map +1 -1
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/patches/index.d.ts +1 -0
- package/dist/api/patches/index.d.ts.map +1 -1
- package/dist/api/patches/reconcile.d.ts +7 -0
- package/dist/api/patches/reconcile.d.ts.map +1 -0
- package/dist/api/patches/reconcile.test.d.ts +2 -0
- package/dist/api/patches/reconcile.test.d.ts.map +1 -0
- package/dist/api/remediate/validators.d.ts +3 -0
- package/dist/api/remediate/validators.d.ts.map +1 -1
- package/dist/{chunk-U4LIJIGD.js → chunk-FXOUJDQ3.js} +1712 -856
- package/dist/cli/program.d.ts.map +1 -1
- package/dist/cli/runners.d.ts +5 -0
- package/dist/cli/runners.d.ts.map +1 -1
- package/dist/cli/triage.d.ts +7 -0
- package/dist/cli/triage.d.ts.map +1 -0
- package/dist/cli/types.d.ts +2 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli.js +220 -3
- package/dist/index.js +1707 -852
- package/dist/index.js.map +1 -1
- package/dist/intelligence/sources/github-advisory.d.ts.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/openapi/server.js +1 -1
- package/dist/platform/package-manager/index.d.ts +1 -0
- package/dist/platform/package-manager/index.d.ts.map +1 -1
- package/dist/platform/supply-chain.d.ts +10 -0
- package/dist/platform/supply-chain.d.ts.map +1 -0
- package/dist/platform/supply-chain.test.d.ts +2 -0
- package/dist/platform/supply-chain.test.d.ts.map +1 -0
- package/dist/platform/types.d.ts +38 -4
- package/dist/platform/types.d.ts.map +1 -1
- package/dist/platform/workspace.d.ts +28 -0
- package/dist/platform/workspace.d.ts.map +1 -0
- package/dist/platform/workspace.test.d.ts +2 -0
- package/dist/platform/workspace.test.d.ts.map +1 -0
- package/dist/remediation/local/options.d.ts.map +1 -1
- package/dist/remediation/local/primary-strategy.d.ts +3 -1
- package/dist/remediation/local/primary-strategy.d.ts.map +1 -1
- package/dist/remediation/strategies/patch-synthesis-prompt.d.ts.map +1 -1
- package/dist/remediation/tools/apply-patch-file/index.d.ts +1 -0
- package/dist/remediation/tools/apply-patch-file/index.d.ts.map +1 -1
- package/dist/remediation/tools/apply-version-bump.d.ts +1 -0
- package/dist/remediation/tools/apply-version-bump.d.ts.map +1 -1
- package/dist/remediation/tools/check-inventory.d.ts.map +1 -1
- package/dist/remediation/tools/check-reachability.d.ts +6 -1
- package/dist/remediation/tools/check-reachability.d.ts.map +1 -1
- package/dist/remediation/tools/generate-patch/helpers.d.ts +1 -0
- package/dist/remediation/tools/generate-patch/helpers.d.ts.map +1 -1
- package/dist/remediation/tools/generate-patch/index.d.ts +1 -0
- package/dist/remediation/tools/generate-patch/index.d.ts.map +1 -1
- package/dist/scanner/adapters/npm-audit.d.ts.map +1 -1
- package/dist/scanner/adapters/yarn-audit.d.ts.map +1 -1
- package/dist/scanner/delta-scan.d.ts.map +1 -1
- package/dist/scanner/lockfile-diff.d.ts +10 -0
- package/dist/scanner/lockfile-diff.d.ts.map +1 -0
- package/dist/scanner/lockfile-diff.test.d.ts +2 -0
- package/dist/scanner/lockfile-diff.test.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -463,12 +463,12 @@ import { readdir, readFile as readFile2, stat } from "fs/promises";
|
|
|
463
463
|
import { join as join5 } from "path";
|
|
464
464
|
|
|
465
465
|
// src/remediation/strategies/patch-utils.ts
|
|
466
|
-
import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as
|
|
466
|
+
import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as readFileSync3 } from "fs";
|
|
467
467
|
import { join as join4 } from "path";
|
|
468
468
|
import { execa as execa2 } from "execa";
|
|
469
469
|
|
|
470
470
|
// src/platform/package-manager/index.ts
|
|
471
|
-
import { existsSync as existsSync2 } from "fs";
|
|
471
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
472
472
|
import { join as join3 } from "path";
|
|
473
473
|
import { execa } from "execa";
|
|
474
474
|
|
|
@@ -661,6 +661,32 @@ function resolveTestCommand(pm, constraints) {
|
|
|
661
661
|
const base = pm === "pnpm" ? ["pnpm", "test"] : pm === "yarn" ? ["yarn", "test"] : pm === "bun" ? ["bun", "test"] : pm === "deno" ? ["deno", "test"] : ["npm", "test"];
|
|
662
662
|
return withWorkspace(base, pm, constraints?.workspace);
|
|
663
663
|
}
|
|
664
|
+
function resolveTypecheckCommand(pm, constraints, cwd = process.cwd()) {
|
|
665
|
+
const pkgJsonPath = join3(cwd, "package.json");
|
|
666
|
+
let hasTypecheckScript = false;
|
|
667
|
+
const hasTsConfig = existsSync2(join3(cwd, "tsconfig.json"));
|
|
668
|
+
if (existsSync2(pkgJsonPath)) {
|
|
669
|
+
try {
|
|
670
|
+
const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf8"));
|
|
671
|
+
if (pkg.scripts?.typecheck) {
|
|
672
|
+
hasTypecheckScript = true;
|
|
673
|
+
}
|
|
674
|
+
} catch {
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (hasTypecheckScript) {
|
|
678
|
+
const base = [pm, "run", "typecheck"];
|
|
679
|
+
return withWorkspace(base, pm, constraints?.workspace);
|
|
680
|
+
}
|
|
681
|
+
if (hasTsConfig) {
|
|
682
|
+
if (pm === "deno") {
|
|
683
|
+
return withWorkspace(["deno", "check"], pm, constraints?.workspace);
|
|
684
|
+
}
|
|
685
|
+
const base = ["npx", "tsc", "--noEmit"];
|
|
686
|
+
return withWorkspace(base, pm, constraints?.workspace);
|
|
687
|
+
}
|
|
688
|
+
return [];
|
|
689
|
+
}
|
|
664
690
|
function resolveAuditCommand(pm, constraints) {
|
|
665
691
|
if (pm === "deno") {
|
|
666
692
|
throw new Error(
|
|
@@ -905,12 +931,12 @@ function defineTool(config) {
|
|
|
905
931
|
|
|
906
932
|
// src/remediation/tools/check-inventory.ts
|
|
907
933
|
import { z } from "zod";
|
|
908
|
-
import { readFileSync as
|
|
909
|
-
import { join as
|
|
934
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
935
|
+
import { join as join8 } from "path";
|
|
910
936
|
import { execa as execa3 } from "execa";
|
|
911
937
|
|
|
912
938
|
// src/platform/policy.ts
|
|
913
|
-
import { existsSync as existsSync6, readFileSync as
|
|
939
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
914
940
|
import { join as join6 } from "path";
|
|
915
941
|
import { parse as yamlParse } from "yaml";
|
|
916
942
|
var DEFAULT_POLICY = {
|
|
@@ -945,7 +971,7 @@ function loadPolicy(cwd, explicitPath) {
|
|
|
945
971
|
const candidate = explicitPath ?? join6(cwd, ".github", "autoremediator.yml");
|
|
946
972
|
if (!existsSync6(candidate)) return DEFAULT_POLICY;
|
|
947
973
|
try {
|
|
948
|
-
const parsed = yamlParse(
|
|
974
|
+
const parsed = yamlParse(readFileSync4(candidate, "utf8"));
|
|
949
975
|
return {
|
|
950
976
|
allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
|
|
951
977
|
denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
|
|
@@ -1006,7 +1032,7 @@ function isActiveSuppression(suppression) {
|
|
|
1006
1032
|
}
|
|
1007
1033
|
function loadSuppressionsFile(filePath) {
|
|
1008
1034
|
try {
|
|
1009
|
-
const content =
|
|
1035
|
+
const content = readFileSync4(filePath, "utf8");
|
|
1010
1036
|
const parsed = yamlParse(content);
|
|
1011
1037
|
return Array.isArray(parsed?.suppressions) ? parsed.suppressions : [];
|
|
1012
1038
|
} catch {
|
|
@@ -1032,6 +1058,140 @@ function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
|
|
|
1032
1058
|
};
|
|
1033
1059
|
}
|
|
1034
1060
|
|
|
1061
|
+
// src/platform/workspace.ts
|
|
1062
|
+
import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync5, statSync } from "fs";
|
|
1063
|
+
import { join as join7, resolve as resolve2, relative } from "path";
|
|
1064
|
+
import yaml from "yaml";
|
|
1065
|
+
function findWorkspaceRoot(startDir) {
|
|
1066
|
+
let curr = resolve2(startDir);
|
|
1067
|
+
while (true) {
|
|
1068
|
+
if (existsSync7(join7(curr, "pnpm-workspace.yaml"))) return curr;
|
|
1069
|
+
const pkgPath = join7(curr, "package.json");
|
|
1070
|
+
if (existsSync7(pkgPath)) {
|
|
1071
|
+
try {
|
|
1072
|
+
const parsed = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
1073
|
+
if (parsed.workspaces) return curr;
|
|
1074
|
+
} catch {
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
const parent = resolve2(curr, "..");
|
|
1078
|
+
if (parent === curr) break;
|
|
1079
|
+
curr = parent;
|
|
1080
|
+
}
|
|
1081
|
+
return null;
|
|
1082
|
+
}
|
|
1083
|
+
function findWorkspacePackages(cwd) {
|
|
1084
|
+
const rootCwd = findWorkspaceRoot(cwd) ?? resolve2(cwd);
|
|
1085
|
+
const packages = [];
|
|
1086
|
+
const packageGlobs = [];
|
|
1087
|
+
const pnpmWorkspaceFile = join7(rootCwd, "pnpm-workspace.yaml");
|
|
1088
|
+
if (existsSync7(pnpmWorkspaceFile)) {
|
|
1089
|
+
try {
|
|
1090
|
+
const content = readFileSync5(pnpmWorkspaceFile, "utf8");
|
|
1091
|
+
const parsed = yaml.parse(content);
|
|
1092
|
+
if (Array.isArray(parsed?.packages)) {
|
|
1093
|
+
packageGlobs.push(...parsed.packages);
|
|
1094
|
+
}
|
|
1095
|
+
} catch {
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
const rootPkgFile = join7(rootCwd, "package.json");
|
|
1099
|
+
if (existsSync7(rootPkgFile)) {
|
|
1100
|
+
try {
|
|
1101
|
+
const rootPkg = JSON.parse(readFileSync5(rootPkgFile, "utf8"));
|
|
1102
|
+
if (Array.isArray(rootPkg.workspaces)) {
|
|
1103
|
+
packageGlobs.push(...rootPkg.workspaces);
|
|
1104
|
+
} else if (Array.isArray(rootPkg.workspaces?.packages)) {
|
|
1105
|
+
packageGlobs.push(...rootPkg.workspaces.packages);
|
|
1106
|
+
}
|
|
1107
|
+
} catch {
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
const denoJsonFile = join7(rootCwd, "deno.json");
|
|
1111
|
+
if (existsSync7(denoJsonFile)) {
|
|
1112
|
+
try {
|
|
1113
|
+
const denoJson = JSON.parse(readFileSync5(denoJsonFile, "utf8"));
|
|
1114
|
+
if (Array.isArray(denoJson.workspace)) {
|
|
1115
|
+
packageGlobs.push(...denoJson.workspace);
|
|
1116
|
+
}
|
|
1117
|
+
} catch {
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
if (packageGlobs.length === 0) {
|
|
1121
|
+
return packages;
|
|
1122
|
+
}
|
|
1123
|
+
const seenDirs = /* @__PURE__ */ new Set();
|
|
1124
|
+
for (const glob of packageGlobs) {
|
|
1125
|
+
const cleanGlob = glob.trim().replace(/\/+$/, "");
|
|
1126
|
+
if (!cleanGlob || cleanGlob.startsWith("!")) continue;
|
|
1127
|
+
if (cleanGlob.endsWith("/*")) {
|
|
1128
|
+
const parentDir = join7(rootCwd, cleanGlob.slice(0, -2));
|
|
1129
|
+
if (!existsSync7(parentDir)) continue;
|
|
1130
|
+
try {
|
|
1131
|
+
const entries = readdirSync(parentDir);
|
|
1132
|
+
for (const entry of entries) {
|
|
1133
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
1134
|
+
const subDir = join7(parentDir, entry);
|
|
1135
|
+
try {
|
|
1136
|
+
if (statSync(subDir).isDirectory()) {
|
|
1137
|
+
addWorkspacePackage(rootCwd, subDir, packages, seenDirs);
|
|
1138
|
+
}
|
|
1139
|
+
} catch {
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
} else {
|
|
1145
|
+
const directDir = join7(rootCwd, cleanGlob);
|
|
1146
|
+
if (existsSync7(directDir)) {
|
|
1147
|
+
try {
|
|
1148
|
+
if (statSync(directDir).isDirectory()) {
|
|
1149
|
+
addWorkspacePackage(rootCwd, directDir, packages, seenDirs);
|
|
1150
|
+
}
|
|
1151
|
+
} catch {
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
return packages;
|
|
1157
|
+
}
|
|
1158
|
+
function addWorkspacePackage(rootCwd, dir, out, seen) {
|
|
1159
|
+
const norm = resolve2(dir);
|
|
1160
|
+
if (seen.has(norm)) return;
|
|
1161
|
+
seen.add(norm);
|
|
1162
|
+
const manifestPath = join7(dir, "package.json");
|
|
1163
|
+
if (!existsSync7(manifestPath)) return;
|
|
1164
|
+
try {
|
|
1165
|
+
const content = readFileSync5(manifestPath, "utf8");
|
|
1166
|
+
const manifest = JSON.parse(content);
|
|
1167
|
+
out.push({
|
|
1168
|
+
name: manifest.name ?? relative(rootCwd, dir),
|
|
1169
|
+
relativeDir: relative(rootCwd, dir),
|
|
1170
|
+
absoluteDir: norm,
|
|
1171
|
+
manifestPath,
|
|
1172
|
+
manifest
|
|
1173
|
+
});
|
|
1174
|
+
} catch {
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
function findDeclaringWorkspacePackage(cwd, dependencyName, filterWorkspace) {
|
|
1178
|
+
const workspaces = findWorkspacePackages(cwd);
|
|
1179
|
+
if (workspaces.length === 0) return null;
|
|
1180
|
+
const matchesFilter = (wp) => {
|
|
1181
|
+
if (!filterWorkspace) return true;
|
|
1182
|
+
const filter = filterWorkspace.trim();
|
|
1183
|
+
return wp.name === filter || wp.relativeDir === filter || wp.relativeDir.replace(/\\/g, "/") === filter.replace(/\\/g, "/");
|
|
1184
|
+
};
|
|
1185
|
+
for (const wp of workspaces) {
|
|
1186
|
+
if (!matchesFilter(wp)) continue;
|
|
1187
|
+
const isDeclared = Boolean(wp.manifest.dependencies?.[dependencyName]) || Boolean(wp.manifest.devDependencies?.[dependencyName]) || Boolean(wp.manifest.peerDependencies?.[dependencyName]);
|
|
1188
|
+
if (isDeclared) {
|
|
1189
|
+
return wp;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return null;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1035
1195
|
// src/remediation/tools/check-inventory.ts
|
|
1036
1196
|
var checkInventoryTool = defineTool({
|
|
1037
1197
|
description: "Read the project's package.json and installed dependencies to list packages and exact versions. Must be called before checking version matches.",
|
|
@@ -1049,7 +1209,7 @@ var checkInventoryTool = defineTool({
|
|
|
1049
1209
|
}) => {
|
|
1050
1210
|
let pkgJson;
|
|
1051
1211
|
try {
|
|
1052
|
-
pkgJson = JSON.parse(
|
|
1212
|
+
pkgJson = JSON.parse(readFileSync6(join8(cwd, "package.json"), "utf8"));
|
|
1053
1213
|
} catch {
|
|
1054
1214
|
return {
|
|
1055
1215
|
packages: [],
|
|
@@ -1081,7 +1241,8 @@ var checkInventoryTool = defineTool({
|
|
|
1081
1241
|
}
|
|
1082
1242
|
const packages = [];
|
|
1083
1243
|
for (const [name, version] of installedVersions.entries()) {
|
|
1084
|
-
const
|
|
1244
|
+
const declaringWorkspace = findDeclaringWorkspacePackage(cwd, name, workspace);
|
|
1245
|
+
const isDirect = Boolean(pkgJson.dependencies?.[name]) || Boolean(pkgJson.devDependencies?.[name]) || Boolean(pkgJson.peerDependencies?.[name]) || Boolean(declaringWorkspace);
|
|
1085
1246
|
packages.push({
|
|
1086
1247
|
name,
|
|
1087
1248
|
version,
|
|
@@ -1204,173 +1365,8 @@ async function validatePatchArtifact(patchFilePath, options = {}) {
|
|
|
1204
1365
|
};
|
|
1205
1366
|
}
|
|
1206
1367
|
|
|
1207
|
-
// src/
|
|
1208
|
-
|
|
1209
|
-
const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "remote";
|
|
1210
|
-
if (raw !== "remote" && raw !== "local") {
|
|
1211
|
-
throw new Error(
|
|
1212
|
-
`Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
|
|
1213
|
-
);
|
|
1214
|
-
}
|
|
1215
|
-
return raw;
|
|
1216
|
-
}
|
|
1217
|
-
function resolveModelName(provider, options = {}, routing = {}) {
|
|
1218
|
-
if (options.model) return options.model;
|
|
1219
|
-
if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
|
|
1220
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1221
|
-
const policy = loadPolicy(cwd, options.policy);
|
|
1222
|
-
const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
|
|
1223
|
-
if (providerEnvModel) return providerEnvModel;
|
|
1224
|
-
const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
|
|
1225
|
-
if (policyPinnedModel) return policyPinnedModel;
|
|
1226
|
-
const defaults = {
|
|
1227
|
-
remote: "remote-default",
|
|
1228
|
-
local: "local"
|
|
1229
|
-
};
|
|
1230
|
-
const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
|
|
1231
|
-
const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
|
|
1232
|
-
if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
|
|
1233
|
-
return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
|
|
1234
|
-
}
|
|
1235
|
-
return defaults[provider];
|
|
1236
|
-
}
|
|
1237
|
-
async function loadRemoteFactory() {
|
|
1238
|
-
const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
|
|
1239
|
-
const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
|
|
1240
|
-
if (!moduleName) {
|
|
1241
|
-
throw new Error(
|
|
1242
|
-
"AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
|
|
1243
|
-
);
|
|
1244
|
-
}
|
|
1245
|
-
if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
|
|
1246
|
-
throw new Error(
|
|
1247
|
-
`AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1250
|
-
const loaded = await import(moduleName);
|
|
1251
|
-
const factory = loaded[exportName];
|
|
1252
|
-
if (typeof factory !== "function") {
|
|
1253
|
-
throw new Error(
|
|
1254
|
-
`Remote client factory "${exportName}" was not found in module "${moduleName}".`
|
|
1255
|
-
);
|
|
1256
|
-
}
|
|
1257
|
-
return factory;
|
|
1258
|
-
}
|
|
1259
|
-
async function createModel(options = {}, routing = {}) {
|
|
1260
|
-
const provider = resolveProvider(options);
|
|
1261
|
-
if (provider === "local") {
|
|
1262
|
-
throw new Error(
|
|
1263
|
-
"Local provider does not create a language model. Use the deterministic pipeline path instead."
|
|
1264
|
-
);
|
|
1265
|
-
}
|
|
1266
|
-
const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
|
|
1267
|
-
if (!apiKey) {
|
|
1268
|
-
throw new Error(
|
|
1269
|
-
"AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
|
|
1270
|
-
);
|
|
1271
|
-
}
|
|
1272
|
-
const modelName = resolveModelName(provider, options, routing);
|
|
1273
|
-
const createRemoteClient = await loadRemoteFactory();
|
|
1274
|
-
const remoteClient = createRemoteClient({ apiKey });
|
|
1275
|
-
return remoteClient(modelName);
|
|
1276
|
-
}
|
|
1277
|
-
function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed", riskLevel = "medium", overrides) {
|
|
1278
|
-
const relaxed = {
|
|
1279
|
-
remote: { low: 0.65, medium: 0.7, high: 0.8 },
|
|
1280
|
-
local: { low: 0.65, medium: 0.7, high: 0.8 }
|
|
1281
|
-
};
|
|
1282
|
-
const strict = {
|
|
1283
|
-
remote: { low: 0.8, medium: 0.85, high: 0.9 },
|
|
1284
|
-
local: { low: 0.8, medium: 0.85, high: 0.9 }
|
|
1285
|
-
};
|
|
1286
|
-
const baseThreshold = safetyProfile === "strict" ? strict[provider][riskLevel] : relaxed[provider][riskLevel];
|
|
1287
|
-
const override = overrides?.[riskLevel];
|
|
1288
|
-
if (typeof override === "number" && Number.isFinite(override)) {
|
|
1289
|
-
return Math.max(0, Math.min(1, override));
|
|
1290
|
-
}
|
|
1291
|
-
return baseThreshold;
|
|
1292
|
-
}
|
|
1293
|
-
function estimateModelCostUsd(params) {
|
|
1294
|
-
const inputTokens = params.promptChars / 4;
|
|
1295
|
-
const outputTokens = params.completionChars / 4;
|
|
1296
|
-
const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
|
|
1297
|
-
return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
|
|
1298
|
-
}
|
|
1299
|
-
function getNvdConfig() {
|
|
1300
|
-
return {
|
|
1301
|
-
apiKey: process.env.AUTOREMEDIATOR_NVD_API_KEY
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
function getGitHubToken() {
|
|
1305
|
-
return process.env.GITHUB_TOKEN;
|
|
1306
|
-
}
|
|
1307
|
-
function getIntelligenceSourceConfig() {
|
|
1308
|
-
return {
|
|
1309
|
-
gitLabAdvisoryApi: process.env.AUTOREMEDIATOR_GITLAB_ADVISORY_API ?? "https://advisories.gitlab.com/api/v1/advisories",
|
|
1310
|
-
certCcSearchUrl: process.env.AUTOREMEDIATOR_CERTCC_SEARCH_URL ?? "https://www.kb.cert.org/vuls/search",
|
|
1311
|
-
epssApi: process.env.AUTOREMEDIATOR_EPSS_API ?? "https://api.first.org/data/v1/epss",
|
|
1312
|
-
cveServicesApi: process.env.AUTOREMEDIATOR_CVE_SERVICES_API ?? "https://cveawg.mitre.org/api/cve",
|
|
1313
|
-
depsDevApi: process.env.AUTOREMEDIATOR_DEPSDEV_API ?? "https://api.deps.dev/v3",
|
|
1314
|
-
scorecardApi: process.env.AUTOREMEDIATOR_SCORECARD_API ?? "https://api.securityscorecards.dev",
|
|
1315
|
-
vendorAdvisoryFeeds: (process.env.AUTOREMEDIATOR_VENDOR_ADVISORY_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
|
|
1316
|
-
commercialFeeds: (process.env.AUTOREMEDIATOR_COMMERCIAL_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
|
|
1317
|
-
commercialFeedToken: process.env.AUTOREMEDIATOR_COMMERCIAL_FEED_TOKEN
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
// src/platform/idempotency.ts
|
|
1322
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
1323
|
-
import { join as join8 } from "path";
|
|
1324
|
-
var DEFAULT_INDEX = {
|
|
1325
|
-
schemaVersion: "1.0",
|
|
1326
|
-
entries: {}
|
|
1327
|
-
};
|
|
1328
|
-
function indexFilePath(cwd) {
|
|
1329
|
-
return join8(cwd, ".autoremediator", "state", "idempotency.json");
|
|
1330
|
-
}
|
|
1331
|
-
function entryKey(idempotencyKey, cveId) {
|
|
1332
|
-
return `${idempotencyKey}::${cveId.toUpperCase()}`;
|
|
1333
|
-
}
|
|
1334
|
-
function loadIndex(cwd) {
|
|
1335
|
-
const filePath = indexFilePath(cwd);
|
|
1336
|
-
if (!existsSync7(filePath)) return DEFAULT_INDEX;
|
|
1337
|
-
try {
|
|
1338
|
-
const parsed = JSON.parse(readFileSync5(filePath, "utf8"));
|
|
1339
|
-
if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
|
|
1340
|
-
return parsed;
|
|
1341
|
-
}
|
|
1342
|
-
return DEFAULT_INDEX;
|
|
1343
|
-
} catch {
|
|
1344
|
-
return DEFAULT_INDEX;
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
function saveIndex(cwd, index) {
|
|
1348
|
-
const filePath = indexFilePath(cwd);
|
|
1349
|
-
mkdirSync2(join8(cwd, ".autoremediator", "state"), { recursive: true });
|
|
1350
|
-
writeFileSync2(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
|
|
1351
|
-
}
|
|
1352
|
-
function readIdempotentReport(cwd, idempotencyKey, cveId) {
|
|
1353
|
-
const index = loadIndex(cwd);
|
|
1354
|
-
const key = entryKey(idempotencyKey, cveId);
|
|
1355
|
-
return index.entries[key]?.report;
|
|
1356
|
-
}
|
|
1357
|
-
function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
|
|
1358
|
-
const index = loadIndex(cwd);
|
|
1359
|
-
const key = entryKey(idempotencyKey, cveId);
|
|
1360
|
-
index.entries[key] = {
|
|
1361
|
-
key: idempotencyKey,
|
|
1362
|
-
cveId: cveId.toUpperCase(),
|
|
1363
|
-
report,
|
|
1364
|
-
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1365
|
-
};
|
|
1366
|
-
saveIndex(cwd, index);
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
// src/remediation/pipeline.ts
|
|
1370
|
-
import { generateText as generateText2 } from "ai";
|
|
1371
|
-
|
|
1372
|
-
// src/remediation/local/run.ts
|
|
1373
|
-
import semver5 from "semver";
|
|
1368
|
+
// src/remediation/evaluate-package.ts
|
|
1369
|
+
import semver2 from "semver";
|
|
1374
1370
|
|
|
1375
1371
|
// src/platform/http-client.ts
|
|
1376
1372
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
@@ -1552,27 +1548,755 @@ async function lookupCveOsv(cveId) {
|
|
|
1552
1548
|
return parseOsvVuln(vuln);
|
|
1553
1549
|
}
|
|
1554
1550
|
|
|
1555
|
-
// src/
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
};
|
|
1563
|
-
if (token) {
|
|
1564
|
-
headers["Authorization"] = `token ${token}`;
|
|
1551
|
+
// src/platform/config.ts
|
|
1552
|
+
function resolveProvider(options = {}) {
|
|
1553
|
+
const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "remote";
|
|
1554
|
+
if (raw !== "remote" && raw !== "local") {
|
|
1555
|
+
throw new Error(
|
|
1556
|
+
`Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
|
|
1557
|
+
);
|
|
1565
1558
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1559
|
+
return raw;
|
|
1560
|
+
}
|
|
1561
|
+
function resolveModelName(provider, options = {}, routing = {}) {
|
|
1562
|
+
if (options.model) return options.model;
|
|
1563
|
+
if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
|
|
1564
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1565
|
+
const policy = loadPolicy(cwd, options.policy);
|
|
1566
|
+
const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
|
|
1567
|
+
if (providerEnvModel) return providerEnvModel;
|
|
1568
|
+
const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
|
|
1569
|
+
if (policyPinnedModel) return policyPinnedModel;
|
|
1570
|
+
const defaults = {
|
|
1571
|
+
remote: "remote-default",
|
|
1572
|
+
local: "local"
|
|
1573
|
+
};
|
|
1574
|
+
const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
|
|
1575
|
+
const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
|
|
1576
|
+
if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
|
|
1577
|
+
return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
|
|
1578
|
+
}
|
|
1579
|
+
return defaults[provider];
|
|
1580
|
+
}
|
|
1581
|
+
async function loadRemoteFactory() {
|
|
1582
|
+
const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
|
|
1583
|
+
const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
|
|
1584
|
+
if (!moduleName) {
|
|
1585
|
+
throw new Error(
|
|
1586
|
+
"AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
|
|
1590
|
+
throw new Error(
|
|
1591
|
+
`AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
const loaded = await import(moduleName);
|
|
1595
|
+
const factory = loaded[exportName];
|
|
1596
|
+
if (typeof factory !== "function") {
|
|
1597
|
+
throw new Error(
|
|
1598
|
+
`Remote client factory "${exportName}" was not found in module "${moduleName}".`
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
return factory;
|
|
1602
|
+
}
|
|
1603
|
+
async function createModel(options = {}, routing = {}) {
|
|
1604
|
+
const provider = resolveProvider(options);
|
|
1605
|
+
if (provider === "local") {
|
|
1606
|
+
throw new Error(
|
|
1607
|
+
"Local provider does not create a language model. Use the deterministic pipeline path instead."
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
|
|
1611
|
+
if (!apiKey) {
|
|
1612
|
+
throw new Error(
|
|
1613
|
+
"AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
|
|
1614
|
+
);
|
|
1615
|
+
}
|
|
1616
|
+
const modelName = resolveModelName(provider, options, routing);
|
|
1617
|
+
const createRemoteClient = await loadRemoteFactory();
|
|
1618
|
+
const remoteClient = createRemoteClient({ apiKey });
|
|
1619
|
+
return remoteClient(modelName);
|
|
1620
|
+
}
|
|
1621
|
+
function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed", riskLevel = "medium", overrides) {
|
|
1622
|
+
const relaxed = {
|
|
1623
|
+
remote: { low: 0.65, medium: 0.7, high: 0.8 },
|
|
1624
|
+
local: { low: 0.65, medium: 0.7, high: 0.8 }
|
|
1625
|
+
};
|
|
1626
|
+
const strict = {
|
|
1627
|
+
remote: { low: 0.8, medium: 0.85, high: 0.9 },
|
|
1628
|
+
local: { low: 0.8, medium: 0.85, high: 0.9 }
|
|
1629
|
+
};
|
|
1630
|
+
const baseThreshold = safetyProfile === "strict" ? strict[provider][riskLevel] : relaxed[provider][riskLevel];
|
|
1631
|
+
const override = overrides?.[riskLevel];
|
|
1632
|
+
if (typeof override === "number" && Number.isFinite(override)) {
|
|
1633
|
+
return Math.max(0, Math.min(1, override));
|
|
1634
|
+
}
|
|
1635
|
+
return baseThreshold;
|
|
1636
|
+
}
|
|
1637
|
+
function estimateModelCostUsd(params) {
|
|
1638
|
+
const inputTokens = params.promptChars / 4;
|
|
1639
|
+
const outputTokens = params.completionChars / 4;
|
|
1640
|
+
const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
|
|
1641
|
+
return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
|
|
1642
|
+
}
|
|
1643
|
+
function getNvdConfig() {
|
|
1644
|
+
return {
|
|
1645
|
+
apiKey: process.env.AUTOREMEDIATOR_NVD_API_KEY
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
function getGitHubToken() {
|
|
1649
|
+
return process.env.GITHUB_TOKEN;
|
|
1650
|
+
}
|
|
1651
|
+
function getIntelligenceSourceConfig() {
|
|
1652
|
+
return {
|
|
1653
|
+
gitLabAdvisoryApi: process.env.AUTOREMEDIATOR_GITLAB_ADVISORY_API ?? "https://advisories.gitlab.com/api/v1/advisories",
|
|
1654
|
+
certCcSearchUrl: process.env.AUTOREMEDIATOR_CERTCC_SEARCH_URL ?? "https://www.kb.cert.org/vuls/search",
|
|
1655
|
+
epssApi: process.env.AUTOREMEDIATOR_EPSS_API ?? "https://api.first.org/data/v1/epss",
|
|
1656
|
+
cveServicesApi: process.env.AUTOREMEDIATOR_CVE_SERVICES_API ?? "https://cveawg.mitre.org/api/cve",
|
|
1657
|
+
depsDevApi: process.env.AUTOREMEDIATOR_DEPSDEV_API ?? "https://api.deps.dev/v3",
|
|
1658
|
+
scorecardApi: process.env.AUTOREMEDIATOR_SCORECARD_API ?? "https://api.securityscorecards.dev",
|
|
1659
|
+
vendorAdvisoryFeeds: (process.env.AUTOREMEDIATOR_VENDOR_ADVISORY_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
|
|
1660
|
+
commercialFeeds: (process.env.AUTOREMEDIATOR_COMMERCIAL_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
|
|
1661
|
+
commercialFeedToken: process.env.AUTOREMEDIATOR_COMMERCIAL_FEED_TOKEN
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// src/intelligence/sources/epss.ts
|
|
1666
|
+
async function fetchEpss(cveId) {
|
|
1667
|
+
const { epssApi } = getIntelligenceSourceConfig();
|
|
1668
|
+
if (!epssApi) return void 0;
|
|
1669
|
+
try {
|
|
1670
|
+
const url = new URL(epssApi);
|
|
1671
|
+
url.searchParams.set("cve", cveId);
|
|
1672
|
+
const res = await httpClient({ url: url.toString() });
|
|
1673
|
+
if (!res.ok) return void 0;
|
|
1674
|
+
const body = res.data;
|
|
1675
|
+
return body.data?.[0];
|
|
1676
|
+
} catch {
|
|
1677
|
+
return void 0;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
async function enrichWithEpss(details) {
|
|
1681
|
+
const row = await fetchEpss(details.id);
|
|
1682
|
+
if (!row) return details;
|
|
1683
|
+
const score = Number.parseFloat(row.epss);
|
|
1684
|
+
const percentile = Number.parseFloat(row.percentile);
|
|
1685
|
+
if (!Number.isFinite(score) || !Number.isFinite(percentile)) {
|
|
1686
|
+
return details;
|
|
1687
|
+
}
|
|
1688
|
+
details.epss = {
|
|
1689
|
+
score,
|
|
1690
|
+
percentile,
|
|
1691
|
+
date: row.date
|
|
1692
|
+
};
|
|
1693
|
+
return details;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// src/intelligence/sources/cisa-kev.ts
|
|
1697
|
+
var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
|
|
1698
|
+
async function fetchCisaKevFeed() {
|
|
1699
|
+
try {
|
|
1700
|
+
const res = await httpClient({ url: CISA_KEV_URL });
|
|
1701
|
+
if (!res.ok) return void 0;
|
|
1702
|
+
return res.data;
|
|
1703
|
+
} catch {
|
|
1704
|
+
return void 0;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
function findKevEntry(feed, cveId) {
|
|
1708
|
+
if (!feed?.vulnerabilities?.length) return void 0;
|
|
1709
|
+
const normalized = cveId.toUpperCase();
|
|
1710
|
+
return feed.vulnerabilities.find((v) => v.cveID.toUpperCase() === normalized);
|
|
1711
|
+
}
|
|
1712
|
+
async function enrichWithCisaKev(details) {
|
|
1713
|
+
const feed = await fetchCisaKevFeed();
|
|
1714
|
+
const entry = findKevEntry(feed, details.id);
|
|
1715
|
+
if (!entry) return details;
|
|
1716
|
+
details.kev = {
|
|
1717
|
+
knownExploited: true,
|
|
1718
|
+
dateAdded: entry.dateAdded,
|
|
1719
|
+
dueDate: entry.dueDate,
|
|
1720
|
+
requiredAction: entry.requiredAction,
|
|
1721
|
+
knownRansomwareCampaignUse: entry.knownRansomwareCampaignUse
|
|
1722
|
+
};
|
|
1723
|
+
if (!details.references.includes(CISA_KEV_URL)) {
|
|
1724
|
+
details.references.push(CISA_KEV_URL);
|
|
1725
|
+
}
|
|
1726
|
+
return details;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
// src/intelligence/sources/registry.ts
|
|
1730
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
1731
|
+
import { join as join9 } from "path";
|
|
1732
|
+
import { execa as execa4 } from "execa";
|
|
1733
|
+
import semver from "semver";
|
|
1734
|
+
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
1735
|
+
async function fetchPackageVersions(packageName) {
|
|
1736
|
+
const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
|
|
1737
|
+
try {
|
|
1738
|
+
const res = await httpClient({ url });
|
|
1739
|
+
if (res.status === 404) return [];
|
|
1740
|
+
if (!res.ok) {
|
|
1741
|
+
throw new Error(`npm registry error ${res.status} for "${packageName}": ${res.text}`);
|
|
1742
|
+
}
|
|
1743
|
+
const data = res.data;
|
|
1744
|
+
return Object.keys(data.versions);
|
|
1745
|
+
} catch (err) {
|
|
1746
|
+
if (err instanceof Error && err.message.includes("registry error")) {
|
|
1747
|
+
throw err;
|
|
1748
|
+
}
|
|
1749
|
+
return [];
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
async function findSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
|
|
1753
|
+
const resolution = await resolveSafeUpgradeVersion(
|
|
1754
|
+
packageName,
|
|
1755
|
+
installedVersion,
|
|
1756
|
+
firstPatchedVersion,
|
|
1757
|
+
vulnerableRange
|
|
1758
|
+
);
|
|
1759
|
+
return resolution.safeVersion;
|
|
1760
|
+
}
|
|
1761
|
+
async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
|
|
1762
|
+
const versions = await fetchPackageVersions(packageName);
|
|
1763
|
+
if (!versions.length) {
|
|
1764
|
+
return {
|
|
1765
|
+
candidates: {},
|
|
1766
|
+
majorOnlyFixAvailable: false
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
const installed = semver.parse(installedVersion);
|
|
1770
|
+
const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
|
|
1771
|
+
if (!vulnerableRange) return true;
|
|
1772
|
+
try {
|
|
1773
|
+
return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
|
|
1774
|
+
} catch {
|
|
1775
|
+
return true;
|
|
1776
|
+
}
|
|
1777
|
+
}).sort(semver.compare);
|
|
1778
|
+
if (!candidates.length) {
|
|
1779
|
+
return {
|
|
1780
|
+
candidates: {},
|
|
1781
|
+
majorOnlyFixAvailable: false
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
const categorizedCandidates = {};
|
|
1785
|
+
for (const candidate of candidates) {
|
|
1786
|
+
const level = classifyUpgradeLevel(installedVersion, candidate);
|
|
1787
|
+
if (!level) continue;
|
|
1788
|
+
if (!categorizedCandidates[level]) {
|
|
1789
|
+
categorizedCandidates[level] = candidate;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
const safeVersion = categorizedCandidates.patch ?? categorizedCandidates.minor ?? categorizedCandidates.major;
|
|
1793
|
+
if (!safeVersion) {
|
|
1794
|
+
return {
|
|
1795
|
+
candidates: categorizedCandidates,
|
|
1796
|
+
majorOnlyFixAvailable: false
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
const upgradeLevel = classifyUpgradeLevel(installedVersion, safeVersion);
|
|
1800
|
+
const majorOnlyFixAvailable = !categorizedCandidates.patch && !categorizedCandidates.minor && Boolean(categorizedCandidates.major);
|
|
1801
|
+
if (!installed || !upgradeLevel) {
|
|
1802
|
+
return {
|
|
1803
|
+
safeVersion,
|
|
1804
|
+
upgradeLevel,
|
|
1805
|
+
candidates: categorizedCandidates,
|
|
1806
|
+
majorOnlyFixAvailable
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
return {
|
|
1810
|
+
safeVersion,
|
|
1811
|
+
upgradeLevel,
|
|
1812
|
+
candidates: categorizedCandidates,
|
|
1813
|
+
majorOnlyFixAvailable
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
function classifyUpgradeLevel(installedVersion, candidateVersion) {
|
|
1817
|
+
const installed = semver.parse(installedVersion);
|
|
1818
|
+
const candidate = semver.parse(candidateVersion);
|
|
1819
|
+
if (!installed || !candidate) return void 0;
|
|
1820
|
+
if (candidate.major > installed.major) return "major";
|
|
1821
|
+
if (candidate.minor > installed.minor) return "minor";
|
|
1822
|
+
if (candidate.patch > installed.patch || candidate.version === installed.version) {
|
|
1823
|
+
return "patch";
|
|
1824
|
+
}
|
|
1825
|
+
return void 0;
|
|
1826
|
+
}
|
|
1827
|
+
async function queryOutdatedPackages(cwd, options = {}) {
|
|
1828
|
+
const pm = options.packageManager ?? detectPackageManager(cwd);
|
|
1829
|
+
let stdout;
|
|
1830
|
+
try {
|
|
1831
|
+
const args = pm === "pnpm" ? ["outdated", "--json"] : ["outdated", "--json"];
|
|
1832
|
+
const yarnArgs = pm === "yarn" ? ["outdated", "--json"] : void 0;
|
|
1833
|
+
const finalArgs = yarnArgs ?? args;
|
|
1834
|
+
const result2 = await execa4(pm, finalArgs, {
|
|
1835
|
+
cwd,
|
|
1836
|
+
reject: false,
|
|
1837
|
+
stdio: "pipe"
|
|
1838
|
+
});
|
|
1839
|
+
if (result2.exitCode !== 0 && result2.exitCode !== 1) {
|
|
1840
|
+
const stderr = typeof result2.stderr === "string" ? result2.stderr : "";
|
|
1841
|
+
if (!result2.stdout) {
|
|
1842
|
+
throw new Error(
|
|
1843
|
+
`${pm} outdated --json failed (exit ${result2.exitCode}): ${stderr.slice(0, 500)}`
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
stdout = typeof result2.stdout === "string" ? result2.stdout : "";
|
|
1848
|
+
} catch (err) {
|
|
1849
|
+
if (err instanceof Error && !err.message.includes("outdated --json failed")) {
|
|
1850
|
+
throw new Error(`Failed to query outdated packages: ${err.message}`);
|
|
1851
|
+
}
|
|
1852
|
+
throw err;
|
|
1853
|
+
}
|
|
1854
|
+
if (!stdout.trim()) {
|
|
1855
|
+
return /* @__PURE__ */ new Map();
|
|
1856
|
+
}
|
|
1857
|
+
const rawEntries = {};
|
|
1858
|
+
if (pm === "yarn") {
|
|
1859
|
+
for (const line of stdout.split("\n")) {
|
|
1860
|
+
const trimmed = line.trim();
|
|
1861
|
+
if (!trimmed) continue;
|
|
1862
|
+
try {
|
|
1863
|
+
const parsed = JSON.parse(trimmed);
|
|
1864
|
+
if (parsed.type === "table" && parsed.data) {
|
|
1865
|
+
const tableData = parsed.data;
|
|
1866
|
+
const head = tableData.head ?? [];
|
|
1867
|
+
const currentIdx = head.indexOf("Current");
|
|
1868
|
+
const wantedIdx = head.indexOf("Wanted");
|
|
1869
|
+
const latestIdx = head.indexOf("Latest");
|
|
1870
|
+
for (const row of tableData.body ?? []) {
|
|
1871
|
+
const name = row[0];
|
|
1872
|
+
if (!name) continue;
|
|
1873
|
+
rawEntries[name] = {
|
|
1874
|
+
current: row[currentIdx] ?? "",
|
|
1875
|
+
wanted: row[wantedIdx] ?? "",
|
|
1876
|
+
latest: row[latestIdx] ?? "",
|
|
1877
|
+
type: row[head.indexOf("Package Type")] ?? "dependencies"
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
} catch {
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
} else {
|
|
1885
|
+
try {
|
|
1886
|
+
const parsed = JSON.parse(stdout);
|
|
1887
|
+
if (Array.isArray(parsed)) {
|
|
1888
|
+
for (const entry of parsed) {
|
|
1889
|
+
const name = entry.name ?? entry.packageName;
|
|
1890
|
+
if (!name) continue;
|
|
1891
|
+
rawEntries[name] = {
|
|
1892
|
+
current: entry.current,
|
|
1893
|
+
wanted: entry.wanted,
|
|
1894
|
+
latest: entry.latest,
|
|
1895
|
+
type: entry.dependencyType
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
} else {
|
|
1899
|
+
Object.assign(rawEntries, parsed);
|
|
1900
|
+
}
|
|
1901
|
+
} catch {
|
|
1902
|
+
throw new Error(`Could not parse ${pm} outdated --json output.`);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
let directDeps;
|
|
1906
|
+
try {
|
|
1907
|
+
const pkgRaw = JSON.parse(readFileSync7(join9(cwd, "package.json"), "utf8"));
|
|
1908
|
+
directDeps = /* @__PURE__ */ new Set([
|
|
1909
|
+
...Object.keys(pkgRaw.dependencies ?? {}),
|
|
1910
|
+
...Object.keys(pkgRaw.devDependencies ?? {})
|
|
1911
|
+
]);
|
|
1912
|
+
} catch {
|
|
1913
|
+
directDeps = /* @__PURE__ */ new Set();
|
|
1914
|
+
}
|
|
1915
|
+
const result = /* @__PURE__ */ new Map();
|
|
1916
|
+
for (const [name, entry] of Object.entries(rawEntries)) {
|
|
1917
|
+
const currentVersion = entry.current ?? "";
|
|
1918
|
+
const wantedVersion = entry.wanted ?? currentVersion;
|
|
1919
|
+
const latestVersion = entry.latest ?? currentVersion;
|
|
1920
|
+
if (!currentVersion || !latestVersion) continue;
|
|
1921
|
+
const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
|
|
1922
|
+
if (!options.includeTransitive && dependencyScope === "transitive") continue;
|
|
1923
|
+
const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
|
|
1924
|
+
result.set(name, {
|
|
1925
|
+
currentVersion,
|
|
1926
|
+
wantedVersion,
|
|
1927
|
+
latestVersion,
|
|
1928
|
+
isMajorBump,
|
|
1929
|
+
dependencyScope
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
return result;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
// src/remediation/evaluate-package.ts
|
|
1936
|
+
async function evaluatePackage(packageName, options = {}) {
|
|
1937
|
+
const version = options.version?.trim();
|
|
1938
|
+
const enrich = options.enrichIntelligence !== false;
|
|
1939
|
+
let versions = [];
|
|
1940
|
+
let latestVersion;
|
|
1941
|
+
try {
|
|
1942
|
+
versions = await fetchPackageVersions(packageName);
|
|
1943
|
+
if (versions.length > 0) {
|
|
1944
|
+
const valid = versions.filter((v) => semver2.valid(v)).sort((a, b) => semver2.compare(a, b));
|
|
1945
|
+
latestVersion = valid[valid.length - 1];
|
|
1946
|
+
}
|
|
1947
|
+
} catch {
|
|
1948
|
+
}
|
|
1949
|
+
let rawVulns = [];
|
|
1950
|
+
try {
|
|
1951
|
+
rawVulns = await queryOsvPackage(packageName, version);
|
|
1952
|
+
} catch {
|
|
1953
|
+
rawVulns = [];
|
|
1954
|
+
}
|
|
1955
|
+
let kevFeed = null;
|
|
1956
|
+
if (enrich) {
|
|
1957
|
+
try {
|
|
1958
|
+
kevFeed = await fetchCisaKevFeed();
|
|
1959
|
+
} catch {
|
|
1960
|
+
kevFeed = null;
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
const entries = [];
|
|
1964
|
+
for (const raw of rawVulns) {
|
|
1965
|
+
const cveDetails = parseOsvVuln(raw);
|
|
1966
|
+
const affected = cveDetails.affectedPackages.find(
|
|
1967
|
+
(p) => p.name.toLowerCase() === packageName.toLowerCase()
|
|
1968
|
+
);
|
|
1969
|
+
if (version && affected?.vulnerableRange) {
|
|
1970
|
+
const cleanVer = semver2.clean(version) || version;
|
|
1971
|
+
if (semver2.valid(cleanVer) && !semver2.satisfies(cleanVer, affected.vulnerableRange)) {
|
|
1972
|
+
continue;
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
let inCisaKev = false;
|
|
1976
|
+
let epssScore;
|
|
1977
|
+
let epssPercentile;
|
|
1978
|
+
if (enrich && cveDetails.id.startsWith("CVE-")) {
|
|
1979
|
+
if (kevFeed) {
|
|
1980
|
+
inCisaKev = Boolean(findKevEntry(kevFeed, cveDetails.id));
|
|
1981
|
+
}
|
|
1982
|
+
try {
|
|
1983
|
+
const epss = await fetchEpss(cveDetails.id);
|
|
1984
|
+
if (epss) {
|
|
1985
|
+
epssScore = Number.parseFloat(epss.epss);
|
|
1986
|
+
epssPercentile = Number.parseFloat(epss.percentile);
|
|
1987
|
+
}
|
|
1988
|
+
} catch {
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
let safeUpgradeVersion;
|
|
1992
|
+
if (affected?.firstPatchedVersion) {
|
|
1993
|
+
try {
|
|
1994
|
+
const safe = await findSafeUpgradeVersion(
|
|
1995
|
+
packageName,
|
|
1996
|
+
version || "0.0.0",
|
|
1997
|
+
affected.firstPatchedVersion
|
|
1998
|
+
);
|
|
1999
|
+
safeUpgradeVersion = safe ?? affected.firstPatchedVersion;
|
|
2000
|
+
} catch {
|
|
2001
|
+
safeUpgradeVersion = affected.firstPatchedVersion;
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
entries.push({
|
|
2005
|
+
cveId: cveDetails.id,
|
|
2006
|
+
severity: cveDetails.severity,
|
|
2007
|
+
summary: cveDetails.summary,
|
|
2008
|
+
vulnerableRange: affected?.vulnerableRange,
|
|
2009
|
+
firstPatchedVersion: affected?.firstPatchedVersion,
|
|
2010
|
+
safeUpgradeVersion,
|
|
2011
|
+
epssScore,
|
|
2012
|
+
epssPercentile,
|
|
2013
|
+
inCisaKev,
|
|
2014
|
+
references: cveDetails.references
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
const isVulnerable = entries.length > 0;
|
|
2018
|
+
let verdict = "safe";
|
|
2019
|
+
if (isVulnerable) {
|
|
2020
|
+
const hasKevOrHighEpss = entries.some((e) => e.inCisaKev || e.epssScore && e.epssScore > 0.5);
|
|
2021
|
+
const hasHighOrCritical = entries.some(
|
|
2022
|
+
(e) => e.severity === "CRITICAL" || e.severity === "HIGH"
|
|
2023
|
+
);
|
|
2024
|
+
if (hasKevOrHighEpss) {
|
|
2025
|
+
verdict = "actively-exploited";
|
|
2026
|
+
} else if (hasHighOrCritical) {
|
|
2027
|
+
verdict = "vulnerable";
|
|
2028
|
+
} else {
|
|
2029
|
+
verdict = "caution";
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
let recommendedVersion;
|
|
2033
|
+
if (isVulnerable) {
|
|
2034
|
+
const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver2.valid(v))).sort((a, b) => semver2.compare(a, b));
|
|
2035
|
+
recommendedVersion = upgradeCandidates[upgradeCandidates.length - 1] || latestVersion;
|
|
2036
|
+
} else {
|
|
2037
|
+
recommendedVersion = version || latestVersion;
|
|
2038
|
+
}
|
|
2039
|
+
const summary = isVulnerable ? `Package "${packageName}${version ? `@${version}` : ""}" has ${entries.length} known vulnerability(ies) (verdict: ${verdict}). Recommended safe version: ${recommendedVersion || "none"}.` : `Package "${packageName}${version ? `@${version}` : ""}" has no known vulnerabilities. Safe to install.`;
|
|
2040
|
+
return {
|
|
2041
|
+
schemaVersion: "1.0",
|
|
2042
|
+
packageName,
|
|
2043
|
+
evaluatedVersion: version,
|
|
2044
|
+
isVulnerable,
|
|
2045
|
+
verdict,
|
|
2046
|
+
summary,
|
|
2047
|
+
vulnerabilities: entries,
|
|
2048
|
+
recommendedVersion,
|
|
2049
|
+
latestVersion,
|
|
2050
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
// src/api/patches/reconcile.ts
|
|
2055
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2056
|
+
import { readFile as readFile3, rm, writeFile } from "fs/promises";
|
|
2057
|
+
import { join as join10 } from "path";
|
|
2058
|
+
import { execa as execa5 } from "execa";
|
|
2059
|
+
async function reconcilePatchArtifacts(options = {}) {
|
|
2060
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2061
|
+
const pm = options.packageManager ?? detectPackageManager(cwd);
|
|
2062
|
+
const summaries = await listPatchArtifacts(options);
|
|
2063
|
+
const results = [];
|
|
2064
|
+
for (const summary of summaries) {
|
|
2065
|
+
const inspection = await inspectPatchArtifact(summary.patchFilePath, options);
|
|
2066
|
+
let packageName = inspection.packageName;
|
|
2067
|
+
let vulnerableVersion = inspection.vulnerableVersion;
|
|
2068
|
+
const cveId = inspection.cveId;
|
|
2069
|
+
if (!packageName && summary.patchFileName) {
|
|
2070
|
+
const basename = summary.patchFileName.replace(/\.patch$/, "");
|
|
2071
|
+
const plusIdx = basename.lastIndexOf("+");
|
|
2072
|
+
if (plusIdx > 0) {
|
|
2073
|
+
let rawPkg = basename.slice(0, plusIdx);
|
|
2074
|
+
vulnerableVersion = basename.slice(plusIdx + 1);
|
|
2075
|
+
if (rawPkg.includes("+")) {
|
|
2076
|
+
rawPkg = "@" + rawPkg.replace("+", "/");
|
|
2077
|
+
}
|
|
2078
|
+
packageName = rawPkg;
|
|
2079
|
+
} else {
|
|
2080
|
+
packageName = basename;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
if (!packageName) {
|
|
2084
|
+
results.push({
|
|
2085
|
+
patchFilePath: summary.patchFilePath,
|
|
2086
|
+
packageName: "unknown",
|
|
2087
|
+
cveId,
|
|
2088
|
+
status: "patch-retained",
|
|
2089
|
+
reason: "Cannot determine package name from patch artifact."
|
|
2090
|
+
});
|
|
2091
|
+
continue;
|
|
2092
|
+
}
|
|
2093
|
+
let safeUpgradeVersion;
|
|
2094
|
+
try {
|
|
2095
|
+
const evaluation = await evaluatePackage(packageName, {
|
|
2096
|
+
version: vulnerableVersion,
|
|
2097
|
+
enrichIntelligence: false
|
|
2098
|
+
});
|
|
2099
|
+
if (cveId) {
|
|
2100
|
+
const vuln = evaluation.vulnerabilities.find(
|
|
2101
|
+
(v) => v.cveId.toLowerCase() === cveId.toLowerCase()
|
|
2102
|
+
);
|
|
2103
|
+
if (vuln?.safeUpgradeVersion) {
|
|
2104
|
+
safeUpgradeVersion = vuln.safeUpgradeVersion;
|
|
2105
|
+
}
|
|
2106
|
+
} else {
|
|
2107
|
+
safeUpgradeVersion = evaluation.vulnerabilities.find(
|
|
2108
|
+
(v) => v.safeUpgradeVersion
|
|
2109
|
+
)?.safeUpgradeVersion;
|
|
2110
|
+
}
|
|
2111
|
+
} catch {
|
|
2112
|
+
}
|
|
2113
|
+
if (!safeUpgradeVersion) {
|
|
2114
|
+
results.push({
|
|
2115
|
+
patchFilePath: summary.patchFilePath,
|
|
2116
|
+
packageName,
|
|
2117
|
+
cveId,
|
|
2118
|
+
installedVersion: vulnerableVersion,
|
|
2119
|
+
status: "patch-retained",
|
|
2120
|
+
reason: "No safe upstream release exists yet; patch remains necessary."
|
|
2121
|
+
});
|
|
2122
|
+
continue;
|
|
2123
|
+
}
|
|
2124
|
+
if (!options.autoApply) {
|
|
2125
|
+
results.push({
|
|
2126
|
+
patchFilePath: summary.patchFilePath,
|
|
2127
|
+
packageName,
|
|
2128
|
+
cveId,
|
|
2129
|
+
installedVersion: vulnerableVersion,
|
|
2130
|
+
safeUpstreamVersion: safeUpgradeVersion,
|
|
2131
|
+
status: "safe-version-available",
|
|
2132
|
+
reason: `Safe upstream version ${safeUpgradeVersion} is available. Re-run with autoApply: true or --auto-apply to upgrade.`
|
|
2133
|
+
});
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
try {
|
|
2137
|
+
const pkgJsonPath = join10(cwd, "package.json");
|
|
2138
|
+
if (existsSync8(pkgJsonPath)) {
|
|
2139
|
+
const pkgJson = JSON.parse(await readFile3(pkgJsonPath, "utf8"));
|
|
2140
|
+
let updated = false;
|
|
2141
|
+
const depSections = [
|
|
2142
|
+
"dependencies",
|
|
2143
|
+
"devDependencies",
|
|
2144
|
+
"peerDependencies",
|
|
2145
|
+
"optionalDependencies"
|
|
2146
|
+
];
|
|
2147
|
+
for (const depSection of depSections) {
|
|
2148
|
+
if (pkgJson[depSection] && pkgJson[depSection][packageName]) {
|
|
2149
|
+
pkgJson[depSection][packageName] = `^${safeUpgradeVersion}`;
|
|
2150
|
+
updated = true;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
if (pkgJson.pnpm?.patchedDependencies) {
|
|
2154
|
+
for (const key of Object.keys(pkgJson.pnpm.patchedDependencies)) {
|
|
2155
|
+
if (key.startsWith(`${packageName}@`)) {
|
|
2156
|
+
delete pkgJson.pnpm.patchedDependencies[key];
|
|
2157
|
+
updated = true;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
if (updated) {
|
|
2162
|
+
await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
await rm(summary.patchFilePath, { force: true });
|
|
2166
|
+
if (inspection.manifestFilePath) {
|
|
2167
|
+
await rm(inspection.manifestFilePath, { force: true });
|
|
2168
|
+
}
|
|
2169
|
+
const installCmd = resolveInstallCommand(pm);
|
|
2170
|
+
await execa5(installCmd[0], installCmd.slice(1), { cwd, reject: false });
|
|
2171
|
+
if (options.runTests) {
|
|
2172
|
+
const testCmd = resolveTestCommand(pm);
|
|
2173
|
+
await execa5(testCmd[0], testCmd.slice(1), { cwd, reject: false });
|
|
2174
|
+
}
|
|
2175
|
+
results.push({
|
|
2176
|
+
patchFilePath: summary.patchFilePath,
|
|
2177
|
+
packageName,
|
|
2178
|
+
cveId,
|
|
2179
|
+
installedVersion: vulnerableVersion,
|
|
2180
|
+
safeUpstreamVersion: safeUpgradeVersion,
|
|
2181
|
+
status: "upgraded",
|
|
2182
|
+
reason: `Successfully upgraded to ${safeUpgradeVersion} and retired patch artifact.`
|
|
2183
|
+
});
|
|
2184
|
+
} catch (err) {
|
|
2185
|
+
results.push({
|
|
2186
|
+
patchFilePath: summary.patchFilePath,
|
|
2187
|
+
packageName,
|
|
2188
|
+
cveId,
|
|
2189
|
+
installedVersion: vulnerableVersion,
|
|
2190
|
+
safeUpstreamVersion: safeUpgradeVersion,
|
|
2191
|
+
status: "error",
|
|
2192
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
const reconciledCount = results.filter((r) => r.status === "upgraded").length;
|
|
2197
|
+
const retainedCount = results.filter(
|
|
2198
|
+
(r) => r.status === "patch-retained" || r.status === "safe-version-available"
|
|
2199
|
+
).length;
|
|
2200
|
+
return {
|
|
2201
|
+
reconciled: results,
|
|
2202
|
+
reconciledCount,
|
|
2203
|
+
retainedCount
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
// src/platform/idempotency.ts
|
|
2208
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
|
|
2209
|
+
import { join as join11 } from "path";
|
|
2210
|
+
var DEFAULT_INDEX = {
|
|
2211
|
+
schemaVersion: "1.0",
|
|
2212
|
+
entries: {}
|
|
2213
|
+
};
|
|
2214
|
+
function indexFilePath(cwd) {
|
|
2215
|
+
return join11(cwd, ".autoremediator", "state", "idempotency.json");
|
|
2216
|
+
}
|
|
2217
|
+
function entryKey(idempotencyKey, cveId) {
|
|
2218
|
+
return `${idempotencyKey}::${cveId.toUpperCase()}`;
|
|
2219
|
+
}
|
|
2220
|
+
function loadIndex(cwd) {
|
|
2221
|
+
const filePath = indexFilePath(cwd);
|
|
2222
|
+
if (!existsSync9(filePath)) return DEFAULT_INDEX;
|
|
2223
|
+
try {
|
|
2224
|
+
const parsed = JSON.parse(readFileSync8(filePath, "utf8"));
|
|
2225
|
+
if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
|
|
2226
|
+
return parsed;
|
|
2227
|
+
}
|
|
2228
|
+
return DEFAULT_INDEX;
|
|
2229
|
+
} catch {
|
|
2230
|
+
return DEFAULT_INDEX;
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
function saveIndex(cwd, index) {
|
|
2234
|
+
const filePath = indexFilePath(cwd);
|
|
2235
|
+
mkdirSync2(join11(cwd, ".autoremediator", "state"), { recursive: true });
|
|
2236
|
+
writeFileSync2(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
|
|
2237
|
+
}
|
|
2238
|
+
function readIdempotentReport(cwd, idempotencyKey, cveId) {
|
|
2239
|
+
const index = loadIndex(cwd);
|
|
2240
|
+
const key = entryKey(idempotencyKey, cveId);
|
|
2241
|
+
return index.entries[key]?.report;
|
|
2242
|
+
}
|
|
2243
|
+
function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
|
|
2244
|
+
const index = loadIndex(cwd);
|
|
2245
|
+
const key = entryKey(idempotencyKey, cveId);
|
|
2246
|
+
index.entries[key] = {
|
|
2247
|
+
key: idempotencyKey,
|
|
2248
|
+
cveId: cveId.toUpperCase(),
|
|
2249
|
+
report,
|
|
2250
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2251
|
+
};
|
|
2252
|
+
saveIndex(cwd, index);
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
// src/remediation/pipeline.ts
|
|
2256
|
+
import { generateText as generateText2 } from "ai";
|
|
2257
|
+
|
|
2258
|
+
// src/remediation/local/run.ts
|
|
2259
|
+
import semver6 from "semver";
|
|
2260
|
+
|
|
2261
|
+
// src/intelligence/sources/github-advisory.ts
|
|
2262
|
+
var GITHUB_API_BASE = "https://api.github.com/advisories";
|
|
2263
|
+
async function fetchGhAdvisories(cveId) {
|
|
2264
|
+
const token = getGitHubToken();
|
|
2265
|
+
const headers = {
|
|
2266
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
2267
|
+
"User-Agent": "autoremediator"
|
|
2268
|
+
};
|
|
2269
|
+
if (token) {
|
|
2270
|
+
headers["Authorization"] = `token ${token}`;
|
|
2271
|
+
}
|
|
2272
|
+
const normalized = cveId.trim();
|
|
2273
|
+
if (normalized.toUpperCase().startsWith("GHSA-")) {
|
|
2274
|
+
const url2 = `${GITHUB_API_BASE}/${encodeURIComponent(normalized)}`;
|
|
2275
|
+
try {
|
|
2276
|
+
const res = await httpClient({ url: url2, headers });
|
|
2277
|
+
if (!res.ok) {
|
|
2278
|
+
if (res.status === 404) return [];
|
|
2279
|
+
throw new Error(`HTTP ${res.status}: ${res.text}`);
|
|
2280
|
+
}
|
|
2281
|
+
return res.data ? [res.data] : [];
|
|
2282
|
+
} catch (err) {
|
|
2283
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2284
|
+
console.warn(
|
|
2285
|
+
`[autoremediator] GitHub Advisory API error for ${cveId}: ${errorMsg} \u2014 skipping.`
|
|
2286
|
+
);
|
|
2287
|
+
return [];
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
const url = new URL(GITHUB_API_BASE);
|
|
2291
|
+
url.searchParams.set("cve_id", cveId);
|
|
2292
|
+
url.searchParams.set("ecosystem", "npm");
|
|
2293
|
+
url.searchParams.set("type", "reviewed");
|
|
2294
|
+
url.searchParams.set("per_page", "10");
|
|
2295
|
+
try {
|
|
2296
|
+
const res = await httpClient({
|
|
2297
|
+
url: url.toString(),
|
|
2298
|
+
headers
|
|
2299
|
+
});
|
|
1576
2300
|
if (!res.ok) {
|
|
1577
2301
|
if (res.status === 404) return [];
|
|
1578
2302
|
throw new Error(`HTTP ${res.status}: ${res.text}`);
|
|
@@ -1671,9 +2395,9 @@ async function enrichWithNvd(details) {
|
|
|
1671
2395
|
}
|
|
1672
2396
|
|
|
1673
2397
|
// src/intelligence/sources/snapshot.ts
|
|
1674
|
-
import { readFileSync as
|
|
2398
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
1675
2399
|
function loadIntelligenceSnapshot(filePath) {
|
|
1676
|
-
const raw =
|
|
2400
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
1677
2401
|
const parsed = JSON.parse(raw);
|
|
1678
2402
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1679
2403
|
throw new Error(`Intelligence snapshot at ${filePath} must be a JSON object keyed by CVE ID.`);
|
|
@@ -1685,8 +2409,8 @@ function lookupSnapshotCve(snapshot, cveId) {
|
|
|
1685
2409
|
}
|
|
1686
2410
|
|
|
1687
2411
|
// src/remediation/tools/check-reachability.ts
|
|
1688
|
-
import { existsSync as
|
|
1689
|
-
import { isAbsolute as isAbsolute2, join as
|
|
2412
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync as statSync2 } from "fs";
|
|
2413
|
+
import { isAbsolute as isAbsolute2, join as join12, resolve as resolve3 } from "path";
|
|
1690
2414
|
import { parseSync } from "oxc-parser";
|
|
1691
2415
|
import { z as z2 } from "zod";
|
|
1692
2416
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
|
|
@@ -1702,16 +2426,16 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
1702
2426
|
]);
|
|
1703
2427
|
function loadTsconfigPaths(cwd) {
|
|
1704
2428
|
for (const configName of ["tsconfig.json", "jsconfig.json"]) {
|
|
1705
|
-
const fullPath =
|
|
1706
|
-
if (!
|
|
2429
|
+
const fullPath = join12(cwd, configName);
|
|
2430
|
+
if (!existsSync10(fullPath)) continue;
|
|
1707
2431
|
try {
|
|
1708
|
-
const raw =
|
|
2432
|
+
const raw = readFileSync10(fullPath, "utf8");
|
|
1709
2433
|
const cleaned = raw.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
|
|
1710
2434
|
const parsed = JSON.parse(cleaned);
|
|
1711
2435
|
const compilerOptions = parsed.compilerOptions;
|
|
1712
2436
|
if (compilerOptions?.paths) {
|
|
1713
2437
|
return {
|
|
1714
|
-
baseUrl: compilerOptions.baseUrl ?
|
|
2438
|
+
baseUrl: compilerOptions.baseUrl ? resolve3(cwd, compilerOptions.baseUrl) : cwd,
|
|
1715
2439
|
paths: compilerOptions.paths
|
|
1716
2440
|
};
|
|
1717
2441
|
}
|
|
@@ -1726,14 +2450,14 @@ function resolveAliasedPath(importSource, config) {
|
|
|
1726
2450
|
}
|
|
1727
2451
|
for (const [pattern, replacements] of Object.entries(config.paths)) {
|
|
1728
2452
|
if (pattern === importSource && replacements[0]) {
|
|
1729
|
-
return
|
|
2453
|
+
return resolve3(config.baseUrl, replacements[0]);
|
|
1730
2454
|
}
|
|
1731
2455
|
if (pattern.endsWith("/*")) {
|
|
1732
2456
|
const prefix = pattern.slice(0, -2);
|
|
1733
2457
|
if (importSource.startsWith(prefix)) {
|
|
1734
2458
|
const suffix = importSource.slice(prefix.length);
|
|
1735
2459
|
const replacement = replacements[0]?.slice(0, -2) ?? "";
|
|
1736
|
-
return
|
|
2460
|
+
return resolve3(config.baseUrl, replacement + suffix);
|
|
1737
2461
|
}
|
|
1738
2462
|
}
|
|
1739
2463
|
}
|
|
@@ -1742,16 +2466,16 @@ function resolveAliasedPath(importSource, config) {
|
|
|
1742
2466
|
function* walkSourceFiles(dir) {
|
|
1743
2467
|
let entries;
|
|
1744
2468
|
try {
|
|
1745
|
-
entries =
|
|
2469
|
+
entries = readdirSync2(dir);
|
|
1746
2470
|
} catch {
|
|
1747
2471
|
return;
|
|
1748
2472
|
}
|
|
1749
2473
|
for (const entry of entries) {
|
|
1750
2474
|
if (IGNORED_DIRS.has(entry)) continue;
|
|
1751
|
-
const full =
|
|
2475
|
+
const full = join12(dir, entry);
|
|
1752
2476
|
let stat2;
|
|
1753
2477
|
try {
|
|
1754
|
-
stat2 =
|
|
2478
|
+
stat2 = statSync2(full);
|
|
1755
2479
|
} catch {
|
|
1756
2480
|
continue;
|
|
1757
2481
|
}
|
|
@@ -1789,7 +2513,31 @@ function walkAst(node, visitor) {
|
|
|
1789
2513
|
}
|
|
1790
2514
|
}
|
|
1791
2515
|
}
|
|
1792
|
-
function
|
|
2516
|
+
function toRootPackageName(importSource) {
|
|
2517
|
+
if (importSource.startsWith("@")) {
|
|
2518
|
+
const parts = importSource.split("/");
|
|
2519
|
+
return parts.slice(0, 2).join("/");
|
|
2520
|
+
}
|
|
2521
|
+
return importSource.split("/")[0] ?? importSource;
|
|
2522
|
+
}
|
|
2523
|
+
function findTransitiveDependencyPath(cwd, targetPackage, importedDirectPackages) {
|
|
2524
|
+
for (const directPkg of importedDirectPackages) {
|
|
2525
|
+
const manifestPath = join12(cwd, "node_modules", directPkg, "package.json");
|
|
2526
|
+
if (!existsSync10(manifestPath)) continue;
|
|
2527
|
+
try {
|
|
2528
|
+
const manifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
|
|
2529
|
+
if (manifest.dependencies && manifest.dependencies[targetPackage]) {
|
|
2530
|
+
return {
|
|
2531
|
+
directDependency: directPkg,
|
|
2532
|
+
version: manifest.dependencies[targetPackage]
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
} catch {
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
return null;
|
|
2539
|
+
}
|
|
2540
|
+
function extractPackageReferences(filename, content, packageName, tsconfig, outImportedDirectPackages) {
|
|
1793
2541
|
const results = [];
|
|
1794
2542
|
let parseResult;
|
|
1795
2543
|
try {
|
|
@@ -1801,6 +2549,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
|
|
|
1801
2549
|
walkAst(parseResult.program, (node) => {
|
|
1802
2550
|
if (node.type === "ImportDeclaration") {
|
|
1803
2551
|
const source = node.source;
|
|
2552
|
+
if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
|
|
2553
|
+
outImportedDirectPackages?.add(toRootPackageName(source.value));
|
|
2554
|
+
}
|
|
1804
2555
|
if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
|
|
1805
2556
|
const symbols = [];
|
|
1806
2557
|
const importedIdentifiers = /* @__PURE__ */ new Map();
|
|
@@ -1835,6 +2586,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
|
|
|
1835
2586
|
}
|
|
1836
2587
|
if (node.type === "ImportExpression") {
|
|
1837
2588
|
const source = node.source;
|
|
2589
|
+
if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
|
|
2590
|
+
outImportedDirectPackages?.add(toRootPackageName(source.value));
|
|
2591
|
+
}
|
|
1838
2592
|
if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
|
|
1839
2593
|
results.push({
|
|
1840
2594
|
matchType: "dynamic-import",
|
|
@@ -1846,6 +2600,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
|
|
|
1846
2600
|
}
|
|
1847
2601
|
if (node.type === "CallExpression" && node.callee?.name === "require" && Array.isArray(node.arguments) && node.arguments.length > 0) {
|
|
1848
2602
|
const firstArg = node.arguments[0];
|
|
2603
|
+
if (firstArg?.value && typeof firstArg.value === "string" && !firstArg.value.startsWith(".") && !isAbsolute2(firstArg.value)) {
|
|
2604
|
+
outImportedDirectPackages?.add(toRootPackageName(firstArg.value));
|
|
2605
|
+
}
|
|
1849
2606
|
if (firstArg?.value && typeof firstArg.value === "string" && matchesPackage(firstArg.value, packageName, tsconfig)) {
|
|
1850
2607
|
results.push({
|
|
1851
2608
|
matchType: "require",
|
|
@@ -1857,6 +2614,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
|
|
|
1857
2614
|
}
|
|
1858
2615
|
if (node.type === "ExportNamedDeclaration" || node.type === "ExportAllDeclaration") {
|
|
1859
2616
|
const source = node.source;
|
|
2617
|
+
if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
|
|
2618
|
+
outImportedDirectPackages?.add(toRootPackageName(source.value));
|
|
2619
|
+
}
|
|
1860
2620
|
if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
|
|
1861
2621
|
const symbols = [];
|
|
1862
2622
|
if (node.type === "ExportAllDeclaration") {
|
|
@@ -1933,15 +2693,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
|
|
|
1933
2693
|
let hasReExport = false;
|
|
1934
2694
|
let totalInvocations = 0;
|
|
1935
2695
|
let hasPackageImport = false;
|
|
2696
|
+
const importedDirectPackages = /* @__PURE__ */ new Set();
|
|
1936
2697
|
for (const filePath of walkSourceFiles(cwd)) {
|
|
1937
2698
|
scanned++;
|
|
1938
2699
|
let content;
|
|
1939
2700
|
try {
|
|
1940
|
-
content =
|
|
2701
|
+
content = readFileSync10(filePath, "utf8");
|
|
1941
2702
|
} catch {
|
|
1942
2703
|
continue;
|
|
1943
2704
|
}
|
|
1944
|
-
const refs = extractPackageReferences(filePath, content, packageName, tsconfig);
|
|
2705
|
+
const refs = extractPackageReferences(filePath, content, packageName, tsconfig, importedDirectPackages);
|
|
1945
2706
|
if (refs.length > 0) {
|
|
1946
2707
|
hasPackageImport = true;
|
|
1947
2708
|
}
|
|
@@ -1984,6 +2745,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
|
|
|
1984
2745
|
};
|
|
1985
2746
|
}
|
|
1986
2747
|
if (!hasPackageImport) {
|
|
2748
|
+
const transitivePath = findTransitiveDependencyPath(cwd, packageName, importedDirectPackages);
|
|
2749
|
+
if (transitivePath) {
|
|
2750
|
+
return {
|
|
2751
|
+
packageName,
|
|
2752
|
+
status: "reachable",
|
|
2753
|
+
reason: `Package '${packageName}' is a transitive dependency of imported package '${transitivePath.directDependency}'`,
|
|
2754
|
+
reachabilityBasis: "transitive-path-found",
|
|
2755
|
+
evidence: []
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
1987
2758
|
return {
|
|
1988
2759
|
packageName,
|
|
1989
2760
|
status: "not-reachable",
|
|
@@ -2112,270 +2883,64 @@ async function runSecOpsPreflight(normalizedId, cveDetails, opts) {
|
|
|
2112
2883
|
id: cveDetails.id,
|
|
2113
2884
|
kev: cveDetails.kev,
|
|
2114
2885
|
epss: cveDetails.epss
|
|
2115
|
-
},
|
|
2116
|
-
policy: { exploitSignalOverride: opts.exploitSignalOverride }
|
|
2117
|
-
});
|
|
2118
|
-
if (result.exploitSignalTriggered) {
|
|
2119
|
-
exploitSignalTriggered = true;
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
let slaBreaches;
|
|
2123
|
-
if (opts.slaCheck && opts.slaPolicy && cveDetails.publishedAt) {
|
|
2124
|
-
const breach = checkSlaBreach(
|
|
2125
|
-
normalizedId,
|
|
2126
|
-
cveDetails.severity,
|
|
2127
|
-
cveDetails.publishedAt,
|
|
2128
|
-
opts.slaPolicy
|
|
2129
|
-
);
|
|
2130
|
-
if (breach) {
|
|
2131
|
-
slaBreaches = [breach];
|
|
2132
|
-
}
|
|
2133
|
-
}
|
|
2134
|
-
return { suppressed: false, exploitSignalTriggered, slaBreaches };
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
|
-
// src/remediation/local/sbom.ts
|
|
2138
|
-
function buildSbom(packages, vulnerableNames, results) {
|
|
2139
|
-
const statusByPackage = /* @__PURE__ */ new Map();
|
|
2140
|
-
for (const result of results) {
|
|
2141
|
-
if (!vulnerableNames.has(result.packageName)) continue;
|
|
2142
|
-
if (result.suppressedBy) {
|
|
2143
|
-
statusByPackage.set(result.packageName, "suppressed");
|
|
2144
|
-
} else if (!result.applied && result.strategy === "none") {
|
|
2145
|
-
statusByPackage.set(result.packageName, "skipped");
|
|
2146
|
-
} else if (result.applied) {
|
|
2147
|
-
statusByPackage.set(result.packageName, "patched");
|
|
2148
|
-
} else {
|
|
2149
|
-
statusByPackage.set(result.packageName, "unpatched");
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
return packages.map((pkg) => {
|
|
2153
|
-
const isVulnerable = vulnerableNames.has(pkg.name);
|
|
2154
|
-
const entry = {
|
|
2155
|
-
name: pkg.name,
|
|
2156
|
-
version: pkg.version,
|
|
2157
|
-
scope: pkg.type === "direct" ? "direct" : "transitive"
|
|
2158
|
-
};
|
|
2159
|
-
if (isVulnerable) {
|
|
2160
|
-
entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
|
|
2161
|
-
}
|
|
2162
|
-
return entry;
|
|
2163
|
-
});
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
// src/intelligence/sources/registry.ts
|
|
2167
|
-
import { readFileSync as readFileSync8 } from "fs";
|
|
2168
|
-
import { join as join10 } from "path";
|
|
2169
|
-
import { execa as execa4 } from "execa";
|
|
2170
|
-
import semver from "semver";
|
|
2171
|
-
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
2172
|
-
async function fetchPackageVersions(packageName) {
|
|
2173
|
-
const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
|
|
2174
|
-
try {
|
|
2175
|
-
const res = await httpClient({ url });
|
|
2176
|
-
if (res.status === 404) return [];
|
|
2177
|
-
if (!res.ok) {
|
|
2178
|
-
throw new Error(`npm registry error ${res.status} for "${packageName}": ${res.text}`);
|
|
2179
|
-
}
|
|
2180
|
-
const data = res.data;
|
|
2181
|
-
return Object.keys(data.versions);
|
|
2182
|
-
} catch (err) {
|
|
2183
|
-
if (err instanceof Error && err.message.includes("registry error")) {
|
|
2184
|
-
throw err;
|
|
2185
|
-
}
|
|
2186
|
-
return [];
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
async function findSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
|
|
2190
|
-
const resolution = await resolveSafeUpgradeVersion(
|
|
2191
|
-
packageName,
|
|
2192
|
-
installedVersion,
|
|
2193
|
-
firstPatchedVersion,
|
|
2194
|
-
vulnerableRange
|
|
2195
|
-
);
|
|
2196
|
-
return resolution.safeVersion;
|
|
2197
|
-
}
|
|
2198
|
-
async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
|
|
2199
|
-
const versions = await fetchPackageVersions(packageName);
|
|
2200
|
-
if (!versions.length) {
|
|
2201
|
-
return {
|
|
2202
|
-
candidates: {},
|
|
2203
|
-
majorOnlyFixAvailable: false
|
|
2204
|
-
};
|
|
2205
|
-
}
|
|
2206
|
-
const installed = semver.parse(installedVersion);
|
|
2207
|
-
const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
|
|
2208
|
-
if (!vulnerableRange) return true;
|
|
2209
|
-
try {
|
|
2210
|
-
return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
|
|
2211
|
-
} catch {
|
|
2212
|
-
return true;
|
|
2213
|
-
}
|
|
2214
|
-
}).sort(semver.compare);
|
|
2215
|
-
if (!candidates.length) {
|
|
2216
|
-
return {
|
|
2217
|
-
candidates: {},
|
|
2218
|
-
majorOnlyFixAvailable: false
|
|
2219
|
-
};
|
|
2220
|
-
}
|
|
2221
|
-
const categorizedCandidates = {};
|
|
2222
|
-
for (const candidate of candidates) {
|
|
2223
|
-
const level = classifyUpgradeLevel(installedVersion, candidate);
|
|
2224
|
-
if (!level) continue;
|
|
2225
|
-
if (!categorizedCandidates[level]) {
|
|
2226
|
-
categorizedCandidates[level] = candidate;
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
const safeVersion = categorizedCandidates.patch ?? categorizedCandidates.minor ?? categorizedCandidates.major;
|
|
2230
|
-
if (!safeVersion) {
|
|
2231
|
-
return {
|
|
2232
|
-
candidates: categorizedCandidates,
|
|
2233
|
-
majorOnlyFixAvailable: false
|
|
2234
|
-
};
|
|
2235
|
-
}
|
|
2236
|
-
const upgradeLevel = classifyUpgradeLevel(installedVersion, safeVersion);
|
|
2237
|
-
const majorOnlyFixAvailable = !categorizedCandidates.patch && !categorizedCandidates.minor && Boolean(categorizedCandidates.major);
|
|
2238
|
-
if (!installed || !upgradeLevel) {
|
|
2239
|
-
return {
|
|
2240
|
-
safeVersion,
|
|
2241
|
-
upgradeLevel,
|
|
2242
|
-
candidates: categorizedCandidates,
|
|
2243
|
-
majorOnlyFixAvailable
|
|
2244
|
-
};
|
|
2245
|
-
}
|
|
2246
|
-
return {
|
|
2247
|
-
safeVersion,
|
|
2248
|
-
upgradeLevel,
|
|
2249
|
-
candidates: categorizedCandidates,
|
|
2250
|
-
majorOnlyFixAvailable
|
|
2251
|
-
};
|
|
2252
|
-
}
|
|
2253
|
-
function classifyUpgradeLevel(installedVersion, candidateVersion) {
|
|
2254
|
-
const installed = semver.parse(installedVersion);
|
|
2255
|
-
const candidate = semver.parse(candidateVersion);
|
|
2256
|
-
if (!installed || !candidate) return void 0;
|
|
2257
|
-
if (candidate.major > installed.major) return "major";
|
|
2258
|
-
if (candidate.minor > installed.minor) return "minor";
|
|
2259
|
-
if (candidate.patch > installed.patch || candidate.version === installed.version) {
|
|
2260
|
-
return "patch";
|
|
2261
|
-
}
|
|
2262
|
-
return void 0;
|
|
2263
|
-
}
|
|
2264
|
-
async function queryOutdatedPackages(cwd, options = {}) {
|
|
2265
|
-
const pm = options.packageManager ?? detectPackageManager(cwd);
|
|
2266
|
-
let stdout;
|
|
2267
|
-
try {
|
|
2268
|
-
const args = pm === "pnpm" ? ["outdated", "--json"] : ["outdated", "--json"];
|
|
2269
|
-
const yarnArgs = pm === "yarn" ? ["outdated", "--json"] : void 0;
|
|
2270
|
-
const finalArgs = yarnArgs ?? args;
|
|
2271
|
-
const result2 = await execa4(pm, finalArgs, {
|
|
2272
|
-
cwd,
|
|
2273
|
-
reject: false,
|
|
2274
|
-
stdio: "pipe"
|
|
2275
|
-
});
|
|
2276
|
-
if (result2.exitCode !== 0 && result2.exitCode !== 1) {
|
|
2277
|
-
const stderr = typeof result2.stderr === "string" ? result2.stderr : "";
|
|
2278
|
-
if (!result2.stdout) {
|
|
2279
|
-
throw new Error(
|
|
2280
|
-
`${pm} outdated --json failed (exit ${result2.exitCode}): ${stderr.slice(0, 500)}`
|
|
2281
|
-
);
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
|
-
stdout = typeof result2.stdout === "string" ? result2.stdout : "";
|
|
2285
|
-
} catch (err) {
|
|
2286
|
-
if (err instanceof Error && !err.message.includes("outdated --json failed")) {
|
|
2287
|
-
throw new Error(`Failed to query outdated packages: ${err.message}`);
|
|
2288
|
-
}
|
|
2289
|
-
throw err;
|
|
2290
|
-
}
|
|
2291
|
-
if (!stdout.trim()) {
|
|
2292
|
-
return /* @__PURE__ */ new Map();
|
|
2293
|
-
}
|
|
2294
|
-
const rawEntries = {};
|
|
2295
|
-
if (pm === "yarn") {
|
|
2296
|
-
for (const line of stdout.split("\n")) {
|
|
2297
|
-
const trimmed = line.trim();
|
|
2298
|
-
if (!trimmed) continue;
|
|
2299
|
-
try {
|
|
2300
|
-
const parsed = JSON.parse(trimmed);
|
|
2301
|
-
if (parsed.type === "table" && parsed.data) {
|
|
2302
|
-
const tableData = parsed.data;
|
|
2303
|
-
const head = tableData.head ?? [];
|
|
2304
|
-
const currentIdx = head.indexOf("Current");
|
|
2305
|
-
const wantedIdx = head.indexOf("Wanted");
|
|
2306
|
-
const latestIdx = head.indexOf("Latest");
|
|
2307
|
-
for (const row of tableData.body ?? []) {
|
|
2308
|
-
const name = row[0];
|
|
2309
|
-
if (!name) continue;
|
|
2310
|
-
rawEntries[name] = {
|
|
2311
|
-
current: row[currentIdx] ?? "",
|
|
2312
|
-
wanted: row[wantedIdx] ?? "",
|
|
2313
|
-
latest: row[latestIdx] ?? "",
|
|
2314
|
-
type: row[head.indexOf("Package Type")] ?? "dependencies"
|
|
2315
|
-
};
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
} catch {
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
} else {
|
|
2322
|
-
try {
|
|
2323
|
-
const parsed = JSON.parse(stdout);
|
|
2324
|
-
if (Array.isArray(parsed)) {
|
|
2325
|
-
for (const entry of parsed) {
|
|
2326
|
-
const name = entry.name ?? entry.packageName;
|
|
2327
|
-
if (!name) continue;
|
|
2328
|
-
rawEntries[name] = {
|
|
2329
|
-
current: entry.current,
|
|
2330
|
-
wanted: entry.wanted,
|
|
2331
|
-
latest: entry.latest,
|
|
2332
|
-
type: entry.dependencyType
|
|
2333
|
-
};
|
|
2334
|
-
}
|
|
2335
|
-
} else {
|
|
2336
|
-
Object.assign(rawEntries, parsed);
|
|
2337
|
-
}
|
|
2338
|
-
} catch {
|
|
2339
|
-
throw new Error(`Could not parse ${pm} outdated --json output.`);
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
let directDeps;
|
|
2343
|
-
try {
|
|
2344
|
-
const pkgRaw = JSON.parse(readFileSync8(join10(cwd, "package.json"), "utf8"));
|
|
2345
|
-
directDeps = /* @__PURE__ */ new Set([
|
|
2346
|
-
...Object.keys(pkgRaw.dependencies ?? {}),
|
|
2347
|
-
...Object.keys(pkgRaw.devDependencies ?? {})
|
|
2348
|
-
]);
|
|
2349
|
-
} catch {
|
|
2350
|
-
directDeps = /* @__PURE__ */ new Set();
|
|
2351
|
-
}
|
|
2352
|
-
const result = /* @__PURE__ */ new Map();
|
|
2353
|
-
for (const [name, entry] of Object.entries(rawEntries)) {
|
|
2354
|
-
const currentVersion = entry.current ?? "";
|
|
2355
|
-
const wantedVersion = entry.wanted ?? currentVersion;
|
|
2356
|
-
const latestVersion = entry.latest ?? currentVersion;
|
|
2357
|
-
if (!currentVersion || !latestVersion) continue;
|
|
2358
|
-
const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
|
|
2359
|
-
if (!options.includeTransitive && dependencyScope === "transitive") continue;
|
|
2360
|
-
const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
|
|
2361
|
-
result.set(name, {
|
|
2362
|
-
currentVersion,
|
|
2363
|
-
wantedVersion,
|
|
2364
|
-
latestVersion,
|
|
2365
|
-
isMajorBump,
|
|
2366
|
-
dependencyScope
|
|
2886
|
+
},
|
|
2887
|
+
policy: { exploitSignalOverride: opts.exploitSignalOverride }
|
|
2367
2888
|
});
|
|
2889
|
+
if (result.exploitSignalTriggered) {
|
|
2890
|
+
exploitSignalTriggered = true;
|
|
2891
|
+
}
|
|
2368
2892
|
}
|
|
2369
|
-
|
|
2893
|
+
let slaBreaches;
|
|
2894
|
+
if (opts.slaCheck && opts.slaPolicy && cveDetails.publishedAt) {
|
|
2895
|
+
const breach = checkSlaBreach(
|
|
2896
|
+
normalizedId,
|
|
2897
|
+
cveDetails.severity,
|
|
2898
|
+
cveDetails.publishedAt,
|
|
2899
|
+
opts.slaPolicy
|
|
2900
|
+
);
|
|
2901
|
+
if (breach) {
|
|
2902
|
+
slaBreaches = [breach];
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
return { suppressed: false, exploitSignalTriggered, slaBreaches };
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
// src/remediation/local/sbom.ts
|
|
2909
|
+
function buildSbom(packages, vulnerableNames, results) {
|
|
2910
|
+
const statusByPackage = /* @__PURE__ */ new Map();
|
|
2911
|
+
for (const result of results) {
|
|
2912
|
+
if (!vulnerableNames.has(result.packageName)) continue;
|
|
2913
|
+
if (result.suppressedBy) {
|
|
2914
|
+
statusByPackage.set(result.packageName, "suppressed");
|
|
2915
|
+
} else if (!result.applied && result.strategy === "none") {
|
|
2916
|
+
statusByPackage.set(result.packageName, "skipped");
|
|
2917
|
+
} else if (result.applied) {
|
|
2918
|
+
statusByPackage.set(result.packageName, "patched");
|
|
2919
|
+
} else {
|
|
2920
|
+
statusByPackage.set(result.packageName, "unpatched");
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
return packages.map((pkg) => {
|
|
2924
|
+
const isVulnerable = vulnerableNames.has(pkg.name);
|
|
2925
|
+
const entry = {
|
|
2926
|
+
name: pkg.name,
|
|
2927
|
+
version: pkg.version,
|
|
2928
|
+
scope: pkg.type === "direct" ? "direct" : "transitive"
|
|
2929
|
+
};
|
|
2930
|
+
if (isVulnerable) {
|
|
2931
|
+
entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
|
|
2932
|
+
}
|
|
2933
|
+
return entry;
|
|
2934
|
+
});
|
|
2370
2935
|
}
|
|
2371
2936
|
|
|
2372
2937
|
// src/remediation/tools/apply-version-bump.ts
|
|
2373
2938
|
import { z as z4 } from "zod";
|
|
2374
|
-
import { join as
|
|
2375
|
-
import { readFileSync as
|
|
2939
|
+
import { join as join14 } from "path";
|
|
2940
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
|
|
2376
2941
|
|
|
2377
2942
|
// src/platform/safe-exec.ts
|
|
2378
|
-
import { execa as
|
|
2943
|
+
import { execa as execa6 } from "execa";
|
|
2379
2944
|
var DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([
|
|
2380
2945
|
"NODE_OPTIONS",
|
|
2381
2946
|
"NODE_PATH",
|
|
@@ -2424,7 +2989,7 @@ function safeExeca(file, args = [], options = {}) {
|
|
|
2424
2989
|
options.env ?? process.env,
|
|
2425
2990
|
options.allowNodeOptions
|
|
2426
2991
|
);
|
|
2427
|
-
return
|
|
2992
|
+
return execa6(file, args, {
|
|
2428
2993
|
...options,
|
|
2429
2994
|
timeout,
|
|
2430
2995
|
env: sanitizedEnv
|
|
@@ -2432,19 +2997,19 @@ function safeExeca(file, args = [], options = {}) {
|
|
|
2432
2997
|
}
|
|
2433
2998
|
|
|
2434
2999
|
// src/remediation/tools/apply-version-bump.ts
|
|
2435
|
-
import
|
|
3000
|
+
import semver3 from "semver";
|
|
2436
3001
|
|
|
2437
3002
|
// src/platform/repo-lock.ts
|
|
2438
|
-
import { mkdir, rm } from "fs/promises";
|
|
2439
|
-
import { join as
|
|
3003
|
+
import { mkdir, rm as rm2 } from "fs/promises";
|
|
3004
|
+
import { join as join13 } from "path";
|
|
2440
3005
|
async function sleep(ms) {
|
|
2441
|
-
await new Promise((
|
|
3006
|
+
await new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
2442
3007
|
}
|
|
2443
3008
|
async function acquireRepoLock(cwd, options = {}) {
|
|
2444
3009
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
2445
3010
|
const retryDelayMs = options.retryDelayMs ?? 125;
|
|
2446
|
-
const lockRoot =
|
|
2447
|
-
const lockPath =
|
|
3011
|
+
const lockRoot = join13(cwd, ".autoremediator", "locks");
|
|
3012
|
+
const lockPath = join13(cwd, ".autoremediator", "locks", "remediation.lock");
|
|
2448
3013
|
const startedAt = Date.now();
|
|
2449
3014
|
await mkdir(lockRoot, { recursive: true });
|
|
2450
3015
|
while (true) {
|
|
@@ -2453,7 +3018,7 @@ async function acquireRepoLock(cwd, options = {}) {
|
|
|
2453
3018
|
return {
|
|
2454
3019
|
lockPath,
|
|
2455
3020
|
release: async () => {
|
|
2456
|
-
await
|
|
3021
|
+
await rm2(lockPath, { recursive: true, force: true });
|
|
2457
3022
|
}
|
|
2458
3023
|
};
|
|
2459
3024
|
} catch {
|
|
@@ -2485,6 +3050,7 @@ var applyVersionBumpTool = defineTool({
|
|
|
2485
3050
|
dryRun: z4.boolean().default(false).describe("If true, report changes but do not write"),
|
|
2486
3051
|
policy: z4.string().optional().describe("Optional path to .autoremediator policy file"),
|
|
2487
3052
|
runTests: z4.boolean().default(false).describe("If true, run test validation after applying the fix"),
|
|
3053
|
+
typecheck: z4.boolean().default(false).describe("If true, run typecheck validation after applying the fix"),
|
|
2488
3054
|
installMode: z4.enum(["standard", "prefer-offline", "deterministic"]).optional(),
|
|
2489
3055
|
installPreferOffline: z4.boolean().optional(),
|
|
2490
3056
|
enforceFrozenLockfile: z4.boolean().optional(),
|
|
@@ -2499,13 +3065,14 @@ var applyVersionBumpTool = defineTool({
|
|
|
2499
3065
|
dryRun,
|
|
2500
3066
|
policy,
|
|
2501
3067
|
runTests,
|
|
3068
|
+
typecheck,
|
|
2502
3069
|
installMode,
|
|
2503
3070
|
installPreferOffline,
|
|
2504
3071
|
enforceFrozenLockfile,
|
|
2505
3072
|
workspace
|
|
2506
3073
|
}) => {
|
|
2507
3074
|
const pm = packageManager ?? detectPackageManager(cwd);
|
|
2508
|
-
|
|
3075
|
+
let pkgPath = join14(cwd, "package.json");
|
|
2509
3076
|
const loadedPolicy = loadPolicy(cwd, policy);
|
|
2510
3077
|
const commandConstraints = {
|
|
2511
3078
|
...loadedPolicy.constraints,
|
|
@@ -2515,9 +3082,6 @@ var applyVersionBumpTool = defineTool({
|
|
|
2515
3082
|
workspace: workspace ?? loadedPolicy.constraints?.workspace
|
|
2516
3083
|
};
|
|
2517
3084
|
const yarnMajor = pm === "yarn" ? await getYarnMajorVersion(cwd) : void 0;
|
|
2518
|
-
const installCommand = resolveInstallCommand(pm, commandConstraints, yarnMajor);
|
|
2519
|
-
const testCommand = resolveTestCommand(pm, commandConstraints);
|
|
2520
|
-
const dedupeCommand = resolveDedupeCommand(pm, commandConstraints);
|
|
2521
3085
|
if (!isPackageAllowed(loadedPolicy, packageName)) {
|
|
2522
3086
|
return {
|
|
2523
3087
|
packageName,
|
|
@@ -2530,7 +3094,7 @@ var applyVersionBumpTool = defineTool({
|
|
|
2530
3094
|
message: `Policy blocked changes for package "${packageName}".`
|
|
2531
3095
|
};
|
|
2532
3096
|
}
|
|
2533
|
-
const isMajorBump =
|
|
3097
|
+
const isMajorBump = semver3.valid(fromVersion) && semver3.valid(toVersion) && semver3.major(toVersion) > semver3.major(fromVersion);
|
|
2534
3098
|
if (isMajorBump && !loadedPolicy.allowMajorBumps) {
|
|
2535
3099
|
return {
|
|
2536
3100
|
packageName,
|
|
@@ -2543,9 +3107,10 @@ var applyVersionBumpTool = defineTool({
|
|
|
2543
3107
|
message: `Policy blocked major bump for "${packageName}" (${fromVersion} -> ${toVersion}).`
|
|
2544
3108
|
};
|
|
2545
3109
|
}
|
|
3110
|
+
pkgPath = join14(cwd, "package.json");
|
|
2546
3111
|
let pkgJson;
|
|
2547
3112
|
try {
|
|
2548
|
-
pkgJson = JSON.parse(
|
|
3113
|
+
pkgJson = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
2549
3114
|
} catch {
|
|
2550
3115
|
return {
|
|
2551
3116
|
packageName,
|
|
@@ -2557,9 +3122,20 @@ var applyVersionBumpTool = defineTool({
|
|
|
2557
3122
|
message: `Could not read package.json at "${pkgPath}".`
|
|
2558
3123
|
};
|
|
2559
3124
|
}
|
|
2560
|
-
|
|
3125
|
+
let depField = ["dependencies", "devDependencies", "peerDependencies"].find(
|
|
2561
3126
|
(f) => pkgJson[f]?.[packageName] !== void 0
|
|
2562
3127
|
);
|
|
3128
|
+
if (!depField) {
|
|
3129
|
+
const declaringWorkspace = findDeclaringWorkspacePackage(cwd, packageName, workspace);
|
|
3130
|
+
if (declaringWorkspace) {
|
|
3131
|
+
pkgPath = declaringWorkspace.manifestPath;
|
|
3132
|
+
pkgJson = declaringWorkspace.manifest;
|
|
3133
|
+
depField = ["dependencies", "devDependencies", "peerDependencies"].find(
|
|
3134
|
+
(f) => pkgJson[f]?.[packageName] !== void 0
|
|
3135
|
+
);
|
|
3136
|
+
commandConstraints.workspace = workspace ?? declaringWorkspace.name;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
2563
3139
|
if (!depField) {
|
|
2564
3140
|
return {
|
|
2565
3141
|
packageName,
|
|
@@ -2575,9 +3151,14 @@ var applyVersionBumpTool = defineTool({
|
|
|
2575
3151
|
const prefixMatch = currentRange.match(/^([~^]?)/);
|
|
2576
3152
|
const prefix = prefixMatch?.[1] ?? "";
|
|
2577
3153
|
const newRange = `${prefix}${toVersion}`;
|
|
3154
|
+
const effectiveInstallCommand = resolveInstallCommand(pm, commandConstraints, yarnMajor);
|
|
3155
|
+
const effectiveTestCommand = resolveTestCommand(pm, commandConstraints);
|
|
3156
|
+
const effectiveTypecheckCommand = resolveTypecheckCommand(pm, commandConstraints, cwd);
|
|
3157
|
+
const effectiveDedupeCommand = resolveDedupeCommand(pm, commandConstraints);
|
|
3158
|
+
const shouldTypecheck = typecheck || Boolean(commandConstraints.typecheck);
|
|
2578
3159
|
if (dryRun) {
|
|
2579
|
-
const installCmd =
|
|
2580
|
-
const testCmd =
|
|
3160
|
+
const installCmd = effectiveInstallCommand.join(" ");
|
|
3161
|
+
const testCmd = effectiveTestCommand.join(" ");
|
|
2581
3162
|
return {
|
|
2582
3163
|
packageName,
|
|
2583
3164
|
strategy: "version-bump",
|
|
@@ -2585,14 +3166,14 @@ var applyVersionBumpTool = defineTool({
|
|
|
2585
3166
|
toVersion,
|
|
2586
3167
|
applied: false,
|
|
2587
3168
|
dryRun: true,
|
|
2588
|
-
message: `[DRY RUN] Would update ${depField}.${packageName}: "${currentRange}" -> "${newRange}", then run ${installCmd}${runTests ? ` and ${testCmd}` : ""}${
|
|
3169
|
+
message: `[DRY RUN] Would update ${depField}.${packageName}: "${currentRange}" -> "${newRange}", then run ${installCmd}${runTests ? ` and ${testCmd}` : ""}${effectiveDedupeCommand.length > 0 ? ` and ${effectiveDedupeCommand.join(" ")} (best-effort)` : ""}.`
|
|
2589
3170
|
};
|
|
2590
3171
|
}
|
|
2591
3172
|
return withRepoLock(cwd, async () => {
|
|
2592
3173
|
pkgJson[depField][packageName] = newRange;
|
|
2593
3174
|
writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
2594
3175
|
try {
|
|
2595
|
-
const [installCmd, ...installArgs] =
|
|
3176
|
+
const [installCmd, ...installArgs] = effectiveInstallCommand;
|
|
2596
3177
|
await safeExeca(installCmd, installArgs, {
|
|
2597
3178
|
cwd,
|
|
2598
3179
|
stdio: "pipe"
|
|
@@ -2609,12 +3190,12 @@ var applyVersionBumpTool = defineTool({
|
|
|
2609
3190
|
applied: false,
|
|
2610
3191
|
dryRun: false,
|
|
2611
3192
|
unresolvedReason: "install-failed",
|
|
2612
|
-
message: `${
|
|
3193
|
+
message: `${effectiveInstallCommand.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
|
|
2613
3194
|
};
|
|
2614
3195
|
}
|
|
2615
3196
|
if (runTests) {
|
|
2616
3197
|
try {
|
|
2617
|
-
const [testCmd, ...testArgs] =
|
|
3198
|
+
const [testCmd, ...testArgs] = effectiveTestCommand;
|
|
2618
3199
|
await safeExeca(testCmd, testArgs, {
|
|
2619
3200
|
cwd,
|
|
2620
3201
|
stdio: "pipe"
|
|
@@ -2623,7 +3204,38 @@ var applyVersionBumpTool = defineTool({
|
|
|
2623
3204
|
pkgJson[depField][packageName] = currentRange;
|
|
2624
3205
|
writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
2625
3206
|
try {
|
|
2626
|
-
const [rollbackCmd, ...rollbackArgs] =
|
|
3207
|
+
const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
|
|
3208
|
+
await safeExeca(rollbackCmd, rollbackArgs, {
|
|
3209
|
+
cwd,
|
|
3210
|
+
stdio: "pipe"
|
|
3211
|
+
});
|
|
3212
|
+
} catch {
|
|
3213
|
+
}
|
|
3214
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3215
|
+
return {
|
|
3216
|
+
packageName,
|
|
3217
|
+
strategy: "version-bump",
|
|
3218
|
+
fromVersion,
|
|
3219
|
+
toVersion,
|
|
3220
|
+
applied: false,
|
|
3221
|
+
dryRun: false,
|
|
3222
|
+
unresolvedReason: "validation-failed",
|
|
3223
|
+
message: `${effectiveTestCommand.join(" ")} failed after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
|
|
3224
|
+
};
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
if (shouldTypecheck && effectiveTypecheckCommand.length > 0) {
|
|
3228
|
+
try {
|
|
3229
|
+
const [tcCmd, ...tcArgs] = effectiveTypecheckCommand;
|
|
3230
|
+
await safeExeca(tcCmd, tcArgs, {
|
|
3231
|
+
cwd,
|
|
3232
|
+
stdio: "pipe"
|
|
3233
|
+
});
|
|
3234
|
+
} catch (err) {
|
|
3235
|
+
pkgJson[depField][packageName] = currentRange;
|
|
3236
|
+
writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
3237
|
+
try {
|
|
3238
|
+
const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
|
|
2627
3239
|
await safeExeca(rollbackCmd, rollbackArgs, {
|
|
2628
3240
|
cwd,
|
|
2629
3241
|
stdio: "pipe"
|
|
@@ -2639,20 +3251,22 @@ var applyVersionBumpTool = defineTool({
|
|
|
2639
3251
|
applied: false,
|
|
2640
3252
|
dryRun: false,
|
|
2641
3253
|
unresolvedReason: "validation-failed",
|
|
2642
|
-
message:
|
|
3254
|
+
message: `TypeCheck validation failed (${effectiveTypecheckCommand.join(" ")}) after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
|
|
2643
3255
|
};
|
|
2644
3256
|
}
|
|
2645
3257
|
}
|
|
2646
3258
|
let dedupeNote = "";
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
3259
|
+
if (effectiveDedupeCommand.length > 0) {
|
|
3260
|
+
try {
|
|
3261
|
+
const [dedupeCmd, ...dedupeArgs] = effectiveDedupeCommand;
|
|
3262
|
+
await safeExeca(dedupeCmd, dedupeArgs, {
|
|
3263
|
+
cwd,
|
|
3264
|
+
stdio: "pipe"
|
|
3265
|
+
});
|
|
3266
|
+
} catch (err) {
|
|
3267
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3268
|
+
dedupeNote = ` Dedupe warning: ${effectiveDedupeCommand.join(" ")} failed (${message}).`;
|
|
3269
|
+
}
|
|
2656
3270
|
}
|
|
2657
3271
|
return {
|
|
2658
3272
|
packageName,
|
|
@@ -2661,7 +3275,7 @@ var applyVersionBumpTool = defineTool({
|
|
|
2661
3275
|
toVersion,
|
|
2662
3276
|
applied: true,
|
|
2663
3277
|
dryRun: false,
|
|
2664
|
-
message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion}, ran ${
|
|
3278
|
+
message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion} in ${depField}, then ran ${effectiveInstallCommand.join(" ")}${runTests ? ` and passed ${effectiveTestCommand.join(" ")}` : ""}.${dedupeNote}`
|
|
2665
3279
|
};
|
|
2666
3280
|
});
|
|
2667
3281
|
}
|
|
@@ -2669,9 +3283,9 @@ var applyVersionBumpTool = defineTool({
|
|
|
2669
3283
|
|
|
2670
3284
|
// src/remediation/tools/apply-package-override/index.ts
|
|
2671
3285
|
import { z as z5 } from "zod";
|
|
2672
|
-
import { join as
|
|
2673
|
-
import { existsSync as
|
|
2674
|
-
import
|
|
3286
|
+
import { join as join15 } from "path";
|
|
3287
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "fs";
|
|
3288
|
+
import semver4 from "semver";
|
|
2675
3289
|
|
|
2676
3290
|
// src/remediation/tools/apply-package-override/helpers.ts
|
|
2677
3291
|
async function collectDependencyTrace(cwd, pm, packageName, constraints) {
|
|
@@ -2813,9 +3427,9 @@ var applyPackageOverrideTool = defineTool({
|
|
|
2813
3427
|
workspace
|
|
2814
3428
|
}) => {
|
|
2815
3429
|
const pm = packageManager ?? detectPackageManager(cwd);
|
|
2816
|
-
const pkgPath =
|
|
2817
|
-
const denoJsonPath =
|
|
2818
|
-
const isDenoNative = pm === "deno" && !
|
|
3430
|
+
const pkgPath = join15(cwd, "package.json");
|
|
3431
|
+
const denoJsonPath = join15(cwd, "deno.json");
|
|
3432
|
+
const isDenoNative = pm === "deno" && !existsSync11(pkgPath);
|
|
2819
3433
|
const loadedPolicy = loadPolicy(cwd, policy);
|
|
2820
3434
|
const commandConstraints = {
|
|
2821
3435
|
...loadedPolicy.constraints,
|
|
@@ -2841,7 +3455,7 @@ var applyPackageOverrideTool = defineTool({
|
|
|
2841
3455
|
message: `Policy blocked changes for package "${packageName}".`
|
|
2842
3456
|
};
|
|
2843
3457
|
}
|
|
2844
|
-
const isMajorBump =
|
|
3458
|
+
const isMajorBump = semver4.valid(fromVersion) && semver4.valid(toVersion) && semver4.major(toVersion) > semver4.major(fromVersion);
|
|
2845
3459
|
if (isMajorBump && !loadedPolicy.allowMajorBumps) {
|
|
2846
3460
|
return {
|
|
2847
3461
|
packageName,
|
|
@@ -2857,7 +3471,7 @@ var applyPackageOverrideTool = defineTool({
|
|
|
2857
3471
|
if (isDenoNative) {
|
|
2858
3472
|
let denoJson;
|
|
2859
3473
|
try {
|
|
2860
|
-
denoJson = JSON.parse(
|
|
3474
|
+
denoJson = JSON.parse(readFileSync12(denoJsonPath, "utf8"));
|
|
2861
3475
|
} catch {
|
|
2862
3476
|
return {
|
|
2863
3477
|
packageName,
|
|
@@ -2960,7 +3574,7 @@ var applyPackageOverrideTool = defineTool({
|
|
|
2960
3574
|
}
|
|
2961
3575
|
let pkgJson;
|
|
2962
3576
|
try {
|
|
2963
|
-
pkgJson = JSON.parse(
|
|
3577
|
+
pkgJson = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
2964
3578
|
} catch {
|
|
2965
3579
|
return {
|
|
2966
3580
|
packageName,
|
|
@@ -3057,6 +3671,76 @@ var applyPackageOverrideTool = defineTool({
|
|
|
3057
3671
|
}
|
|
3058
3672
|
});
|
|
3059
3673
|
|
|
3674
|
+
// src/platform/supply-chain.ts
|
|
3675
|
+
async function verifyPackageTrust(packageName, targetVersion, policy) {
|
|
3676
|
+
if (!policy || !policy.minPublishAgeHours && !policy.blockInstallScripts && !policy.requireProvenance) {
|
|
3677
|
+
return { trusted: true };
|
|
3678
|
+
}
|
|
3679
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
|
|
3680
|
+
let packument = null;
|
|
3681
|
+
try {
|
|
3682
|
+
const res = await httpClient({
|
|
3683
|
+
url,
|
|
3684
|
+
headers: { Accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8" }
|
|
3685
|
+
});
|
|
3686
|
+
if (res.ok && res.data) {
|
|
3687
|
+
packument = res.data;
|
|
3688
|
+
}
|
|
3689
|
+
} catch (err) {
|
|
3690
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3691
|
+
return {
|
|
3692
|
+
trusted: false,
|
|
3693
|
+
reason: `Could not verify supply-chain trust for "${packageName}@${targetVersion}": ${msg}`
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
if (!packument) {
|
|
3697
|
+
return {
|
|
3698
|
+
trusted: false,
|
|
3699
|
+
reason: `Package packument for "${packageName}" not found on registry.`
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3702
|
+
let publishAgeHours;
|
|
3703
|
+
if (packument.time?.[targetVersion]) {
|
|
3704
|
+
const publishedAt = new Date(packument.time[targetVersion]).getTime();
|
|
3705
|
+
publishAgeHours = Math.max(0, (Date.now() - publishedAt) / (1e3 * 60 * 60));
|
|
3706
|
+
if (policy.minPublishAgeHours && publishAgeHours < policy.minPublishAgeHours) {
|
|
3707
|
+
return {
|
|
3708
|
+
trusted: false,
|
|
3709
|
+
publishAgeHours,
|
|
3710
|
+
reason: `Target release "${packageName}@${targetVersion}" was published only ${publishAgeHours.toFixed(1)}h ago (minimum required age is ${policy.minPublishAgeHours}h).`
|
|
3711
|
+
};
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
const versionData = packument.versions?.[targetVersion];
|
|
3715
|
+
const scripts = versionData?.scripts ?? {};
|
|
3716
|
+
const installScriptKeys = ["preinstall", "install", "postinstall"].filter((k) => Boolean(scripts[k]));
|
|
3717
|
+
const hasInstallScripts = installScriptKeys.length > 0;
|
|
3718
|
+
if (policy.blockInstallScripts && hasInstallScripts) {
|
|
3719
|
+
return {
|
|
3720
|
+
trusted: false,
|
|
3721
|
+
publishAgeHours,
|
|
3722
|
+
hasInstallScripts: true,
|
|
3723
|
+
reason: `Target release "${packageName}@${targetVersion}" defines risky lifecycle scripts (${installScriptKeys.join(", ")}).`
|
|
3724
|
+
};
|
|
3725
|
+
}
|
|
3726
|
+
const hasProvenance = Boolean(versionData?.dist?.attestations?.provenance || versionData?.dist?.attestations?.url);
|
|
3727
|
+
if (policy.requireProvenance && !hasProvenance) {
|
|
3728
|
+
return {
|
|
3729
|
+
trusted: false,
|
|
3730
|
+
publishAgeHours,
|
|
3731
|
+
hasInstallScripts,
|
|
3732
|
+
hasProvenance: false,
|
|
3733
|
+
reason: `Target release "${packageName}@${targetVersion}" does not have cryptographic build provenance attestations.`
|
|
3734
|
+
};
|
|
3735
|
+
}
|
|
3736
|
+
return {
|
|
3737
|
+
trusted: true,
|
|
3738
|
+
publishAgeHours,
|
|
3739
|
+
hasInstallScripts,
|
|
3740
|
+
hasProvenance
|
|
3741
|
+
};
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3060
3744
|
// src/remediation/local/primary-strategy.ts
|
|
3061
3745
|
async function resolvePrimaryResult(params) {
|
|
3062
3746
|
const { vulnerable, cwd, packageManager, dryRun, policy, runTests, constraints } = params;
|
|
@@ -3127,6 +3811,22 @@ async function resolvePrimaryResult(params) {
|
|
|
3127
3811
|
}
|
|
3128
3812
|
};
|
|
3129
3813
|
}
|
|
3814
|
+
const trust2 = await verifyPackageTrust(pkg.name, safeUpgrade2.safeVersion, constraints.supplyChainPolicy);
|
|
3815
|
+
if (!trust2.trusted) {
|
|
3816
|
+
return {
|
|
3817
|
+
steps: 1,
|
|
3818
|
+
result: {
|
|
3819
|
+
packageName: pkg.name,
|
|
3820
|
+
strategy: "none",
|
|
3821
|
+
fromVersion: pkg.version,
|
|
3822
|
+
applied: false,
|
|
3823
|
+
dryRun,
|
|
3824
|
+
unresolvedReason: "policy-blocked",
|
|
3825
|
+
vulnerableRange: vulnerable.affected.vulnerableRange,
|
|
3826
|
+
message: `Supply chain trust gate blocked "${pkg.name}@${safeUpgrade2.safeVersion}": ${trust2.reason}`
|
|
3827
|
+
}
|
|
3828
|
+
};
|
|
3829
|
+
}
|
|
3130
3830
|
const overrideResult = await applyPackageOverrideTool.execute({
|
|
3131
3831
|
cwd,
|
|
3132
3832
|
packageManager,
|
|
@@ -3182,6 +3882,22 @@ async function resolvePrimaryResult(params) {
|
|
|
3182
3882
|
}
|
|
3183
3883
|
};
|
|
3184
3884
|
}
|
|
3885
|
+
const trust = await verifyPackageTrust(pkg.name, safeUpgrade.safeVersion, constraints.supplyChainPolicy);
|
|
3886
|
+
if (!trust.trusted) {
|
|
3887
|
+
return {
|
|
3888
|
+
steps: 1,
|
|
3889
|
+
result: {
|
|
3890
|
+
packageName: pkg.name,
|
|
3891
|
+
strategy: "none",
|
|
3892
|
+
fromVersion: pkg.version,
|
|
3893
|
+
applied: false,
|
|
3894
|
+
dryRun,
|
|
3895
|
+
unresolvedReason: "policy-blocked",
|
|
3896
|
+
vulnerableRange: vulnerable.affected.vulnerableRange,
|
|
3897
|
+
message: `Supply chain trust gate blocked "${pkg.name}@${safeUpgrade.safeVersion}": ${trust.reason}`
|
|
3898
|
+
}
|
|
3899
|
+
};
|
|
3900
|
+
}
|
|
3185
3901
|
const applyResult = await applyVersionBumpTool.execute({
|
|
3186
3902
|
cwd,
|
|
3187
3903
|
packageManager,
|
|
@@ -3191,6 +3907,7 @@ async function resolvePrimaryResult(params) {
|
|
|
3191
3907
|
dryRun,
|
|
3192
3908
|
policy,
|
|
3193
3909
|
runTests,
|
|
3910
|
+
typecheck: constraints.typecheck,
|
|
3194
3911
|
installMode: constraints.installMode,
|
|
3195
3912
|
installPreferOffline: constraints.installPreferOffline,
|
|
3196
3913
|
enforceFrozenLockfile: constraints.enforceFrozenLockfile,
|
|
@@ -3204,10 +3921,10 @@ async function resolvePrimaryResult(params) {
|
|
|
3204
3921
|
|
|
3205
3922
|
// src/remediation/tools/fetch-package-source.ts
|
|
3206
3923
|
import { z as z6 } from "zod";
|
|
3207
|
-
import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as
|
|
3208
|
-
import { join as
|
|
3924
|
+
import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile4, rm as rm3 } from "fs/promises";
|
|
3925
|
+
import { join as join16 } from "path";
|
|
3209
3926
|
import { tmpdir } from "os";
|
|
3210
|
-
import { execa as
|
|
3927
|
+
import { execa as execa7 } from "execa";
|
|
3211
3928
|
var fetchPackageSourceTool = defineTool({
|
|
3212
3929
|
description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
|
|
3213
3930
|
parameters: z6.object({
|
|
@@ -3225,25 +3942,25 @@ var fetchPackageSourceTool = defineTool({
|
|
|
3225
3942
|
if (safePatterns.length === 0) {
|
|
3226
3943
|
return { success: false, error: "No valid file patterns provided." };
|
|
3227
3944
|
}
|
|
3228
|
-
const tempBaseDir = await mkdtemp(
|
|
3229
|
-
const extractDir =
|
|
3945
|
+
const tempBaseDir = await mkdtemp(join16(tmpdir(), "autoremediator-pkg-"));
|
|
3946
|
+
const extractDir = join16(tempBaseDir, "out");
|
|
3230
3947
|
try {
|
|
3231
3948
|
const scopedName = packageName.split("/").pop();
|
|
3232
3949
|
const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
|
|
3233
3950
|
await mkdir2(tempBaseDir, { recursive: true });
|
|
3234
|
-
const tarballPath =
|
|
3235
|
-
await
|
|
3951
|
+
const tarballPath = join16(tempBaseDir, "package.tgz");
|
|
3952
|
+
await execa7("curl", ["-L", "-o", tarballPath, npmUrl]);
|
|
3236
3953
|
await mkdir2(extractDir, { recursive: true });
|
|
3237
|
-
await
|
|
3954
|
+
await execa7("tar", ["-xzf", tarballPath, "-C", extractDir]);
|
|
3238
3955
|
const extractedContents = await readdir2(extractDir);
|
|
3239
|
-
const packageRootDir = extractedContents.includes("package") ?
|
|
3956
|
+
const packageRootDir = extractedContents.includes("package") ? join16(extractDir, "package") : extractDir;
|
|
3240
3957
|
const sourceCode = {};
|
|
3241
3958
|
async function walkDir(dir, relativeBase) {
|
|
3242
3959
|
try {
|
|
3243
3960
|
const files = await readdir2(dir, { withFileTypes: true });
|
|
3244
3961
|
for (const file of files) {
|
|
3245
|
-
const fullPath =
|
|
3246
|
-
const relPath =
|
|
3962
|
+
const fullPath = join16(dir, file.name);
|
|
3963
|
+
const relPath = join16(relativeBase, file.name);
|
|
3247
3964
|
if (file.isDirectory()) {
|
|
3248
3965
|
if (!["node_modules", ".git", "dist", "build", "coverage", ".next", "out"].includes(
|
|
3249
3966
|
file.name
|
|
@@ -3257,7 +3974,7 @@ var fetchPackageSourceTool = defineTool({
|
|
|
3257
3974
|
});
|
|
3258
3975
|
if (matches) {
|
|
3259
3976
|
try {
|
|
3260
|
-
const content = await
|
|
3977
|
+
const content = await readFile4(fullPath, "utf8");
|
|
3261
3978
|
sourceCode[relPath] = content;
|
|
3262
3979
|
} catch {
|
|
3263
3980
|
}
|
|
@@ -3292,7 +4009,7 @@ var fetchPackageSourceTool = defineTool({
|
|
|
3292
4009
|
error: `Failed to fetch and extract package ${packageName}@${version}: ${message}`
|
|
3293
4010
|
};
|
|
3294
4011
|
} finally {
|
|
3295
|
-
await
|
|
4012
|
+
await rm3(tempBaseDir, { recursive: true, force: true });
|
|
3296
4013
|
}
|
|
3297
4014
|
}
|
|
3298
4015
|
});
|
|
@@ -3350,6 +4067,7 @@ Analyze the source code to:
|
|
|
3350
4067
|
2. Explain the root cause of the security issue
|
|
3351
4068
|
3. Propose a secure fix that addresses the vulnerability
|
|
3352
4069
|
4. Provide the complete fixed version of affected files
|
|
4070
|
+
5. Optionally provide a standalone Node.js reproduction/regression test script
|
|
3353
4071
|
|
|
3354
4072
|
## Response Format
|
|
3355
4073
|
Respond ONLY with valid JSON (no markdown, no extra text):
|
|
@@ -3360,7 +4078,8 @@ Respond ONLY with valid JSON (no markdown, no extra text):
|
|
|
3360
4078
|
"path/to/other.ts": "Complete fixed source code for this file"
|
|
3361
4079
|
},
|
|
3362
4080
|
"confidence": 0.95,
|
|
3363
|
-
"riskLevel": "medium"
|
|
4081
|
+
"riskLevel": "medium",
|
|
4082
|
+
"reproductionTest": "// Node.js script asserting the patch resolves the exploit/regression"
|
|
3364
4083
|
}
|
|
3365
4084
|
|
|
3366
4085
|
Important:
|
|
@@ -3640,6 +4359,7 @@ var generatePatchTool = defineTool({
|
|
|
3640
4359
|
success: true,
|
|
3641
4360
|
patches,
|
|
3642
4361
|
patchContent: patches[0]?.unifiedDiff,
|
|
4362
|
+
reproductionTest: analysis.reproductionTest,
|
|
3643
4363
|
llmProvider: provider,
|
|
3644
4364
|
llmModel: modelName,
|
|
3645
4365
|
latencyMs,
|
|
@@ -3664,17 +4384,17 @@ var generatePatchTool = defineTool({
|
|
|
3664
4384
|
|
|
3665
4385
|
// src/remediation/tools/apply-patch-file/index.ts
|
|
3666
4386
|
import { z as z8 } from "zod";
|
|
3667
|
-
import { mkdir as mkdir3, writeFile as
|
|
3668
|
-
import { join as
|
|
3669
|
-
import { execa as
|
|
4387
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
4388
|
+
import { join as join18 } from "path";
|
|
4389
|
+
import { execa as execa9 } from "execa";
|
|
3670
4390
|
|
|
3671
4391
|
// src/remediation/tools/apply-patch-file/helpers.ts
|
|
3672
|
-
import { existsSync as
|
|
3673
|
-
import { mkdtemp as mkdtemp2, readFile as
|
|
4392
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4393
|
+
import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm4, writeFile as writeFile2 } from "fs/promises";
|
|
3674
4394
|
import { createHash } from "crypto";
|
|
3675
4395
|
import { tmpdir as tmpdir2 } from "os";
|
|
3676
|
-
import { join as
|
|
3677
|
-
import { execa as
|
|
4396
|
+
import { join as join17 } from "path";
|
|
4397
|
+
import { execa as execa8 } from "execa";
|
|
3678
4398
|
async function resolvePatchMode(packageManager, cwd) {
|
|
3679
4399
|
if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno")
|
|
3680
4400
|
return "patch-package";
|
|
@@ -3705,13 +4425,13 @@ function computePatchIntegrity(patchContent) {
|
|
|
3705
4425
|
return `sha256:${hex}`;
|
|
3706
4426
|
}
|
|
3707
4427
|
async function writePatchManifest(manifestFilePath, artifact) {
|
|
3708
|
-
await
|
|
4428
|
+
await writeFile2(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
|
|
3709
4429
|
}
|
|
3710
4430
|
async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
3711
|
-
const pkgJsonPath =
|
|
4431
|
+
const pkgJsonPath = join17(cwd, "package.json");
|
|
3712
4432
|
let pkgJson;
|
|
3713
4433
|
try {
|
|
3714
|
-
pkgJson = JSON.parse(await
|
|
4434
|
+
pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf8"));
|
|
3715
4435
|
} catch {
|
|
3716
4436
|
return {
|
|
3717
4437
|
success: false,
|
|
@@ -3723,7 +4443,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
|
3723
4443
|
try {
|
|
3724
4444
|
const commands = getPackageManagerCommands(packageManager);
|
|
3725
4445
|
const [cmd, ...args] = commands.installDev("patch-package");
|
|
3726
|
-
await
|
|
4446
|
+
await execa8(cmd, args, {
|
|
3727
4447
|
cwd,
|
|
3728
4448
|
stdio: "pipe"
|
|
3729
4449
|
});
|
|
@@ -3744,13 +4464,13 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
|
3744
4464
|
} else if (!currentPostinstall) {
|
|
3745
4465
|
pkgJson.scripts.postinstall = patchApplyCmd;
|
|
3746
4466
|
}
|
|
3747
|
-
await
|
|
4467
|
+
await writeFile2(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
3748
4468
|
return { success: true };
|
|
3749
4469
|
}
|
|
3750
4470
|
async function capturePackageJsonSnapshot(cwd) {
|
|
3751
|
-
const path =
|
|
4471
|
+
const path = join17(cwd, "package.json");
|
|
3752
4472
|
try {
|
|
3753
|
-
const content = await
|
|
4473
|
+
const content = await readFile5(path, "utf8");
|
|
3754
4474
|
return { path, content };
|
|
3755
4475
|
} catch {
|
|
3756
4476
|
return void 0;
|
|
@@ -3766,19 +4486,19 @@ async function cleanupPatchArtifacts(params) {
|
|
|
3766
4486
|
installCommand,
|
|
3767
4487
|
rerunInstall
|
|
3768
4488
|
} = params;
|
|
3769
|
-
await
|
|
4489
|
+
await rm4(patchFilePath, { force: true }).catch(() => void 0);
|
|
3770
4490
|
if (manifestFilePath) {
|
|
3771
|
-
await
|
|
4491
|
+
await rm4(manifestFilePath, { force: true }).catch(() => void 0);
|
|
3772
4492
|
}
|
|
3773
4493
|
if (patchMode === "patch-package" && packageJsonSnapshot) {
|
|
3774
|
-
await
|
|
4494
|
+
await writeFile2(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
|
|
3775
4495
|
() => void 0
|
|
3776
4496
|
);
|
|
3777
4497
|
}
|
|
3778
4498
|
if (!rerunInstall) return;
|
|
3779
4499
|
try {
|
|
3780
4500
|
const [installCmd, ...installArgs] = installCommand;
|
|
3781
|
-
await
|
|
4501
|
+
await execa8(installCmd, installArgs, {
|
|
3782
4502
|
cwd,
|
|
3783
4503
|
stdio: "pipe"
|
|
3784
4504
|
});
|
|
@@ -3792,7 +4512,7 @@ async function applyNativePatch(params) {
|
|
|
3792
4512
|
const createArgs = ["patch", packageSpec];
|
|
3793
4513
|
let patchDir;
|
|
3794
4514
|
try {
|
|
3795
|
-
const createResult = await
|
|
4515
|
+
const createResult = await execa8(createCommand, createArgs, {
|
|
3796
4516
|
cwd,
|
|
3797
4517
|
stdio: "pipe"
|
|
3798
4518
|
});
|
|
@@ -3820,17 +4540,17 @@ ${createResult.stderr}`);
|
|
|
3820
4540
|
error: `Could not determine native patch directory for ${packageSpec}.`
|
|
3821
4541
|
};
|
|
3822
4542
|
}
|
|
3823
|
-
const tempPatchDir = await mkdtemp2(
|
|
3824
|
-
const tempPatchFile =
|
|
4543
|
+
const tempPatchDir = await mkdtemp2(join17(tmpdir2(), "autoremediator-native-patch-"));
|
|
4544
|
+
const tempPatchFile = join17(tempPatchDir, "change.patch");
|
|
3825
4545
|
try {
|
|
3826
|
-
await
|
|
3827
|
-
await
|
|
4546
|
+
await writeFile2(tempPatchFile, patchContent, "utf8");
|
|
4547
|
+
await execa8("patch", ["-p1", "-i", tempPatchFile], {
|
|
3828
4548
|
cwd: patchDir,
|
|
3829
4549
|
stdio: "pipe"
|
|
3830
4550
|
});
|
|
3831
4551
|
const commitCommand = patchMode === "native-pnpm" ? "pnpm" : "yarn";
|
|
3832
4552
|
const commitArgs = patchMode === "native-pnpm" ? ["patch-commit", patchDir] : ["patch-commit", "-s", patchDir];
|
|
3833
|
-
await
|
|
4553
|
+
await execa8(commitCommand, commitArgs, {
|
|
3834
4554
|
cwd,
|
|
3835
4555
|
stdio: "pipe"
|
|
3836
4556
|
});
|
|
@@ -3845,19 +4565,19 @@ ${createResult.stderr}`);
|
|
|
3845
4565
|
error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
|
|
3846
4566
|
};
|
|
3847
4567
|
} finally {
|
|
3848
|
-
await
|
|
4568
|
+
await rm4(tempPatchDir, { recursive: true, force: true });
|
|
3849
4569
|
}
|
|
3850
4570
|
return { success: true };
|
|
3851
4571
|
}
|
|
3852
4572
|
function extractPatchDirectory(output) {
|
|
3853
4573
|
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
3854
4574
|
for (const line of lines) {
|
|
3855
|
-
if (
|
|
4575
|
+
if (existsSync12(line)) {
|
|
3856
4576
|
return line;
|
|
3857
4577
|
}
|
|
3858
4578
|
const tokens = line.split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, ""));
|
|
3859
4579
|
for (const token of tokens) {
|
|
3860
|
-
if (token.startsWith("/") &&
|
|
4580
|
+
if (token.startsWith("/") && existsSync12(token)) {
|
|
3861
4581
|
return token;
|
|
3862
4582
|
}
|
|
3863
4583
|
}
|
|
@@ -3867,7 +4587,7 @@ function extractPatchDirectory(output) {
|
|
|
3867
4587
|
async function validatePatchWithTests(cwd, testCommand) {
|
|
3868
4588
|
try {
|
|
3869
4589
|
const [cmd, ...args] = testCommand;
|
|
3870
|
-
const result = await
|
|
4590
|
+
const result = await execa8(cmd, args, {
|
|
3871
4591
|
cwd,
|
|
3872
4592
|
timeout: 6e4,
|
|
3873
4593
|
stdio: "pipe"
|
|
@@ -3927,6 +4647,7 @@ var applyPatchFileTool = defineTool({
|
|
|
3927
4647
|
enforceFrozenLockfile: z8.boolean().optional(),
|
|
3928
4648
|
workspace: z8.string().optional(),
|
|
3929
4649
|
validateWithTests: z8.boolean().optional().default(true).describe("Run package manager test command to validate patch doesn't break anything"),
|
|
4650
|
+
reproductionTest: z8.string().optional().describe("Optional synthesized reproduction test script verifying fix"),
|
|
3930
4651
|
dryRun: z8.boolean().optional().default(false).describe("If true, report but do not mutate files")
|
|
3931
4652
|
}).refine((value) => Boolean(value.patchContent || value.patches && value.patches.length > 0), {
|
|
3932
4653
|
message: "Either patchContent or patches must be provided"
|
|
@@ -3948,6 +4669,7 @@ var applyPatchFileTool = defineTool({
|
|
|
3948
4669
|
workspace,
|
|
3949
4670
|
riskLevel,
|
|
3950
4671
|
validateWithTests,
|
|
4672
|
+
reproductionTest,
|
|
3951
4673
|
dryRun
|
|
3952
4674
|
}) => {
|
|
3953
4675
|
try {
|
|
@@ -3998,7 +4720,7 @@ var applyPatchFileTool = defineTool({
|
|
|
3998
4720
|
};
|
|
3999
4721
|
}
|
|
4000
4722
|
const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
|
|
4001
|
-
const patchFilePath =
|
|
4723
|
+
const patchFilePath = join18(cwd, patchesDir, patchFileName);
|
|
4002
4724
|
const manifestFilePath = `${patchFilePath}.json`;
|
|
4003
4725
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4004
4726
|
const baseArtifact = {
|
|
@@ -4037,9 +4759,9 @@ var applyPatchFileTool = defineTool({
|
|
|
4037
4759
|
}
|
|
4038
4760
|
return withRepoLock(cwd, async () => {
|
|
4039
4761
|
const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
|
|
4040
|
-
const patchesDirPath =
|
|
4762
|
+
const patchesDirPath = join18(cwd, patchesDir);
|
|
4041
4763
|
await mkdir3(patchesDirPath, { recursive: true });
|
|
4042
|
-
await
|
|
4764
|
+
await writeFile3(patchFilePath, selectedPatch, "utf8");
|
|
4043
4765
|
validationPhases.push({
|
|
4044
4766
|
phase: "patch-write",
|
|
4045
4767
|
passed: true,
|
|
@@ -4107,7 +4829,7 @@ var applyPatchFileTool = defineTool({
|
|
|
4107
4829
|
if (patchMode === "patch-package") {
|
|
4108
4830
|
try {
|
|
4109
4831
|
const [installCmd, ...installArgs] = installCommand;
|
|
4110
|
-
await
|
|
4832
|
+
await execa9(installCmd, installArgs, {
|
|
4111
4833
|
cwd,
|
|
4112
4834
|
stdio: "pipe"
|
|
4113
4835
|
});
|
|
@@ -4200,10 +4922,33 @@ var applyPatchFileTool = defineTool({
|
|
|
4200
4922
|
message: "Patch validation tests passed."
|
|
4201
4923
|
});
|
|
4202
4924
|
}
|
|
4925
|
+
let reproductionTestPath;
|
|
4926
|
+
if (reproductionTest) {
|
|
4927
|
+
const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
|
|
4928
|
+
reproductionTestPath = join18(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
|
|
4929
|
+
await writeFile3(reproductionTestPath, reproductionTest, "utf8");
|
|
4930
|
+
try {
|
|
4931
|
+
const reproRun = await execa9("node", [reproductionTestPath], { cwd, stdio: "pipe" });
|
|
4932
|
+
validationPhases.push({
|
|
4933
|
+
phase: "reproduction-test",
|
|
4934
|
+
passed: reproRun.exitCode === 0,
|
|
4935
|
+
message: "Reproduction test verified fix successfully."
|
|
4936
|
+
});
|
|
4937
|
+
} catch (err) {
|
|
4938
|
+
const reproErr = err instanceof Error ? err.message : String(err);
|
|
4939
|
+
validationPhases.push({
|
|
4940
|
+
phase: "reproduction-test",
|
|
4941
|
+
passed: false,
|
|
4942
|
+
error: `Reproduction test failed: ${reproErr}`
|
|
4943
|
+
});
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4203
4946
|
const finalArtifact = {
|
|
4204
4947
|
...artifact,
|
|
4205
4948
|
applied: true,
|
|
4206
4949
|
dryRun: false,
|
|
4950
|
+
reproductionTest,
|
|
4951
|
+
reproductionTestPath,
|
|
4207
4952
|
validationPhases
|
|
4208
4953
|
};
|
|
4209
4954
|
await writePatchManifest(manifestFilePath, finalArtifact);
|
|
@@ -4513,7 +5258,11 @@ function resolveLocalRunOptions(options) {
|
|
|
4513
5258
|
const runTests = options.runTests ?? false;
|
|
4514
5259
|
const policy = options.policy ?? "";
|
|
4515
5260
|
const patchesDir = options.patchesDir || "./patches";
|
|
4516
|
-
const constraints =
|
|
5261
|
+
const constraints = {
|
|
5262
|
+
...options.constraints,
|
|
5263
|
+
typecheck: options.typecheck ?? options.constraints?.typecheck,
|
|
5264
|
+
supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy
|
|
5265
|
+
};
|
|
4517
5266
|
const loadedPolicy = loadPolicy(cwd, options.policy);
|
|
4518
5267
|
const llmProvider = resolveProvider(options);
|
|
4519
5268
|
return {
|
|
@@ -4550,7 +5299,7 @@ function resolveLocalRunOptions(options) {
|
|
|
4550
5299
|
}
|
|
4551
5300
|
|
|
4552
5301
|
// src/remediation/local/vulnerability-match.ts
|
|
4553
|
-
import
|
|
5302
|
+
import semver5 from "semver";
|
|
4554
5303
|
function findVulnerablePackages(cveDetails, installedPackages) {
|
|
4555
5304
|
const vulnerablePackages = [];
|
|
4556
5305
|
for (const affected of cveDetails.affectedPackages) {
|
|
@@ -4559,10 +5308,10 @@ function findVulnerablePackages(cveDetails, installedPackages) {
|
|
|
4559
5308
|
if (affected.ecosystem !== "npm") continue;
|
|
4560
5309
|
const matches = installedPackages.filter((pkg) => pkg.name === affected.name);
|
|
4561
5310
|
for (const installed of matches) {
|
|
4562
|
-
if (!
|
|
5311
|
+
if (!semver5.valid(installed.version)) continue;
|
|
4563
5312
|
let isVulnerable = false;
|
|
4564
5313
|
try {
|
|
4565
|
-
isVulnerable =
|
|
5314
|
+
isVulnerable = semver5.satisfies(installed.version, affected.vulnerableRange, {
|
|
4566
5315
|
includePrerelease: false
|
|
4567
5316
|
});
|
|
4568
5317
|
} catch {
|
|
@@ -4905,7 +5654,7 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
4905
5654
|
};
|
|
4906
5655
|
if (regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
|
|
4907
5656
|
try {
|
|
4908
|
-
if (
|
|
5657
|
+
if (semver6.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
|
|
4909
5658
|
includePrerelease: false
|
|
4910
5659
|
})) {
|
|
4911
5660
|
primaryResult.regressionDetected = true;
|
|
@@ -4996,8 +5745,8 @@ function accumulateStepResults(params) {
|
|
|
4996
5745
|
}
|
|
4997
5746
|
|
|
4998
5747
|
// src/remediation/orchestration-prompt.ts
|
|
4999
|
-
import { existsSync as
|
|
5000
|
-
import { join as
|
|
5748
|
+
import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
|
|
5749
|
+
import { join as join19 } from "path";
|
|
5001
5750
|
function buildProviderAddendum(provider, personality = "balanced") {
|
|
5002
5751
|
const personalityDirective = personality === "analytical" ? "Use concise, explicit rationale for tool decisions and unresolved outcomes." : personality === "pragmatic" ? "Prefer the smallest safe remediation path while preserving policy and validation gates." : "Balance concise execution with brief rationale for risky or unresolved outcomes.";
|
|
5003
5752
|
const providerDirective = provider === "remote" ? "Use strict structured output and deterministic reporting fields." : "Use deterministic-first behavior and only rely on remote model fallback when required by patch generation.";
|
|
@@ -5008,13 +5757,13 @@ Provider profile:
|
|
|
5008
5757
|
- ${personalityDirective}`;
|
|
5009
5758
|
}
|
|
5010
5759
|
function loadOrchestrationPrompt(ctx) {
|
|
5011
|
-
const promptPath =
|
|
5760
|
+
const promptPath = join19(
|
|
5012
5761
|
process.cwd(),
|
|
5013
5762
|
".github",
|
|
5014
5763
|
"instructions",
|
|
5015
5764
|
"orchestration.instructions.md"
|
|
5016
5765
|
);
|
|
5017
|
-
if (!
|
|
5766
|
+
if (!existsSync13(promptPath)) {
|
|
5018
5767
|
return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
|
|
5019
5768
|
Working directory: ${ctx.cwd}
|
|
5020
5769
|
Package manager: ${ctx.packageManager}
|
|
@@ -5040,7 +5789,7 @@ Fallback sequence (when neither version bump nor override can be applied):
|
|
|
5040
5789
|
|
|
5041
5790
|
Always respect dryRun and policy constraints.`;
|
|
5042
5791
|
}
|
|
5043
|
-
const template =
|
|
5792
|
+
const template = readFileSync13(promptPath, "utf8");
|
|
5044
5793
|
return template.replaceAll("{{cveId}}", ctx.cveId).replaceAll("{{cwd}}", ctx.cwd).replaceAll("{{packageManager}}", ctx.packageManager).replaceAll("{{dryRun}}", String(ctx.dryRun)).replaceAll("{{runTests}}", String(ctx.runTests)).replaceAll("{{policy}}", ctx.policy || "undefined").replaceAll("{{patchesDir}}", ctx.patchesDir).replaceAll(
|
|
5045
5794
|
"{{directDependenciesOnly}}",
|
|
5046
5795
|
String(ctx.constraints.directDependenciesOnly ?? false)
|
|
@@ -5061,73 +5810,9 @@ function createProgressEmitter(options) {
|
|
|
5061
5810
|
};
|
|
5062
5811
|
}
|
|
5063
5812
|
|
|
5064
|
-
// src/remediation/tools/lookup-cve.ts
|
|
5065
|
-
import { z as z9 } from "zod";
|
|
5066
|
-
|
|
5067
|
-
// src/intelligence/sources/cisa-kev.ts
|
|
5068
|
-
var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
|
|
5069
|
-
async function fetchCisaKevFeed() {
|
|
5070
|
-
try {
|
|
5071
|
-
const res = await httpClient({ url: CISA_KEV_URL });
|
|
5072
|
-
if (!res.ok) return void 0;
|
|
5073
|
-
return res.data;
|
|
5074
|
-
} catch {
|
|
5075
|
-
return void 0;
|
|
5076
|
-
}
|
|
5077
|
-
}
|
|
5078
|
-
function findKevEntry(feed, cveId) {
|
|
5079
|
-
if (!feed?.vulnerabilities?.length) return void 0;
|
|
5080
|
-
const normalized = cveId.toUpperCase();
|
|
5081
|
-
return feed.vulnerabilities.find((v) => v.cveID.toUpperCase() === normalized);
|
|
5082
|
-
}
|
|
5083
|
-
async function enrichWithCisaKev(details) {
|
|
5084
|
-
const feed = await fetchCisaKevFeed();
|
|
5085
|
-
const entry = findKevEntry(feed, details.id);
|
|
5086
|
-
if (!entry) return details;
|
|
5087
|
-
details.kev = {
|
|
5088
|
-
knownExploited: true,
|
|
5089
|
-
dateAdded: entry.dateAdded,
|
|
5090
|
-
dueDate: entry.dueDate,
|
|
5091
|
-
requiredAction: entry.requiredAction,
|
|
5092
|
-
knownRansomwareCampaignUse: entry.knownRansomwareCampaignUse
|
|
5093
|
-
};
|
|
5094
|
-
if (!details.references.includes(CISA_KEV_URL)) {
|
|
5095
|
-
details.references.push(CISA_KEV_URL);
|
|
5096
|
-
}
|
|
5097
|
-
return details;
|
|
5098
|
-
}
|
|
5099
|
-
|
|
5100
|
-
// src/intelligence/sources/epss.ts
|
|
5101
|
-
async function fetchEpss(cveId) {
|
|
5102
|
-
const { epssApi } = getIntelligenceSourceConfig();
|
|
5103
|
-
if (!epssApi) return void 0;
|
|
5104
|
-
try {
|
|
5105
|
-
const url = new URL(epssApi);
|
|
5106
|
-
url.searchParams.set("cve", cveId);
|
|
5107
|
-
const res = await httpClient({ url: url.toString() });
|
|
5108
|
-
if (!res.ok) return void 0;
|
|
5109
|
-
const body = res.data;
|
|
5110
|
-
return body.data?.[0];
|
|
5111
|
-
} catch {
|
|
5112
|
-
return void 0;
|
|
5113
|
-
}
|
|
5114
|
-
}
|
|
5115
|
-
async function enrichWithEpss(details) {
|
|
5116
|
-
const row = await fetchEpss(details.id);
|
|
5117
|
-
if (!row) return details;
|
|
5118
|
-
const score = Number.parseFloat(row.epss);
|
|
5119
|
-
const percentile = Number.parseFloat(row.percentile);
|
|
5120
|
-
if (!Number.isFinite(score) || !Number.isFinite(percentile)) {
|
|
5121
|
-
return details;
|
|
5122
|
-
}
|
|
5123
|
-
details.epss = {
|
|
5124
|
-
score,
|
|
5125
|
-
percentile,
|
|
5126
|
-
date: row.date
|
|
5127
|
-
};
|
|
5128
|
-
return details;
|
|
5129
|
-
}
|
|
5130
|
-
|
|
5813
|
+
// src/remediation/tools/lookup-cve.ts
|
|
5814
|
+
import { z as z9 } from "zod";
|
|
5815
|
+
|
|
5131
5816
|
// src/intelligence/sources/cve-services.ts
|
|
5132
5817
|
function pickEnglishDescription(container) {
|
|
5133
5818
|
if (!container?.descriptions?.length) return void 0;
|
|
@@ -5411,7 +6096,7 @@ var lookupCveTool = defineTool({
|
|
|
5411
6096
|
|
|
5412
6097
|
// src/remediation/tools/check-version-match.ts
|
|
5413
6098
|
import { z as z10 } from "zod";
|
|
5414
|
-
import
|
|
6099
|
+
import semver7 from "semver";
|
|
5415
6100
|
var affectedPackageSchema = z10.object({
|
|
5416
6101
|
name: z10.string(),
|
|
5417
6102
|
ecosystem: z10.literal("npm"),
|
|
@@ -5440,10 +6125,10 @@ var checkVersionMatchTool = defineTool({
|
|
|
5440
6125
|
(p) => p.name === affected.name
|
|
5441
6126
|
);
|
|
5442
6127
|
for (const installed of matches) {
|
|
5443
|
-
if (!
|
|
6128
|
+
if (!semver7.valid(installed.version)) continue;
|
|
5444
6129
|
let isVulnerable = false;
|
|
5445
6130
|
try {
|
|
5446
|
-
isVulnerable =
|
|
6131
|
+
isVulnerable = semver7.satisfies(installed.version, affected.vulnerableRange, {
|
|
5447
6132
|
includePrerelease: false
|
|
5448
6133
|
});
|
|
5449
6134
|
} catch {
|
|
@@ -5761,7 +6446,7 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
5761
6446
|
}
|
|
5762
6447
|
|
|
5763
6448
|
// src/api/change-request/index.ts
|
|
5764
|
-
import { execa as
|
|
6449
|
+
import { execa as execa10 } from "execa";
|
|
5765
6450
|
import { randomBytes } from "crypto";
|
|
5766
6451
|
function sanitizeBranchToken(value) {
|
|
5767
6452
|
return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
@@ -5790,22 +6475,68 @@ function resolveProvider2(options) {
|
|
|
5790
6475
|
return options.provider ?? "github";
|
|
5791
6476
|
}
|
|
5792
6477
|
function resolveGrouping(options) {
|
|
5793
|
-
|
|
6478
|
+
const allowed = ["all", "per-cve", "per-package", "by-severity"];
|
|
6479
|
+
if (options.grouping && !allowed.includes(options.grouping)) {
|
|
5794
6480
|
throw new Error(
|
|
5795
|
-
|
|
6481
|
+
`Unsupported changeRequest.grouping: "${options.grouping}". Supported values: ${allowed.join(", ")}`
|
|
5796
6482
|
);
|
|
5797
6483
|
}
|
|
5798
|
-
return "all";
|
|
6484
|
+
return options.grouping ?? "all";
|
|
6485
|
+
}
|
|
6486
|
+
function groupReports(reports, grouping) {
|
|
6487
|
+
if (reports.length === 0) return [];
|
|
6488
|
+
if (grouping === "all") {
|
|
6489
|
+
return [reports];
|
|
6490
|
+
}
|
|
6491
|
+
if (grouping === "per-cve") {
|
|
6492
|
+
const map = /* @__PURE__ */ new Map();
|
|
6493
|
+
for (const report of reports) {
|
|
6494
|
+
const key = report.cveId.toUpperCase();
|
|
6495
|
+
const list = map.get(key) ?? [];
|
|
6496
|
+
list.push(report);
|
|
6497
|
+
map.set(key, list);
|
|
6498
|
+
}
|
|
6499
|
+
return Array.from(map.values());
|
|
6500
|
+
}
|
|
6501
|
+
if (grouping === "per-package") {
|
|
6502
|
+
const map = /* @__PURE__ */ new Map();
|
|
6503
|
+
for (const report of reports) {
|
|
6504
|
+
const pkgs = new Set(report.results.map((r) => r.packageName));
|
|
6505
|
+
if (pkgs.size === 0) {
|
|
6506
|
+
const list = map.get("misc") ?? [];
|
|
6507
|
+
list.push(report);
|
|
6508
|
+
map.set("misc", list);
|
|
6509
|
+
} else {
|
|
6510
|
+
for (const pkg of pkgs) {
|
|
6511
|
+
const list = map.get(pkg) ?? [];
|
|
6512
|
+
list.push(report);
|
|
6513
|
+
map.set(pkg, list);
|
|
6514
|
+
}
|
|
6515
|
+
}
|
|
6516
|
+
}
|
|
6517
|
+
return Array.from(map.values());
|
|
6518
|
+
}
|
|
6519
|
+
if (grouping === "by-severity") {
|
|
6520
|
+
const map = /* @__PURE__ */ new Map();
|
|
6521
|
+
for (const report of reports) {
|
|
6522
|
+
const sev = report.cveDetails?.severity ?? "UNKNOWN";
|
|
6523
|
+
const list = map.get(sev) ?? [];
|
|
6524
|
+
list.push(report);
|
|
6525
|
+
map.set(sev, list);
|
|
6526
|
+
}
|
|
6527
|
+
return Array.from(map.values());
|
|
6528
|
+
}
|
|
6529
|
+
return [reports];
|
|
5799
6530
|
}
|
|
5800
6531
|
async function detectCurrentBranch(cwd) {
|
|
5801
|
-
const result = await
|
|
6532
|
+
const result = await execa10("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
5802
6533
|
cwd,
|
|
5803
6534
|
stdio: "pipe"
|
|
5804
6535
|
});
|
|
5805
6536
|
return result.stdout.trim();
|
|
5806
6537
|
}
|
|
5807
6538
|
async function ensureRepoHasChanges(cwd) {
|
|
5808
|
-
const diff = await
|
|
6539
|
+
const diff = await execa10("git", ["status", "--porcelain"], {
|
|
5809
6540
|
cwd,
|
|
5810
6541
|
stdio: "pipe"
|
|
5811
6542
|
});
|
|
@@ -5834,7 +6565,7 @@ async function createGitHubRequest(params) {
|
|
|
5834
6565
|
if (params.draft) {
|
|
5835
6566
|
args.push("--draft");
|
|
5836
6567
|
}
|
|
5837
|
-
const result = await
|
|
6568
|
+
const result = await execa10("gh", args, {
|
|
5838
6569
|
cwd: params.cwd,
|
|
5839
6570
|
stdio: "pipe"
|
|
5840
6571
|
});
|
|
@@ -5859,7 +6590,7 @@ async function createGitLabRequest(params) {
|
|
|
5859
6590
|
if (params.draft) {
|
|
5860
6591
|
args.push("--draft");
|
|
5861
6592
|
}
|
|
5862
|
-
const result = await
|
|
6593
|
+
const result = await execa10("glab", args, {
|
|
5863
6594
|
cwd: params.cwd,
|
|
5864
6595
|
stdio: "pipe"
|
|
5865
6596
|
});
|
|
@@ -5869,64 +6600,68 @@ async function createChangeRequestsForReports(params) {
|
|
|
5869
6600
|
const { cwd, options, reports } = params;
|
|
5870
6601
|
const provider = resolveProvider2(options);
|
|
5871
6602
|
const grouping = resolveGrouping(options);
|
|
5872
|
-
const
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
const
|
|
6603
|
+
const groups = groupReports(reports, grouping);
|
|
6604
|
+
if (groups.length === 0) {
|
|
6605
|
+
return [];
|
|
6606
|
+
}
|
|
6607
|
+
const results = [];
|
|
6608
|
+
const hasChanges = await ensureRepoHasChanges(cwd).catch(() => false);
|
|
6609
|
+
const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd).catch(() => "main");
|
|
6610
|
+
const branchPrefix = options.branchPrefix ?? "autoremediator";
|
|
6611
|
+
const pushRemote = options.pushRemote ?? "origin";
|
|
6612
|
+
if (options.pushRemote && !/^[a-zA-Z0-9._-]+$/.test(options.pushRemote)) {
|
|
6613
|
+
throw new Error(`Invalid pushRemote value: ${options.pushRemote}`);
|
|
6614
|
+
}
|
|
6615
|
+
if (options.repository && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(options.repository)) {
|
|
6616
|
+
throw new Error(`Invalid repository format: ${options.repository}`);
|
|
6617
|
+
}
|
|
6618
|
+
for (const group of groups) {
|
|
6619
|
+
const plan = buildPlan(group);
|
|
6620
|
+
const titlePrefix = options.titlePrefix?.trim().slice(0, 200);
|
|
6621
|
+
const bodyFooter = options.bodyFooter ? options.bodyFooter.slice(0, 2e3) : void 0;
|
|
6622
|
+
const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
|
|
6623
|
+
const body = bodyFooter ? `${plan.body}
|
|
5877
6624
|
|
|
5878
6625
|
${bodyFooter}` : plan.body;
|
|
5879
|
-
try {
|
|
5880
|
-
const hasChanges = await ensureRepoHasChanges(cwd);
|
|
5881
6626
|
if (!hasChanges) {
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
];
|
|
6627
|
+
results.push({
|
|
6628
|
+
provider,
|
|
6629
|
+
grouping,
|
|
6630
|
+
repository: options.repository,
|
|
6631
|
+
branchName: "",
|
|
6632
|
+
title,
|
|
6633
|
+
body,
|
|
6634
|
+
created: false,
|
|
6635
|
+
cveIds: plan.cveIds,
|
|
6636
|
+
packageNames: plan.packageNames,
|
|
6637
|
+
error: "No repository changes to include in change request."
|
|
6638
|
+
});
|
|
6639
|
+
continue;
|
|
5896
6640
|
}
|
|
5897
|
-
const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd);
|
|
5898
|
-
const branchPrefix = options.branchPrefix ?? "autoremediator";
|
|
5899
6641
|
const branchName = toBranchName(branchPrefix, plan.cveIds);
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
target: baseBranch,
|
|
5924
|
-
title,
|
|
5925
|
-
body,
|
|
5926
|
-
draft: options.draft
|
|
5927
|
-
});
|
|
5928
|
-
return [
|
|
5929
|
-
{
|
|
6642
|
+
try {
|
|
6643
|
+
await execa10("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
|
|
6644
|
+
await execa10("git", ["add", "-A"], { cwd, stdio: "pipe" });
|
|
6645
|
+
await execa10("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
|
|
6646
|
+
await execa10("git", ["push", "-u", pushRemote, branchName], { cwd, stdio: "pipe" });
|
|
6647
|
+
const url = provider === "github" ? await createGitHubRequest({
|
|
6648
|
+
cwd,
|
|
6649
|
+
repository: options.repository,
|
|
6650
|
+
head: branchName,
|
|
6651
|
+
base: baseBranch,
|
|
6652
|
+
title,
|
|
6653
|
+
body,
|
|
6654
|
+
draft: options.draft
|
|
6655
|
+
}) : await createGitLabRequest({
|
|
6656
|
+
cwd,
|
|
6657
|
+
repository: options.repository,
|
|
6658
|
+
source: branchName,
|
|
6659
|
+
target: baseBranch,
|
|
6660
|
+
title,
|
|
6661
|
+
body,
|
|
6662
|
+
draft: options.draft
|
|
6663
|
+
});
|
|
6664
|
+
results.push({
|
|
5930
6665
|
provider,
|
|
5931
6666
|
grouping,
|
|
5932
6667
|
repository: options.repository,
|
|
@@ -5938,11 +6673,13 @@ ${bodyFooter}` : plan.body;
|
|
|
5938
6673
|
url,
|
|
5939
6674
|
cveIds: plan.cveIds,
|
|
5940
6675
|
packageNames: plan.packageNames
|
|
6676
|
+
});
|
|
6677
|
+
if (groups.length > 1) {
|
|
6678
|
+
await execa10("git", ["checkout", baseBranch], { cwd, stdio: "pipe" }).catch(() => {
|
|
6679
|
+
});
|
|
5941
6680
|
}
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
return [
|
|
5945
|
-
{
|
|
6681
|
+
} catch (error) {
|
|
6682
|
+
results.push({
|
|
5946
6683
|
provider,
|
|
5947
6684
|
grouping,
|
|
5948
6685
|
repository: options.repository,
|
|
@@ -5954,9 +6691,10 @@ ${bodyFooter}` : plan.body;
|
|
|
5954
6691
|
cveIds: plan.cveIds,
|
|
5955
6692
|
packageNames: plan.packageNames,
|
|
5956
6693
|
error: error instanceof Error ? error.message : String(error)
|
|
5957
|
-
}
|
|
5958
|
-
|
|
6694
|
+
});
|
|
6695
|
+
}
|
|
5959
6696
|
}
|
|
6697
|
+
return results;
|
|
5960
6698
|
}
|
|
5961
6699
|
|
|
5962
6700
|
// src/api/context.ts
|
|
@@ -5990,7 +6728,7 @@ function resolveConstraints(options, cwd) {
|
|
|
5990
6728
|
|
|
5991
6729
|
// src/platform/evidence.ts
|
|
5992
6730
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
5993
|
-
import { join as
|
|
6731
|
+
import { join as join20 } from "path";
|
|
5994
6732
|
function createEvidenceLog(cwd, cveIds, context = {}) {
|
|
5995
6733
|
return {
|
|
5996
6734
|
runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -6022,9 +6760,9 @@ function finalizeEvidence(log) {
|
|
|
6022
6760
|
return log;
|
|
6023
6761
|
}
|
|
6024
6762
|
function writeEvidenceLog(cwd, log) {
|
|
6025
|
-
const dir =
|
|
6763
|
+
const dir = join20(cwd, ".autoremediator", "evidence");
|
|
6026
6764
|
mkdirSync3(dir, { recursive: true });
|
|
6027
|
-
const filePath =
|
|
6765
|
+
const filePath = join20(dir, `${log.runId}.json`);
|
|
6028
6766
|
writeFileSync5(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
|
|
6029
6767
|
return filePath;
|
|
6030
6768
|
}
|
|
@@ -6524,10 +7262,15 @@ function ciExitCode(summary) {
|
|
|
6524
7262
|
}
|
|
6525
7263
|
|
|
6526
7264
|
// src/api/remediate/validators.ts
|
|
7265
|
+
var CVE_ID_REGEX = /^CVE-\d{4}-\d{1,7}$/i;
|
|
7266
|
+
var GHSA_ID_REGEX = /^GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$/i;
|
|
7267
|
+
function isVulnerabilityId(id) {
|
|
7268
|
+
return CVE_ID_REGEX.test(id) || GHSA_ID_REGEX.test(id);
|
|
7269
|
+
}
|
|
6527
7270
|
function assertValidCveId(cveId) {
|
|
6528
|
-
if (
|
|
7271
|
+
if (!isVulnerabilityId(cveId)) {
|
|
6529
7272
|
throw new Error(
|
|
6530
|
-
`Invalid
|
|
7273
|
+
`Invalid vulnerability ID: "${cveId}". Expected format: CVE-YYYY-NNNNN (e.g. CVE-2021-23337) or GHSA-xxxx-xxxx-xxxx.`
|
|
6531
7274
|
);
|
|
6532
7275
|
}
|
|
6533
7276
|
}
|
|
@@ -6623,13 +7366,14 @@ async function planRemediation(cveId, options = {}) {
|
|
|
6623
7366
|
}
|
|
6624
7367
|
|
|
6625
7368
|
// src/scanner/parse-input.ts
|
|
6626
|
-
import { extname, resolve as
|
|
6627
|
-
import { readFileSync as
|
|
6628
|
-
import { execa as
|
|
7369
|
+
import { extname, resolve as resolve4 } from "path";
|
|
7370
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
7371
|
+
import { execa as execa11 } from "execa";
|
|
6629
7372
|
|
|
6630
7373
|
// src/scanner/adapters/npm-audit.ts
|
|
6631
|
-
import { readFileSync as
|
|
7374
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
6632
7375
|
var CVE_REGEX = /CVE-\d{4}-\d{1,7}/gi;
|
|
7376
|
+
var GHSA_REGEX = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
|
|
6633
7377
|
function normalizeSeverity(raw) {
|
|
6634
7378
|
if (!raw) return "UNKNOWN";
|
|
6635
7379
|
const up = raw.toUpperCase();
|
|
@@ -6644,7 +7388,8 @@ function parseNpmAuditJsonFromString(content) {
|
|
|
6644
7388
|
for (const vuln of Object.values(report.vulnerabilities ?? {})) {
|
|
6645
7389
|
for (const viaEntry of vuln.via ?? []) {
|
|
6646
7390
|
const text = typeof viaEntry === "string" ? viaEntry : `${viaEntry.url ?? ""} ${viaEntry.name ?? ""}`;
|
|
6647
|
-
const
|
|
7391
|
+
const cveMatches = text.match(CVE_REGEX) ?? [];
|
|
7392
|
+
const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX) ?? [];
|
|
6648
7393
|
for (const match of matches) {
|
|
6649
7394
|
const cveId = match.toUpperCase();
|
|
6650
7395
|
const key = `${cveId}:${vuln.name}`;
|
|
@@ -6662,13 +7407,14 @@ function parseNpmAuditJsonFromString(content) {
|
|
|
6662
7407
|
return findings;
|
|
6663
7408
|
}
|
|
6664
7409
|
function parseNpmAuditJsonFile(filePath) {
|
|
6665
|
-
const content =
|
|
7410
|
+
const content = readFileSync14(filePath, "utf8");
|
|
6666
7411
|
return parseNpmAuditJsonFromString(content);
|
|
6667
7412
|
}
|
|
6668
7413
|
|
|
6669
7414
|
// src/scanner/adapters/yarn-audit.ts
|
|
6670
|
-
import { readFileSync as
|
|
7415
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
6671
7416
|
var CVE_REGEX2 = /CVE-\d{4}-\d{1,7}/gi;
|
|
7417
|
+
var GHSA_REGEX2 = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
|
|
6672
7418
|
function normalizeSeverity2(raw) {
|
|
6673
7419
|
if (!raw) return "UNKNOWN";
|
|
6674
7420
|
const up = raw.toUpperCase();
|
|
@@ -6693,7 +7439,8 @@ function parseYarnAuditJsonFromString(content) {
|
|
|
6693
7439
|
const packageName = advisory?.module_name;
|
|
6694
7440
|
const severity = normalizeSeverity2(advisory?.severity);
|
|
6695
7441
|
const text = `${advisory?.url ?? ""} ${(advisory?.cves ?? []).join(" ")}`;
|
|
6696
|
-
const
|
|
7442
|
+
const cveMatches = text.match(CVE_REGEX2) ?? [];
|
|
7443
|
+
const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX2) ?? [];
|
|
6697
7444
|
for (const match of matches) {
|
|
6698
7445
|
const cveId = match.toUpperCase();
|
|
6699
7446
|
const key = `${cveId}:${packageName ?? ""}`;
|
|
@@ -6710,12 +7457,12 @@ function parseYarnAuditJsonFromString(content) {
|
|
|
6710
7457
|
return findings;
|
|
6711
7458
|
}
|
|
6712
7459
|
function parseYarnAuditJsonFile(filePath) {
|
|
6713
|
-
const content =
|
|
7460
|
+
const content = readFileSync15(filePath, "utf8");
|
|
6714
7461
|
return parseYarnAuditJsonFromString(content);
|
|
6715
7462
|
}
|
|
6716
7463
|
|
|
6717
7464
|
// src/scanner/adapters/sarif.ts
|
|
6718
|
-
import { readFileSync as
|
|
7465
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
6719
7466
|
var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
|
|
6720
7467
|
function extractPackageName(result) {
|
|
6721
7468
|
const pkg = result.properties?.["packageName"];
|
|
@@ -6753,7 +7500,7 @@ function parseSarifFromString(content) {
|
|
|
6753
7500
|
return findings;
|
|
6754
7501
|
}
|
|
6755
7502
|
function parseSarifFile(filePath) {
|
|
6756
|
-
const content =
|
|
7503
|
+
const content = readFileSync16(filePath, "utf8");
|
|
6757
7504
|
return parseSarifFromString(content);
|
|
6758
7505
|
}
|
|
6759
7506
|
|
|
@@ -6762,7 +7509,7 @@ function parseScanInput(filePath, format) {
|
|
|
6762
7509
|
if (filePath.includes("\0")) {
|
|
6763
7510
|
throw new Error("Invalid scan input path: path contains null bytes");
|
|
6764
7511
|
}
|
|
6765
|
-
const resolvedPath =
|
|
7512
|
+
const resolvedPath = resolve4(filePath);
|
|
6766
7513
|
const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
|
|
6767
7514
|
if (resolved === "npm-audit") {
|
|
6768
7515
|
return parseNpmAuditJsonFile(resolvedPath);
|
|
@@ -6784,7 +7531,7 @@ async function parseScanInputFromAudit(params) {
|
|
|
6784
7531
|
}
|
|
6785
7532
|
const command = resolveAuditCommand(pm, { workspace: params.workspace });
|
|
6786
7533
|
const [cmd, ...args] = command;
|
|
6787
|
-
const result = await
|
|
7534
|
+
const result = await execa11(cmd, args, {
|
|
6788
7535
|
cwd: params.cwd,
|
|
6789
7536
|
stdio: "pipe",
|
|
6790
7537
|
reject: false
|
|
@@ -6838,7 +7585,7 @@ function inferFormat(filePath) {
|
|
|
6838
7585
|
const ext = extname(filePath).toLowerCase();
|
|
6839
7586
|
if (ext === ".sarif") return "sarif";
|
|
6840
7587
|
try {
|
|
6841
|
-
const content =
|
|
7588
|
+
const content = readFileSync17(filePath, "utf8");
|
|
6842
7589
|
const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
|
|
6843
7590
|
if (firstLine) {
|
|
6844
7591
|
const parsed = JSON.parse(firstLine);
|
|
@@ -7249,14 +7996,14 @@ async function remediatePortfolio(targets, options = {}) {
|
|
|
7249
7996
|
}
|
|
7250
7997
|
|
|
7251
7998
|
// src/api/update-outdated/index.ts
|
|
7252
|
-
import { join as
|
|
7253
|
-
import { readFileSync as
|
|
7254
|
-
import { execa as
|
|
7999
|
+
import { join as join21 } from "path";
|
|
8000
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync6 } from "fs";
|
|
8001
|
+
import { execa as execa12 } from "execa";
|
|
7255
8002
|
async function applyBump(params) {
|
|
7256
|
-
const pkgPath =
|
|
8003
|
+
const pkgPath = join21(params.cwd, "package.json");
|
|
7257
8004
|
let pkgJson;
|
|
7258
8005
|
try {
|
|
7259
|
-
pkgJson = JSON.parse(
|
|
8006
|
+
pkgJson = JSON.parse(readFileSync18(pkgPath, "utf8"));
|
|
7260
8007
|
} catch {
|
|
7261
8008
|
return {
|
|
7262
8009
|
applied: false,
|
|
@@ -7281,7 +8028,7 @@ async function applyBump(params) {
|
|
|
7281
8028
|
writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
7282
8029
|
try {
|
|
7283
8030
|
const [installCmd, ...installArgs] = params.installCommand;
|
|
7284
|
-
await
|
|
8031
|
+
await execa12(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
|
|
7285
8032
|
} catch (err) {
|
|
7286
8033
|
pkgJson[depField][params.packageName] = currentRange;
|
|
7287
8034
|
writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
@@ -7294,13 +8041,13 @@ async function applyBump(params) {
|
|
|
7294
8041
|
if (params.runTests) {
|
|
7295
8042
|
try {
|
|
7296
8043
|
const [testCmd, ...testArgs] = params.testCommand;
|
|
7297
|
-
await
|
|
8044
|
+
await execa12(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
|
|
7298
8045
|
} catch (err) {
|
|
7299
8046
|
pkgJson[depField][params.packageName] = currentRange;
|
|
7300
8047
|
writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
7301
8048
|
try {
|
|
7302
8049
|
const [rollbackCmd, ...rollbackArgs] = params.installCommand;
|
|
7303
|
-
await
|
|
8050
|
+
await execa12(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
|
|
7304
8051
|
} catch {
|
|
7305
8052
|
}
|
|
7306
8053
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -7501,131 +8248,169 @@ async function checkReachability(options) {
|
|
|
7501
8248
|
return assessPackageReachability(cwd, options.packageName, options.symbol);
|
|
7502
8249
|
}
|
|
7503
8250
|
|
|
7504
|
-
// src/
|
|
7505
|
-
import
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
}
|
|
7525
|
-
|
|
7526
|
-
if (enrich) {
|
|
8251
|
+
// src/scanner/delta-scan.ts
|
|
8252
|
+
import { existsSync as existsSync14, readFileSync as readFileSync19 } from "fs";
|
|
8253
|
+
import { join as join22 } from "path";
|
|
8254
|
+
import { execa as execa13 } from "execa";
|
|
8255
|
+
import semver9 from "semver";
|
|
8256
|
+
|
|
8257
|
+
// src/scanner/lockfile-diff.ts
|
|
8258
|
+
import YAML2 from "yaml";
|
|
8259
|
+
import semver8 from "semver";
|
|
8260
|
+
function parseLockfileDependencies(filename, content) {
|
|
8261
|
+
const result = /* @__PURE__ */ new Map();
|
|
8262
|
+
const add = (name, ver) => {
|
|
8263
|
+
if (!name || !ver) return;
|
|
8264
|
+
const cleanVer = semver8.clean(ver) || semver8.coerce(ver)?.version || ver;
|
|
8265
|
+
let set = result.get(name);
|
|
8266
|
+
if (!set) {
|
|
8267
|
+
set = /* @__PURE__ */ new Set();
|
|
8268
|
+
result.set(name, set);
|
|
8269
|
+
}
|
|
8270
|
+
set.add(cleanVer);
|
|
8271
|
+
};
|
|
8272
|
+
if (filename.endsWith("package-lock.json")) {
|
|
7527
8273
|
try {
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
if (version && affected?.vulnerableRange) {
|
|
7540
|
-
const cleanVer = semver7.clean(version) || version;
|
|
7541
|
-
if (semver7.valid(cleanVer) && !semver7.satisfies(cleanVer, affected.vulnerableRange)) {
|
|
7542
|
-
continue;
|
|
8274
|
+
const data = JSON.parse(content);
|
|
8275
|
+
if (data.packages && typeof data.packages === "object") {
|
|
8276
|
+
for (const [key, pkg] of Object.entries(data.packages)) {
|
|
8277
|
+
if (!key || key === "" || typeof pkg !== "object" || !pkg) continue;
|
|
8278
|
+
const idx = key.lastIndexOf("node_modules/");
|
|
8279
|
+
if (idx !== -1) {
|
|
8280
|
+
const rawName = key.slice(idx + "node_modules/".length);
|
|
8281
|
+
const ver = pkg.version;
|
|
8282
|
+
add(rawName, ver);
|
|
8283
|
+
}
|
|
8284
|
+
}
|
|
7543
8285
|
}
|
|
8286
|
+
if (data.dependencies && typeof data.dependencies === "object") {
|
|
8287
|
+
const walkDeps = (deps) => {
|
|
8288
|
+
for (const [name, info] of Object.entries(deps)) {
|
|
8289
|
+
if (!info || typeof info !== "object") continue;
|
|
8290
|
+
const depObj = info;
|
|
8291
|
+
add(name, depObj.version);
|
|
8292
|
+
if (depObj.dependencies && typeof depObj.dependencies === "object") {
|
|
8293
|
+
walkDeps(depObj.dependencies);
|
|
8294
|
+
}
|
|
8295
|
+
}
|
|
8296
|
+
};
|
|
8297
|
+
walkDeps(data.dependencies);
|
|
8298
|
+
}
|
|
8299
|
+
} catch {
|
|
7544
8300
|
}
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
8301
|
+
} else if (filename.endsWith("pnpm-lock.yaml")) {
|
|
8302
|
+
try {
|
|
8303
|
+
const data = YAML2.parse(content);
|
|
8304
|
+
if (data?.packages && typeof data.packages === "object") {
|
|
8305
|
+
for (const [key, val] of Object.entries(data.packages)) {
|
|
8306
|
+
let rawKey = key.replace(/^\//, "");
|
|
8307
|
+
const parenIdx = rawKey.indexOf("(");
|
|
8308
|
+
if (parenIdx !== -1) {
|
|
8309
|
+
rawKey = rawKey.slice(0, parenIdx);
|
|
8310
|
+
}
|
|
8311
|
+
const lastAt = rawKey.lastIndexOf("@");
|
|
8312
|
+
if (lastAt > 0) {
|
|
8313
|
+
const name = val?.name ?? rawKey.slice(0, lastAt);
|
|
8314
|
+
const version = val?.version ?? rawKey.slice(lastAt + 1);
|
|
8315
|
+
add(name, version);
|
|
8316
|
+
}
|
|
8317
|
+
}
|
|
7551
8318
|
}
|
|
7552
|
-
|
|
7553
|
-
const
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
8319
|
+
if (data?.snapshots && typeof data.snapshots === "object") {
|
|
8320
|
+
for (const key of Object.keys(data.snapshots)) {
|
|
8321
|
+
let rawKey = key.replace(/^\//, "");
|
|
8322
|
+
const parenIdx = rawKey.indexOf("(");
|
|
8323
|
+
if (parenIdx !== -1) {
|
|
8324
|
+
rawKey = rawKey.slice(0, parenIdx);
|
|
8325
|
+
}
|
|
8326
|
+
const lastAt = rawKey.lastIndexOf("@");
|
|
8327
|
+
if (lastAt > 0) {
|
|
8328
|
+
const name = rawKey.slice(0, lastAt);
|
|
8329
|
+
const version = rawKey.slice(lastAt + 1);
|
|
8330
|
+
add(name, version);
|
|
8331
|
+
}
|
|
7557
8332
|
}
|
|
7558
|
-
} catch {
|
|
7559
8333
|
}
|
|
8334
|
+
} catch {
|
|
7560
8335
|
}
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
);
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
8336
|
+
} else if (filename.endsWith("yarn.lock")) {
|
|
8337
|
+
const lines = content.split("\n");
|
|
8338
|
+
let currentPackages = [];
|
|
8339
|
+
for (const line of lines) {
|
|
8340
|
+
if (line.startsWith("#") || line.trim() === "") continue;
|
|
8341
|
+
if (!line.startsWith(" ")) {
|
|
8342
|
+
currentPackages = [];
|
|
8343
|
+
const parts = line.split(",").map((p) => p.trim().replace(/^"/, "").replace(/":?$/, ""));
|
|
8344
|
+
for (const part of parts) {
|
|
8345
|
+
const lastAt = part.lastIndexOf("@");
|
|
8346
|
+
if (lastAt > 0) {
|
|
8347
|
+
currentPackages.push(part.slice(0, lastAt));
|
|
8348
|
+
}
|
|
8349
|
+
}
|
|
8350
|
+
} else {
|
|
8351
|
+
const match = line.match(/^\s+version\s+"?([^"\s]+)"?/);
|
|
8352
|
+
if (match && currentPackages.length > 0) {
|
|
8353
|
+
const ver = match[1];
|
|
8354
|
+
for (const pkg of currentPackages) {
|
|
8355
|
+
add(pkg, ver);
|
|
8356
|
+
}
|
|
8357
|
+
currentPackages = [];
|
|
8358
|
+
}
|
|
7572
8359
|
}
|
|
7573
8360
|
}
|
|
7574
|
-
entries.push({
|
|
7575
|
-
cveId: cveDetails.id,
|
|
7576
|
-
severity: cveDetails.severity,
|
|
7577
|
-
summary: cveDetails.summary,
|
|
7578
|
-
vulnerableRange: affected?.vulnerableRange,
|
|
7579
|
-
firstPatchedVersion: affected?.firstPatchedVersion,
|
|
7580
|
-
safeUpgradeVersion,
|
|
7581
|
-
epssScore,
|
|
7582
|
-
epssPercentile,
|
|
7583
|
-
inCisaKev,
|
|
7584
|
-
references: cveDetails.references
|
|
7585
|
-
});
|
|
7586
8361
|
}
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
8362
|
+
return result;
|
|
8363
|
+
}
|
|
8364
|
+
function computeLockfileDiff(filename, oldContent, newContent) {
|
|
8365
|
+
const changes = [];
|
|
8366
|
+
const oldDeps = oldContent ? parseLockfileDependencies(filename, oldContent) : /* @__PURE__ */ new Map();
|
|
8367
|
+
const newDeps = newContent ? parseLockfileDependencies(filename, newContent) : /* @__PURE__ */ new Map();
|
|
8368
|
+
const allPkgs = /* @__PURE__ */ new Set([...oldDeps.keys(), ...newDeps.keys()]);
|
|
8369
|
+
for (const pkg of allPkgs) {
|
|
8370
|
+
const oldVers = oldDeps.get(pkg) ?? /* @__PURE__ */ new Set();
|
|
8371
|
+
const newVers = newDeps.get(pkg) ?? /* @__PURE__ */ new Set();
|
|
8372
|
+
for (const nVer of newVers) {
|
|
8373
|
+
if (!oldVers.has(nVer)) {
|
|
8374
|
+
if (oldVers.size === 0) {
|
|
8375
|
+
changes.push({
|
|
8376
|
+
packageName: pkg,
|
|
8377
|
+
changeType: "added",
|
|
8378
|
+
newVersion: nVer,
|
|
8379
|
+
dependencyType: "transitive",
|
|
8380
|
+
manifestPath: filename
|
|
8381
|
+
});
|
|
8382
|
+
} else {
|
|
8383
|
+
const [firstOld] = oldVers;
|
|
8384
|
+
const cleanOld = semver8.clean(firstOld) || semver8.coerce(firstOld)?.version;
|
|
8385
|
+
const cleanNew = semver8.clean(nVer) || semver8.coerce(nVer)?.version;
|
|
8386
|
+
const isUpgrade = cleanOld && cleanNew && semver8.valid(cleanOld) && semver8.valid(cleanNew) ? semver8.gt(cleanNew, cleanOld) : true;
|
|
8387
|
+
changes.push({
|
|
8388
|
+
packageName: pkg,
|
|
8389
|
+
changeType: isUpgrade ? "upgraded" : "downgraded",
|
|
8390
|
+
oldVersion: firstOld,
|
|
8391
|
+
newVersion: nVer,
|
|
8392
|
+
dependencyType: "transitive",
|
|
8393
|
+
manifestPath: filename
|
|
8394
|
+
});
|
|
8395
|
+
}
|
|
8396
|
+
}
|
|
8397
|
+
}
|
|
8398
|
+
if (newVers.size === 0 && oldVers.size > 0) {
|
|
8399
|
+
for (const oVer of oldVers) {
|
|
8400
|
+
changes.push({
|
|
8401
|
+
packageName: pkg,
|
|
8402
|
+
changeType: "removed",
|
|
8403
|
+
oldVersion: oVer,
|
|
8404
|
+
dependencyType: "transitive",
|
|
8405
|
+
manifestPath: filename
|
|
8406
|
+
});
|
|
8407
|
+
}
|
|
7600
8408
|
}
|
|
7601
8409
|
}
|
|
7602
|
-
|
|
7603
|
-
if (isVulnerable) {
|
|
7604
|
-
const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver7.valid(v))).sort((a, b) => semver7.compare(a, b));
|
|
7605
|
-
recommendedVersion = upgradeCandidates[upgradeCandidates.length - 1] || latestVersion;
|
|
7606
|
-
} else {
|
|
7607
|
-
recommendedVersion = version || latestVersion;
|
|
7608
|
-
}
|
|
7609
|
-
const summary = isVulnerable ? `Package "${packageName}${version ? `@${version}` : ""}" has ${entries.length} known vulnerability(ies) (verdict: ${verdict}). Recommended safe version: ${recommendedVersion || "none"}.` : `Package "${packageName}${version ? `@${version}` : ""}" has no known vulnerabilities. Safe to install.`;
|
|
7610
|
-
return {
|
|
7611
|
-
schemaVersion: "1.0",
|
|
7612
|
-
packageName,
|
|
7613
|
-
evaluatedVersion: version,
|
|
7614
|
-
isVulnerable,
|
|
7615
|
-
verdict,
|
|
7616
|
-
summary,
|
|
7617
|
-
vulnerabilities: entries,
|
|
7618
|
-
recommendedVersion,
|
|
7619
|
-
latestVersion,
|
|
7620
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7621
|
-
};
|
|
8410
|
+
return changes;
|
|
7622
8411
|
}
|
|
7623
8412
|
|
|
7624
8413
|
// src/scanner/delta-scan.ts
|
|
7625
|
-
import { existsSync as existsSync12, readFileSync as readFileSync17 } from "fs";
|
|
7626
|
-
import { join as join20 } from "path";
|
|
7627
|
-
import { execa as execa12 } from "execa";
|
|
7628
|
-
import semver8 from "semver";
|
|
7629
8414
|
var DEP_SECTIONS = [
|
|
7630
8415
|
"dependencies",
|
|
7631
8416
|
"devDependencies",
|
|
@@ -7635,7 +8420,7 @@ var DEP_SECTIONS = [
|
|
|
7635
8420
|
async function getChangedManifestPaths(cwd, baseRef) {
|
|
7636
8421
|
const manifests = /* @__PURE__ */ new Set();
|
|
7637
8422
|
try {
|
|
7638
|
-
const { stdout: diffFiles } = await
|
|
8423
|
+
const { stdout: diffFiles } = await execa13(
|
|
7639
8424
|
"git",
|
|
7640
8425
|
["diff", "--name-only", baseRef, "--", "package.json", "**/package.json"],
|
|
7641
8426
|
{ cwd, reject: false }
|
|
@@ -7646,7 +8431,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
|
|
|
7646
8431
|
manifests.add(trimmed);
|
|
7647
8432
|
}
|
|
7648
8433
|
}
|
|
7649
|
-
const { stdout: untrackedFiles } = await
|
|
8434
|
+
const { stdout: untrackedFiles } = await execa13(
|
|
7650
8435
|
"git",
|
|
7651
8436
|
["ls-files", "--others", "--exclude-standard", "--", "package.json", "**/package.json"],
|
|
7652
8437
|
{ cwd, reject: false }
|
|
@@ -7658,7 +8443,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
|
|
|
7658
8443
|
}
|
|
7659
8444
|
}
|
|
7660
8445
|
} catch {
|
|
7661
|
-
if (
|
|
8446
|
+
if (existsSync14(join22(cwd, "package.json"))) {
|
|
7662
8447
|
manifests.add("package.json");
|
|
7663
8448
|
}
|
|
7664
8449
|
}
|
|
@@ -7666,7 +8451,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
|
|
|
7666
8451
|
}
|
|
7667
8452
|
async function getOldManifestContent(cwd, baseRef, relPath) {
|
|
7668
8453
|
try {
|
|
7669
|
-
const { stdout } = await
|
|
8454
|
+
const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
|
|
7670
8455
|
cwd,
|
|
7671
8456
|
reject: false
|
|
7672
8457
|
});
|
|
@@ -7677,10 +8462,10 @@ async function getOldManifestContent(cwd, baseRef, relPath) {
|
|
|
7677
8462
|
}
|
|
7678
8463
|
}
|
|
7679
8464
|
function getNewManifestContent(cwd, relPath) {
|
|
7680
|
-
const fullPath =
|
|
7681
|
-
if (!
|
|
8465
|
+
const fullPath = join22(cwd, relPath);
|
|
8466
|
+
if (!existsSync14(fullPath)) return null;
|
|
7682
8467
|
try {
|
|
7683
|
-
return JSON.parse(
|
|
8468
|
+
return JSON.parse(readFileSync19(fullPath, "utf8"));
|
|
7684
8469
|
} catch {
|
|
7685
8470
|
return null;
|
|
7686
8471
|
}
|
|
@@ -7701,9 +8486,9 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
|
|
|
7701
8486
|
manifestPath: relPath
|
|
7702
8487
|
});
|
|
7703
8488
|
} else if (oldVer !== newVer) {
|
|
7704
|
-
const cleanOld =
|
|
7705
|
-
const cleanNew =
|
|
7706
|
-
const isUpgrade = cleanOld && cleanNew &&
|
|
8489
|
+
const cleanOld = semver9.clean(oldVer) || semver9.coerce(oldVer)?.version;
|
|
8490
|
+
const cleanNew = semver9.clean(newVer) || semver9.coerce(newVer)?.version;
|
|
8491
|
+
const isUpgrade = cleanOld && cleanNew && semver9.valid(cleanOld) && semver9.valid(cleanNew) ? semver9.gt(cleanNew, cleanOld) : true;
|
|
7707
8492
|
changes.push({
|
|
7708
8493
|
packageName: pkgName,
|
|
7709
8494
|
changeType: isUpgrade ? "upgraded" : "downgraded",
|
|
@@ -7728,22 +8513,89 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
|
|
|
7728
8513
|
}
|
|
7729
8514
|
return changes;
|
|
7730
8515
|
}
|
|
8516
|
+
var LOCKFILE_PATTERNS = [
|
|
8517
|
+
"package-lock.json",
|
|
8518
|
+
"**/package-lock.json",
|
|
8519
|
+
"pnpm-lock.yaml",
|
|
8520
|
+
"**/pnpm-lock.yaml",
|
|
8521
|
+
"yarn.lock",
|
|
8522
|
+
"**/yarn.lock"
|
|
8523
|
+
];
|
|
8524
|
+
async function getChangedLockfilePaths(cwd, baseRef) {
|
|
8525
|
+
const lockfiles = /* @__PURE__ */ new Set();
|
|
8526
|
+
try {
|
|
8527
|
+
const { stdout: diffFiles } = await execa13(
|
|
8528
|
+
"git",
|
|
8529
|
+
["diff", "--name-only", baseRef, "--", ...LOCKFILE_PATTERNS],
|
|
8530
|
+
{ cwd, reject: false }
|
|
8531
|
+
);
|
|
8532
|
+
for (const f of diffFiles.split("\n")) {
|
|
8533
|
+
const trimmed = f.trim();
|
|
8534
|
+
if (trimmed && !trimmed.includes("node_modules") && (trimmed.endsWith("package-lock.json") || trimmed.endsWith("pnpm-lock.yaml") || trimmed.endsWith("yarn.lock"))) {
|
|
8535
|
+
lockfiles.add(trimmed);
|
|
8536
|
+
}
|
|
8537
|
+
}
|
|
8538
|
+
const { stdout: untrackedFiles } = await execa13(
|
|
8539
|
+
"git",
|
|
8540
|
+
["ls-files", "--others", "--exclude-standard", "--", ...LOCKFILE_PATTERNS],
|
|
8541
|
+
{ cwd, reject: false }
|
|
8542
|
+
);
|
|
8543
|
+
for (const f of untrackedFiles.split("\n")) {
|
|
8544
|
+
const trimmed = f.trim();
|
|
8545
|
+
if (trimmed && !trimmed.includes("node_modules") && (trimmed.endsWith("package-lock.json") || trimmed.endsWith("pnpm-lock.yaml") || trimmed.endsWith("yarn.lock"))) {
|
|
8546
|
+
lockfiles.add(trimmed);
|
|
8547
|
+
}
|
|
8548
|
+
}
|
|
8549
|
+
} catch {
|
|
8550
|
+
}
|
|
8551
|
+
return Array.from(lockfiles);
|
|
8552
|
+
}
|
|
8553
|
+
async function getFileContentAtRef(cwd, baseRef, relPath) {
|
|
8554
|
+
try {
|
|
8555
|
+
const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
|
|
8556
|
+
cwd,
|
|
8557
|
+
reject: false
|
|
8558
|
+
});
|
|
8559
|
+
return stdout || null;
|
|
8560
|
+
} catch {
|
|
8561
|
+
return null;
|
|
8562
|
+
}
|
|
8563
|
+
}
|
|
7731
8564
|
async function scanDelta(options = {}) {
|
|
7732
8565
|
const cwd = options.cwd ?? process.cwd();
|
|
7733
8566
|
const baseRef = options.baseRef ?? "HEAD";
|
|
7734
8567
|
const changedManifests = await getChangedManifestPaths(cwd, baseRef);
|
|
8568
|
+
const changedLockfiles = await getChangedLockfilePaths(cwd, baseRef);
|
|
7735
8569
|
const dependencyChanges = [];
|
|
8570
|
+
const directPkgs = /* @__PURE__ */ new Set();
|
|
7736
8571
|
for (const manifestRel of changedManifests) {
|
|
7737
8572
|
const oldPkg = await getOldManifestContent(cwd, baseRef, manifestRel);
|
|
7738
8573
|
const newPkg = getNewManifestContent(cwd, manifestRel);
|
|
7739
8574
|
const diffs = computeDependencyDiff(manifestRel, oldPkg, newPkg);
|
|
8575
|
+
for (const d of diffs) {
|
|
8576
|
+
directPkgs.add(d.packageName);
|
|
8577
|
+
}
|
|
7740
8578
|
dependencyChanges.push(...diffs);
|
|
7741
8579
|
}
|
|
8580
|
+
for (const lockfileRel of changedLockfiles) {
|
|
8581
|
+
const oldContent = await getFileContentAtRef(cwd, baseRef, lockfileRel);
|
|
8582
|
+
const fullPath = join22(cwd, lockfileRel);
|
|
8583
|
+
const newContent = existsSync14(fullPath) ? readFileSync19(fullPath, "utf8") : null;
|
|
8584
|
+
const lockDiffs = computeLockfileDiff(lockfileRel, oldContent, newContent);
|
|
8585
|
+
for (const change of lockDiffs) {
|
|
8586
|
+
if (!directPkgs.has(change.packageName)) {
|
|
8587
|
+
dependencyChanges.push(change);
|
|
8588
|
+
}
|
|
8589
|
+
}
|
|
8590
|
+
if (!changedManifests.includes(lockfileRel)) {
|
|
8591
|
+
changedManifests.push(lockfileRel);
|
|
8592
|
+
}
|
|
8593
|
+
}
|
|
7742
8594
|
const introducedFindings = [];
|
|
7743
8595
|
const resolvedFindings = [];
|
|
7744
8596
|
for (const change of dependencyChanges) {
|
|
7745
8597
|
if (change.changeType === "added" || change.changeType === "upgraded" || change.changeType === "downgraded") {
|
|
7746
|
-
const cleanVer =
|
|
8598
|
+
const cleanVer = semver9.coerce(change.newVersion)?.version ?? change.newVersion;
|
|
7747
8599
|
const evaluation = await evaluatePackage(change.packageName, {
|
|
7748
8600
|
version: cleanVer,
|
|
7749
8601
|
enrichIntelligence: true
|
|
@@ -7763,12 +8615,12 @@ async function scanDelta(options = {}) {
|
|
|
7763
8615
|
}
|
|
7764
8616
|
}
|
|
7765
8617
|
if (change.oldVersion && (change.changeType === "upgraded" || change.changeType === "removed")) {
|
|
7766
|
-
const cleanOldVer =
|
|
8618
|
+
const cleanOldVer = semver9.coerce(change.oldVersion)?.version ?? change.oldVersion;
|
|
7767
8619
|
const oldEval = await evaluatePackage(change.packageName, {
|
|
7768
8620
|
version: cleanOldVer,
|
|
7769
8621
|
enrichIntelligence: true
|
|
7770
8622
|
});
|
|
7771
|
-
const cleanNewVer = change.newVersion ?
|
|
8623
|
+
const cleanNewVer = change.newVersion ? semver9.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
|
|
7772
8624
|
const newEval = cleanNewVer ? await evaluatePackage(change.packageName, {
|
|
7773
8625
|
version: cleanNewVer,
|
|
7774
8626
|
enrichIntelligence: false
|
|
@@ -7931,9 +8783,9 @@ function toSarifOutput(report) {
|
|
|
7931
8783
|
}
|
|
7932
8784
|
|
|
7933
8785
|
// src/version.ts
|
|
7934
|
-
import { readFileSync as
|
|
8786
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
7935
8787
|
function readPackageVersion() {
|
|
7936
|
-
const raw =
|
|
8788
|
+
const raw = readFileSync20(new URL("../package.json", import.meta.url), "utf8");
|
|
7937
8789
|
const metadata = JSON.parse(raw);
|
|
7938
8790
|
if (!metadata.version) {
|
|
7939
8791
|
throw new Error("packages/core/package.json is missing a version field.");
|
|
@@ -7958,13 +8810,17 @@ export {
|
|
|
7958
8810
|
listPatchArtifacts,
|
|
7959
8811
|
inspectPatchArtifact,
|
|
7960
8812
|
validatePatchArtifact,
|
|
8813
|
+
evaluatePackage,
|
|
8814
|
+
reconcilePatchArtifacts,
|
|
7961
8815
|
remediate,
|
|
7962
8816
|
planRemediation,
|
|
8817
|
+
parseScanInput,
|
|
8818
|
+
parseScanInputFromAudit,
|
|
8819
|
+
uniqueCveIds,
|
|
7963
8820
|
remediateFromScan,
|
|
7964
8821
|
remediatePortfolio,
|
|
7965
8822
|
updateOutdated,
|
|
7966
8823
|
checkReachability,
|
|
7967
|
-
evaluatePackage,
|
|
7968
8824
|
scanDelta,
|
|
7969
8825
|
submitRemediateJob,
|
|
7970
8826
|
submitScanJob,
|