autoremediator 0.10.0 → 0.11.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/dist/{chunk-F7W4EYJL.js → chunk-GYCZ6L3O.js} +568 -183
- package/dist/chunk-GYCZ6L3O.js.map +1 -0
- package/dist/cli.js +24 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +73 -1
- package/dist/index.js +565 -180
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/openapi/server.js +1 -1
- package/llms.txt +21 -0
- package/package.json +2 -2
- package/dist/chunk-F7W4EYJL.js.map +0 -1
|
@@ -41,7 +41,13 @@ var OPTION_DESCRIPTIONS = {
|
|
|
41
41
|
enforceFrozenLockfile: "Override frozen lockfile behavior for install commands",
|
|
42
42
|
workspace: "Workspace/package selector for scoped remediation in monorepos",
|
|
43
43
|
includeTransitive: "Include indirect/transitive dependencies in the outdated check. Default: false.",
|
|
44
|
-
updateOutdated: "Run in update-outdated mode: bump all outdated npm packages without requiring a CVE."
|
|
44
|
+
updateOutdated: "Run in update-outdated mode: bump all outdated npm packages without requiring a CVE.",
|
|
45
|
+
kevMandatory: "If true, CVEs with active CISA KEV status bypass severity filtering and are treated as mandatory",
|
|
46
|
+
epssThreshold: "EPSS probability threshold (0..1) above which a CVE is treated as mandatory regardless of severity",
|
|
47
|
+
suppressionsFile: "Path to a YAML file containing additional VEX suppression entries to merge with policy-inline suppressions",
|
|
48
|
+
slaCheck: "Compare CVE publication dates against configured SLA windows and include breach records in the report output",
|
|
49
|
+
skipUnreachable: "Skip remediation for CVEs where the vulnerable package cannot be reached from any project entry point (requires static import analysis)",
|
|
50
|
+
regressionCheck: "After applying a fix, verify the patched version falls outside the CVE's vulnerable range and flag any regression in the report"
|
|
45
51
|
};
|
|
46
52
|
function createConstraintSchemaProperties() {
|
|
47
53
|
return {
|
|
@@ -97,14 +103,21 @@ function createRemediateOptionSchemaProperties(options) {
|
|
|
97
103
|
constraints: {
|
|
98
104
|
type: "object",
|
|
99
105
|
properties: createConstraintSchemaProperties()
|
|
100
|
-
}
|
|
106
|
+
},
|
|
107
|
+
kevMandatory: { type: "boolean", description: OPTION_DESCRIPTIONS.kevMandatory },
|
|
108
|
+
epssThreshold: { type: "number", minimum: 0, maximum: 1, description: OPTION_DESCRIPTIONS.epssThreshold },
|
|
109
|
+
suppressionsFile: { type: "string", description: OPTION_DESCRIPTIONS.suppressionsFile },
|
|
110
|
+
slaCheck: { type: "boolean", description: OPTION_DESCRIPTIONS.slaCheck },
|
|
111
|
+
skipUnreachable: { type: "boolean", description: OPTION_DESCRIPTIONS.skipUnreachable },
|
|
112
|
+
regressionCheck: { type: "boolean", description: OPTION_DESCRIPTIONS.regressionCheck }
|
|
101
113
|
};
|
|
102
114
|
}
|
|
103
115
|
function createScanOptionSchemaProperties() {
|
|
104
116
|
return {
|
|
105
117
|
...createRemediateOptionSchemaProperties({ includeEvidence: true }),
|
|
106
118
|
format: { type: "string", enum: ["npm-audit", "yarn-audit", "sarif", "auto"], description: OPTION_DESCRIPTIONS.format },
|
|
107
|
-
audit: { type: "boolean", description: OPTION_DESCRIPTIONS.audit }
|
|
119
|
+
audit: { type: "boolean", description: OPTION_DESCRIPTIONS.audit },
|
|
120
|
+
slaCheck: { type: "boolean", description: OPTION_DESCRIPTIONS.slaCheck }
|
|
108
121
|
};
|
|
109
122
|
}
|
|
110
123
|
function createScanReportSchemaProperties() {
|
|
@@ -494,7 +507,11 @@ var DEFAULT_POLICY = {
|
|
|
494
507
|
consensusModel: void 0,
|
|
495
508
|
patchConfidenceThresholds: {},
|
|
496
509
|
dynamicModelRouting: false,
|
|
497
|
-
dynamicRoutingThresholdChars: 18e3
|
|
510
|
+
dynamicRoutingThresholdChars: 18e3,
|
|
511
|
+
exploitSignalOverride: void 0,
|
|
512
|
+
suppressions: [],
|
|
513
|
+
sla: void 0,
|
|
514
|
+
skipUnreachable: false
|
|
498
515
|
};
|
|
499
516
|
function loadPolicy(cwd, explicitPath) {
|
|
500
517
|
const candidate = explicitPath ?? join4(cwd, ".github", "autoremediator.yml");
|
|
@@ -527,7 +544,19 @@ function loadPolicy(cwd, explicitPath) {
|
|
|
527
544
|
high: parsed.patchConfidenceThresholds?.high ?? DEFAULT_POLICY.patchConfidenceThresholds?.high
|
|
528
545
|
},
|
|
529
546
|
dynamicModelRouting: parsed.dynamicModelRouting ?? DEFAULT_POLICY.dynamicModelRouting,
|
|
530
|
-
dynamicRoutingThresholdChars: parsed.dynamicRoutingThresholdChars ?? DEFAULT_POLICY.dynamicRoutingThresholdChars
|
|
547
|
+
dynamicRoutingThresholdChars: parsed.dynamicRoutingThresholdChars ?? DEFAULT_POLICY.dynamicRoutingThresholdChars,
|
|
548
|
+
exploitSignalOverride: parsed.exploitSignalOverride ? {
|
|
549
|
+
kev: parsed.exploitSignalOverride.kev ?? void 0,
|
|
550
|
+
epss: parsed.exploitSignalOverride.epss ?? void 0
|
|
551
|
+
} : void 0,
|
|
552
|
+
suppressions: Array.isArray(parsed.suppressions) ? parsed.suppressions : DEFAULT_POLICY.suppressions,
|
|
553
|
+
sla: parsed.sla ? {
|
|
554
|
+
critical: parsed.sla.critical,
|
|
555
|
+
high: parsed.sla.high,
|
|
556
|
+
medium: parsed.sla.medium,
|
|
557
|
+
low: parsed.sla.low
|
|
558
|
+
} : void 0,
|
|
559
|
+
skipUnreachable: parsed.skipUnreachable ?? DEFAULT_POLICY.skipUnreachable
|
|
531
560
|
};
|
|
532
561
|
} catch {
|
|
533
562
|
return DEFAULT_POLICY;
|
|
@@ -540,6 +569,37 @@ function isPackageAllowed(policy, packageName) {
|
|
|
540
569
|
}
|
|
541
570
|
return true;
|
|
542
571
|
}
|
|
572
|
+
function isActiveSuppression(suppression) {
|
|
573
|
+
if (!suppression.expiresAt) return true;
|
|
574
|
+
return new Date(suppression.expiresAt) > /* @__PURE__ */ new Date();
|
|
575
|
+
}
|
|
576
|
+
function loadSuppressionsFile(filePath) {
|
|
577
|
+
try {
|
|
578
|
+
const content = readFileSync2(filePath, "utf8");
|
|
579
|
+
const parsed = yamlParse(content);
|
|
580
|
+
return Array.isArray(parsed?.suppressions) ? parsed.suppressions : [];
|
|
581
|
+
} catch {
|
|
582
|
+
return [];
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
|
|
586
|
+
const severityKey = severity.toLowerCase();
|
|
587
|
+
const deadlineHours = slaPolicy[severityKey];
|
|
588
|
+
if (typeof deadlineHours !== "number") return null;
|
|
589
|
+
const publishedMs = new Date(publishedAt).getTime();
|
|
590
|
+
if (isNaN(publishedMs)) return null;
|
|
591
|
+
const deadlineMs = publishedMs + deadlineHours * 60 * 60 * 1e3;
|
|
592
|
+
const nowMs = Date.now();
|
|
593
|
+
if (nowMs <= deadlineMs) return null;
|
|
594
|
+
const hoursOverdue = Math.round((nowMs - deadlineMs) / (60 * 60 * 1e3));
|
|
595
|
+
return {
|
|
596
|
+
cveId,
|
|
597
|
+
severity,
|
|
598
|
+
publishedAt,
|
|
599
|
+
deadlineAt: new Date(deadlineMs).toISOString(),
|
|
600
|
+
hoursOverdue
|
|
601
|
+
};
|
|
602
|
+
}
|
|
543
603
|
|
|
544
604
|
// src/remediation/tools/check-inventory.ts
|
|
545
605
|
var checkInventoryTool = tool({
|
|
@@ -864,6 +924,9 @@ function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
|
|
|
864
924
|
// src/remediation/pipeline.ts
|
|
865
925
|
import { generateText as generateText2 } from "ai";
|
|
866
926
|
|
|
927
|
+
// src/remediation/local/run.ts
|
|
928
|
+
import semver5 from "semver";
|
|
929
|
+
|
|
867
930
|
// src/platform/http-client.ts
|
|
868
931
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
869
932
|
async function requestWithTimeout(url, init, timeoutMs) {
|
|
@@ -1124,9 +1187,216 @@ async function enrichWithNvd(details) {
|
|
|
1124
1187
|
return details;
|
|
1125
1188
|
}
|
|
1126
1189
|
|
|
1190
|
+
// src/remediation/tools/check-reachability.ts
|
|
1191
|
+
import { tool as tool2 } from "ai";
|
|
1192
|
+
import { z as z2 } from "zod";
|
|
1193
|
+
import { readdirSync, readFileSync as readFileSync5, statSync } from "fs";
|
|
1194
|
+
import { join as join7, extname } from "path";
|
|
1195
|
+
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
1196
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", ".git", "coverage", ".cache"]);
|
|
1197
|
+
var MAX_FILES = 500;
|
|
1198
|
+
function collectSourceFiles(dir, files = []) {
|
|
1199
|
+
if (files.length >= MAX_FILES) return files;
|
|
1200
|
+
let entries;
|
|
1201
|
+
try {
|
|
1202
|
+
entries = readdirSync(dir);
|
|
1203
|
+
} catch {
|
|
1204
|
+
return files;
|
|
1205
|
+
}
|
|
1206
|
+
for (const entry of entries) {
|
|
1207
|
+
if (files.length >= MAX_FILES) break;
|
|
1208
|
+
const full = join7(dir, entry);
|
|
1209
|
+
let stat2;
|
|
1210
|
+
try {
|
|
1211
|
+
stat2 = statSync(full);
|
|
1212
|
+
} catch {
|
|
1213
|
+
continue;
|
|
1214
|
+
}
|
|
1215
|
+
if (stat2.isDirectory()) {
|
|
1216
|
+
if (!SKIP_DIRS.has(entry)) collectSourceFiles(full, files);
|
|
1217
|
+
} else if (SOURCE_EXTENSIONS.has(extname(entry))) {
|
|
1218
|
+
files.push(full);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
return files;
|
|
1222
|
+
}
|
|
1223
|
+
function assessPackageReachability(cwd, packageName) {
|
|
1224
|
+
const files = collectSourceFiles(cwd);
|
|
1225
|
+
if (files.length === 0) {
|
|
1226
|
+
return {
|
|
1227
|
+
packageName,
|
|
1228
|
+
status: "unknown",
|
|
1229
|
+
reason: "No source files found to scan."
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1233
|
+
const pattern = new RegExp(
|
|
1234
|
+
`(?:import\\s[^'"]*from\\s['"]${escaped}(?:/[^'"]*)?['"]|require\\(['"]${escaped}(?:/[^'"]*)?['"]\\)|from\\s['"]${escaped}(?:/[^'"]*)?['"])`,
|
|
1235
|
+
"m"
|
|
1236
|
+
);
|
|
1237
|
+
const evidence = [];
|
|
1238
|
+
for (const filePath of files) {
|
|
1239
|
+
let content;
|
|
1240
|
+
try {
|
|
1241
|
+
content = readFileSync5(filePath, "utf8");
|
|
1242
|
+
} catch {
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
if (pattern.test(content)) {
|
|
1246
|
+
const relative = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
1247
|
+
const matchType = content.includes(`require('${packageName}`) || content.includes(`require("${packageName}`) ? "require" : content.includes(`import(`) ? "dynamic-import" : "import";
|
|
1248
|
+
evidence.push({ filePath: relative, matchType });
|
|
1249
|
+
if (evidence.length >= 3) break;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (evidence.length > 0) {
|
|
1253
|
+
return {
|
|
1254
|
+
packageName,
|
|
1255
|
+
status: "reachable",
|
|
1256
|
+
reason: `Found ${evidence.length} import reference(s) in source files.`,
|
|
1257
|
+
reachabilityBasis: "import-present",
|
|
1258
|
+
evidence
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
return {
|
|
1262
|
+
packageName,
|
|
1263
|
+
status: "not-reachable",
|
|
1264
|
+
reason: `No import or require of '${packageName}' found in ${files.length} source file(s).`
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
var checkReachabilityTool = tool2({
|
|
1268
|
+
description: "Assess whether an npm package is statically reachable (imported) from the project's source files. Returns reachable, not-reachable, or unknown.",
|
|
1269
|
+
parameters: z2.object({
|
|
1270
|
+
cwd: z2.string().describe("Project root directory"),
|
|
1271
|
+
packageName: z2.string().describe("The npm package name to search for")
|
|
1272
|
+
}),
|
|
1273
|
+
execute: async ({ cwd, packageName }) => {
|
|
1274
|
+
return assessPackageReachability(cwd, packageName);
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
// src/remediation/tools/check-exploit-signal.ts
|
|
1279
|
+
import { tool as tool3 } from "ai";
|
|
1280
|
+
import { z as z3 } from "zod";
|
|
1281
|
+
var kevSchema = z3.object({
|
|
1282
|
+
knownExploited: z3.boolean(),
|
|
1283
|
+
dateAdded: z3.string().optional(),
|
|
1284
|
+
dueDate: z3.string().optional(),
|
|
1285
|
+
requiredAction: z3.string().optional(),
|
|
1286
|
+
knownRansomwareCampaignUse: z3.string().optional()
|
|
1287
|
+
}).passthrough();
|
|
1288
|
+
var epssSchema = z3.object({
|
|
1289
|
+
score: z3.number(),
|
|
1290
|
+
percentile: z3.number(),
|
|
1291
|
+
date: z3.string().optional()
|
|
1292
|
+
}).passthrough();
|
|
1293
|
+
var exploitSignalPolicySchema = z3.object({
|
|
1294
|
+
kev: z3.object({ mandatory: z3.boolean() }).optional(),
|
|
1295
|
+
epss: z3.object({ mandatory: z3.boolean(), threshold: z3.number() }).optional()
|
|
1296
|
+
}).optional();
|
|
1297
|
+
var checkExploitSignalTool = tool3({
|
|
1298
|
+
description: "Evaluate KEV and EPSS exploit signals for a CVE against policy thresholds. Returns whether the exploit signal overrides severity filtering (mandatory gate).",
|
|
1299
|
+
parameters: z3.object({
|
|
1300
|
+
cveDetails: z3.object({
|
|
1301
|
+
id: z3.string(),
|
|
1302
|
+
kev: kevSchema.optional(),
|
|
1303
|
+
epss: epssSchema.optional()
|
|
1304
|
+
}).passthrough().describe("CveDetails object from lookup-cve"),
|
|
1305
|
+
policy: z3.object({
|
|
1306
|
+
exploitSignalOverride: exploitSignalPolicySchema
|
|
1307
|
+
}).passthrough().describe("Policy object containing exploitSignalOverride configuration")
|
|
1308
|
+
}),
|
|
1309
|
+
execute: async ({ cveDetails, policy }) => {
|
|
1310
|
+
const override = policy.exploitSignalOverride;
|
|
1311
|
+
if (!override) {
|
|
1312
|
+
return {
|
|
1313
|
+
exploitSignalTriggered: false,
|
|
1314
|
+
reason: "No exploitSignalOverride policy configured."
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
if (override.kev?.mandatory && cveDetails.kev?.knownExploited === true) {
|
|
1318
|
+
return {
|
|
1319
|
+
exploitSignalTriggered: true,
|
|
1320
|
+
reason: `CVE ${cveDetails.id} is in the CISA Known Exploited Vulnerabilities (KEV) catalog and kev.mandatory is enabled.`
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
if (override.epss?.mandatory && typeof override.epss.threshold === "number" && typeof cveDetails.epss?.score === "number" && cveDetails.epss.score >= override.epss.threshold) {
|
|
1324
|
+
return {
|
|
1325
|
+
exploitSignalTriggered: true,
|
|
1326
|
+
reason: `CVE ${cveDetails.id} EPSS score ${cveDetails.epss.score} meets or exceeds threshold ${override.epss.threshold} and epss.mandatory is enabled.`
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
return {
|
|
1330
|
+
exploitSignalTriggered: false,
|
|
1331
|
+
reason: "Exploit signal thresholds not met."
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
// src/remediation/local/secops-preflight.ts
|
|
1337
|
+
async function runSecOpsPreflight(normalizedId, cveDetails, opts) {
|
|
1338
|
+
const allSuppressions = opts.suppressionsFile ? [...opts.suppressions, ...loadSuppressionsFile(opts.suppressionsFile)] : opts.suppressions;
|
|
1339
|
+
const activeSuppression = allSuppressions.find(
|
|
1340
|
+
(s) => s.cveId === normalizedId && isActiveSuppression(s)
|
|
1341
|
+
);
|
|
1342
|
+
if (activeSuppression) {
|
|
1343
|
+
return {
|
|
1344
|
+
suppressed: true,
|
|
1345
|
+
summary: `CVE ${normalizedId} suppressed by VEX policy: ${activeSuppression.justification}${activeSuppression.notes ? ` \u2014 ${activeSuppression.notes}` : ""}`
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
let exploitSignalTriggered;
|
|
1349
|
+
if (opts.exploitSignalOverride) {
|
|
1350
|
+
const result = await checkExploitSignalTool.execute({
|
|
1351
|
+
cveDetails,
|
|
1352
|
+
policy: { exploitSignalOverride: opts.exploitSignalOverride }
|
|
1353
|
+
});
|
|
1354
|
+
if (result.exploitSignalTriggered) {
|
|
1355
|
+
exploitSignalTriggered = true;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
let slaBreaches;
|
|
1359
|
+
if (opts.slaCheck && opts.slaPolicy && cveDetails.publishedAt) {
|
|
1360
|
+
const breach = checkSlaBreach(normalizedId, cveDetails.severity, cveDetails.publishedAt, opts.slaPolicy);
|
|
1361
|
+
if (breach) {
|
|
1362
|
+
slaBreaches = [breach];
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return { suppressed: false, exploitSignalTriggered, slaBreaches };
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// src/remediation/local/sbom.ts
|
|
1369
|
+
function buildSbom(packages, vulnerableNames, results) {
|
|
1370
|
+
const statusByPackage = /* @__PURE__ */ new Map();
|
|
1371
|
+
for (const result of results) {
|
|
1372
|
+
if (!vulnerableNames.has(result.packageName)) continue;
|
|
1373
|
+
if (result.suppressedBy) {
|
|
1374
|
+
statusByPackage.set(result.packageName, "suppressed");
|
|
1375
|
+
} else if (!result.applied && result.strategy === "none") {
|
|
1376
|
+
statusByPackage.set(result.packageName, "skipped");
|
|
1377
|
+
} else if (result.applied) {
|
|
1378
|
+
statusByPackage.set(result.packageName, "patched");
|
|
1379
|
+
} else {
|
|
1380
|
+
statusByPackage.set(result.packageName, "unpatched");
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
return packages.map((pkg) => {
|
|
1384
|
+
const isVulnerable = vulnerableNames.has(pkg.name);
|
|
1385
|
+
const entry = {
|
|
1386
|
+
name: pkg.name,
|
|
1387
|
+
version: pkg.version,
|
|
1388
|
+
scope: pkg.type === "direct" ? "direct" : "indirect"
|
|
1389
|
+
};
|
|
1390
|
+
if (isVulnerable) {
|
|
1391
|
+
entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
|
|
1392
|
+
}
|
|
1393
|
+
return entry;
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1127
1397
|
// src/intelligence/sources/registry.ts
|
|
1128
|
-
import { readFileSync as
|
|
1129
|
-
import { join as
|
|
1398
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
1399
|
+
import { join as join8 } from "path";
|
|
1130
1400
|
import { execa as execa4 } from "execa";
|
|
1131
1401
|
import semver from "semver";
|
|
1132
1402
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
@@ -1295,7 +1565,7 @@ async function queryOutdatedPackages(cwd, options = {}) {
|
|
|
1295
1565
|
}
|
|
1296
1566
|
let directDeps;
|
|
1297
1567
|
try {
|
|
1298
|
-
const pkgRaw = JSON.parse(
|
|
1568
|
+
const pkgRaw = JSON.parse(readFileSync6(join8(cwd, "package.json"), "utf8"));
|
|
1299
1569
|
directDeps = /* @__PURE__ */ new Set([
|
|
1300
1570
|
...Object.keys(pkgRaw.dependencies ?? {}),
|
|
1301
1571
|
...Object.keys(pkgRaw.devDependencies ?? {})
|
|
@@ -1324,24 +1594,24 @@ async function queryOutdatedPackages(cwd, options = {}) {
|
|
|
1324
1594
|
}
|
|
1325
1595
|
|
|
1326
1596
|
// src/remediation/tools/apply-version-bump.ts
|
|
1327
|
-
import { tool as
|
|
1328
|
-
import { z as
|
|
1329
|
-
import { join as
|
|
1330
|
-
import { readFileSync as
|
|
1597
|
+
import { tool as tool4 } from "ai";
|
|
1598
|
+
import { z as z4 } from "zod";
|
|
1599
|
+
import { join as join10 } from "path";
|
|
1600
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
|
|
1331
1601
|
import { execa as execa5 } from "execa";
|
|
1332
1602
|
import semver2 from "semver";
|
|
1333
1603
|
|
|
1334
1604
|
// src/platform/repo-lock.ts
|
|
1335
1605
|
import { mkdir, rm } from "fs/promises";
|
|
1336
|
-
import { join as
|
|
1606
|
+
import { join as join9 } from "path";
|
|
1337
1607
|
async function sleep(ms) {
|
|
1338
1608
|
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1339
1609
|
}
|
|
1340
1610
|
async function acquireRepoLock(cwd, options = {}) {
|
|
1341
1611
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
1342
1612
|
const retryDelayMs = options.retryDelayMs ?? 125;
|
|
1343
|
-
const lockRoot =
|
|
1344
|
-
const lockPath =
|
|
1613
|
+
const lockRoot = join9(cwd, ".autoremediator", "locks");
|
|
1614
|
+
const lockPath = join9(cwd, ".autoremediator", "locks", "remediation.lock");
|
|
1345
1615
|
const startedAt = Date.now();
|
|
1346
1616
|
await mkdir(lockRoot, { recursive: true });
|
|
1347
1617
|
while (true) {
|
|
@@ -1371,21 +1641,21 @@ async function withRepoLock(cwd, fn, options) {
|
|
|
1371
1641
|
}
|
|
1372
1642
|
|
|
1373
1643
|
// src/remediation/tools/apply-version-bump.ts
|
|
1374
|
-
var applyVersionBumpTool =
|
|
1644
|
+
var applyVersionBumpTool = tool4({
|
|
1375
1645
|
description: "Update package.json to use the safe version of a vulnerable package and run the project's package manager install. In dry-run mode, only reports what would change.",
|
|
1376
|
-
parameters:
|
|
1377
|
-
cwd:
|
|
1378
|
-
packageManager:
|
|
1379
|
-
packageName:
|
|
1380
|
-
fromVersion:
|
|
1381
|
-
toVersion:
|
|
1382
|
-
dryRun:
|
|
1383
|
-
policy:
|
|
1384
|
-
runTests:
|
|
1385
|
-
installMode:
|
|
1386
|
-
installPreferOffline:
|
|
1387
|
-
enforceFrozenLockfile:
|
|
1388
|
-
workspace:
|
|
1646
|
+
parameters: z4.object({
|
|
1647
|
+
cwd: z4.string().describe("Absolute path to the consumer project root"),
|
|
1648
|
+
packageManager: z4.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
|
|
1649
|
+
packageName: z4.string().describe("The npm package to upgrade"),
|
|
1650
|
+
fromVersion: z4.string().describe("The currently installed vulnerable version"),
|
|
1651
|
+
toVersion: z4.string().describe("The safe target version to upgrade to"),
|
|
1652
|
+
dryRun: z4.boolean().default(false).describe("If true, report changes but do not write"),
|
|
1653
|
+
policy: z4.string().optional().describe("Optional path to .autoremediator policy file"),
|
|
1654
|
+
runTests: z4.boolean().default(false).describe("If true, run test validation after applying the fix"),
|
|
1655
|
+
installMode: z4.enum(["standard", "prefer-offline", "deterministic"]).optional(),
|
|
1656
|
+
installPreferOffline: z4.boolean().optional(),
|
|
1657
|
+
enforceFrozenLockfile: z4.boolean().optional(),
|
|
1658
|
+
workspace: z4.string().optional()
|
|
1389
1659
|
}),
|
|
1390
1660
|
execute: async ({
|
|
1391
1661
|
cwd,
|
|
@@ -1402,7 +1672,7 @@ var applyVersionBumpTool = tool2({
|
|
|
1402
1672
|
workspace
|
|
1403
1673
|
}) => {
|
|
1404
1674
|
const pm = packageManager ?? detectPackageManager(cwd);
|
|
1405
|
-
const pkgPath =
|
|
1675
|
+
const pkgPath = join10(cwd, "package.json");
|
|
1406
1676
|
const loadedPolicy = loadPolicy(cwd, policy);
|
|
1407
1677
|
const commandConstraints = {
|
|
1408
1678
|
...loadedPolicy.constraints,
|
|
@@ -1442,7 +1712,7 @@ var applyVersionBumpTool = tool2({
|
|
|
1442
1712
|
}
|
|
1443
1713
|
let pkgJson;
|
|
1444
1714
|
try {
|
|
1445
|
-
pkgJson = JSON.parse(
|
|
1715
|
+
pkgJson = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
1446
1716
|
} catch {
|
|
1447
1717
|
return {
|
|
1448
1718
|
packageName,
|
|
@@ -1565,10 +1835,10 @@ var applyVersionBumpTool = tool2({
|
|
|
1565
1835
|
});
|
|
1566
1836
|
|
|
1567
1837
|
// src/remediation/tools/apply-package-override/index.ts
|
|
1568
|
-
import { tool as
|
|
1569
|
-
import { z as
|
|
1570
|
-
import { join as
|
|
1571
|
-
import { readFileSync as
|
|
1838
|
+
import { tool as tool5 } from "ai";
|
|
1839
|
+
import { z as z5 } from "zod";
|
|
1840
|
+
import { join as join11 } from "path";
|
|
1841
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
1572
1842
|
import { execa as execa7 } from "execa";
|
|
1573
1843
|
import semver3 from "semver";
|
|
1574
1844
|
|
|
@@ -1648,22 +1918,22 @@ function restoreRecord(record, key, previousValue) {
|
|
|
1648
1918
|
}
|
|
1649
1919
|
|
|
1650
1920
|
// src/remediation/tools/apply-package-override/index.ts
|
|
1651
|
-
var applyPackageOverrideTool =
|
|
1921
|
+
var applyPackageOverrideTool = tool5({
|
|
1652
1922
|
description: "Apply a package-manager-native package.json override for a vulnerable transitive dependency and reinstall. Uses npm overrides, pnpm.overrides, or yarn resolutions.",
|
|
1653
|
-
parameters:
|
|
1654
|
-
cwd:
|
|
1655
|
-
packageManager:
|
|
1656
|
-
packageName:
|
|
1657
|
-
selector:
|
|
1658
|
-
fromVersion:
|
|
1659
|
-
toVersion:
|
|
1660
|
-
dryRun:
|
|
1661
|
-
policy:
|
|
1662
|
-
runTests:
|
|
1663
|
-
installMode:
|
|
1664
|
-
installPreferOffline:
|
|
1665
|
-
enforceFrozenLockfile:
|
|
1666
|
-
workspace:
|
|
1923
|
+
parameters: z5.object({
|
|
1924
|
+
cwd: z5.string().describe("Absolute path to the consumer project root"),
|
|
1925
|
+
packageManager: z5.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
|
|
1926
|
+
packageName: z5.string().describe("The npm package to override"),
|
|
1927
|
+
selector: z5.string().optional().describe("Optional manager-native override selector key (for nested or scoped overrides)"),
|
|
1928
|
+
fromVersion: z5.string().describe("The currently installed vulnerable version"),
|
|
1929
|
+
toVersion: z5.string().describe("The safe target version to override to"),
|
|
1930
|
+
dryRun: z5.boolean().default(false).describe("If true, report changes but do not write"),
|
|
1931
|
+
policy: z5.string().optional().describe("Optional path to .autoremediator policy file"),
|
|
1932
|
+
runTests: z5.boolean().default(false).describe("If true, run test validation after applying the override"),
|
|
1933
|
+
installMode: z5.enum(["standard", "prefer-offline", "deterministic"]).optional(),
|
|
1934
|
+
installPreferOffline: z5.boolean().optional(),
|
|
1935
|
+
enforceFrozenLockfile: z5.boolean().optional(),
|
|
1936
|
+
workspace: z5.string().optional()
|
|
1667
1937
|
}),
|
|
1668
1938
|
execute: async ({
|
|
1669
1939
|
cwd,
|
|
@@ -1681,7 +1951,7 @@ var applyPackageOverrideTool = tool3({
|
|
|
1681
1951
|
workspace
|
|
1682
1952
|
}) => {
|
|
1683
1953
|
const pm = packageManager ?? detectPackageManager(cwd);
|
|
1684
|
-
const pkgPath =
|
|
1954
|
+
const pkgPath = join11(cwd, "package.json");
|
|
1685
1955
|
const loadedPolicy = loadPolicy(cwd, policy);
|
|
1686
1956
|
const commandConstraints = {
|
|
1687
1957
|
...loadedPolicy.constraints,
|
|
@@ -1722,7 +1992,7 @@ var applyPackageOverrideTool = tool3({
|
|
|
1722
1992
|
}
|
|
1723
1993
|
let pkgJson;
|
|
1724
1994
|
try {
|
|
1725
|
-
pkgJson = JSON.parse(
|
|
1995
|
+
pkgJson = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
1726
1996
|
} catch {
|
|
1727
1997
|
return {
|
|
1728
1998
|
packageName,
|
|
@@ -1959,17 +2229,17 @@ async function resolvePrimaryResult(params) {
|
|
|
1959
2229
|
}
|
|
1960
2230
|
|
|
1961
2231
|
// src/remediation/tools/fetch-package-source.ts
|
|
1962
|
-
import { tool as
|
|
1963
|
-
import { z as
|
|
2232
|
+
import { tool as tool6 } from "ai";
|
|
2233
|
+
import { z as z6 } from "zod";
|
|
1964
2234
|
import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, rm as rm2 } from "fs/promises";
|
|
1965
|
-
import { join as
|
|
2235
|
+
import { join as join12 } from "path";
|
|
1966
2236
|
import { execa as execa8 } from "execa";
|
|
1967
|
-
var fetchPackageSourceTool =
|
|
2237
|
+
var fetchPackageSourceTool = tool6({
|
|
1968
2238
|
description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
|
|
1969
|
-
parameters:
|
|
1970
|
-
packageName:
|
|
1971
|
-
version:
|
|
1972
|
-
filePatterns:
|
|
2239
|
+
parameters: z6.object({
|
|
2240
|
+
packageName: z6.string().min(1).describe("The npm package name (e.g., 'lodash', '@scope/package')"),
|
|
2241
|
+
version: z6.string().regex(/^\d+\.\d+\.\d+/, "Must be a valid semver version").describe("Exact package version to download"),
|
|
2242
|
+
filePatterns: z6.array(z6.string()).optional().default(["*.js", "*.ts"]).describe(
|
|
1973
2243
|
"File patterns to extract (glob patterns, default: *.js, *.ts)"
|
|
1974
2244
|
)
|
|
1975
2245
|
}),
|
|
@@ -1979,23 +2249,23 @@ var fetchPackageSourceTool = tool4({
|
|
|
1979
2249
|
filePatterns
|
|
1980
2250
|
}) => {
|
|
1981
2251
|
const tempBaseDir = `/tmp/autoremediator-pkg-${Date.now()}`;
|
|
1982
|
-
const extractDir =
|
|
2252
|
+
const extractDir = join12(tempBaseDir, "out");
|
|
1983
2253
|
try {
|
|
1984
2254
|
const npmUrl = `https://registry.npmjs.org/${packageName}/-/${packageName.split("/").pop()}-${version}.tgz`;
|
|
1985
2255
|
await mkdir2(tempBaseDir, { recursive: true });
|
|
1986
|
-
const tarballPath =
|
|
2256
|
+
const tarballPath = join12(tempBaseDir, "package.tgz");
|
|
1987
2257
|
await execa8("curl", ["-L", "-o", tarballPath, npmUrl]);
|
|
1988
2258
|
await mkdir2(extractDir, { recursive: true });
|
|
1989
2259
|
await execa8("tar", ["-xzf", tarballPath, "-C", extractDir]);
|
|
1990
2260
|
const extractedContents = await readdir2(extractDir);
|
|
1991
|
-
const packageRootDir = extractedContents.includes("package") ?
|
|
2261
|
+
const packageRootDir = extractedContents.includes("package") ? join12(extractDir, "package") : extractDir;
|
|
1992
2262
|
const sourceCode = {};
|
|
1993
2263
|
async function walkDir(dir, relativeBase) {
|
|
1994
2264
|
try {
|
|
1995
2265
|
const files = await readdir2(dir, { withFileTypes: true });
|
|
1996
2266
|
for (const file of files) {
|
|
1997
|
-
const fullPath =
|
|
1998
|
-
const relPath =
|
|
2267
|
+
const fullPath = join12(dir, file.name);
|
|
2268
|
+
const relPath = join12(relativeBase, file.name);
|
|
1999
2269
|
if (file.isDirectory()) {
|
|
2000
2270
|
if (![
|
|
2001
2271
|
"node_modules",
|
|
@@ -2058,8 +2328,8 @@ var fetchPackageSourceTool = tool4({
|
|
|
2058
2328
|
});
|
|
2059
2329
|
|
|
2060
2330
|
// src/remediation/tools/generate-patch/index.ts
|
|
2061
|
-
import { tool as
|
|
2062
|
-
import { z as
|
|
2331
|
+
import { tool as tool7 } from "ai";
|
|
2332
|
+
import { z as z7 } from "zod";
|
|
2063
2333
|
import { generateText } from "ai";
|
|
2064
2334
|
|
|
2065
2335
|
// src/remediation/strategies/patch-synthesis-prompt.ts
|
|
@@ -2192,31 +2462,31 @@ function generateUnifiedDiff(original, fixed, filePath) {
|
|
|
2192
2462
|
}
|
|
2193
2463
|
|
|
2194
2464
|
// src/remediation/tools/generate-patch/index.ts
|
|
2195
|
-
var generatePatchTool =
|
|
2465
|
+
var generatePatchTool = tool7({
|
|
2196
2466
|
description: "Generate a unified diff patch for a CVE using LLM analysis of vulnerable source code.",
|
|
2197
|
-
parameters:
|
|
2198
|
-
packageName:
|
|
2199
|
-
vulnerableVersion:
|
|
2200
|
-
cveId:
|
|
2201
|
-
cveSummary:
|
|
2202
|
-
sourceFiles:
|
|
2467
|
+
parameters: z7.object({
|
|
2468
|
+
packageName: z7.string().min(1).describe("The npm package name"),
|
|
2469
|
+
vulnerableVersion: z7.string().describe("The vulnerable version string"),
|
|
2470
|
+
cveId: z7.string().regex(/^CVE-\d{4}-\d+$/i).describe("CVE ID (e.g., CVE-2021-23337)"),
|
|
2471
|
+
cveSummary: z7.string().min(10).describe("CVE description and impact"),
|
|
2472
|
+
sourceFiles: z7.record(z7.string()).describe(
|
|
2203
2473
|
"Map of file paths to source code contents from fetch-package-source"
|
|
2204
2474
|
),
|
|
2205
|
-
vulnerabilityCategory:
|
|
2206
|
-
dryRun:
|
|
2207
|
-
llmProvider:
|
|
2208
|
-
model:
|
|
2209
|
-
policy:
|
|
2210
|
-
cwd:
|
|
2211
|
-
providerSafetyProfile:
|
|
2212
|
-
patchConfidenceThresholds:
|
|
2213
|
-
low:
|
|
2214
|
-
medium:
|
|
2215
|
-
high:
|
|
2475
|
+
vulnerabilityCategory: z7.enum(["redos", "code-injection", "path-traversal", "unknown"]).optional().default("unknown").describe("Category of the vulnerability for better context"),
|
|
2476
|
+
dryRun: z7.boolean().optional().default(false).describe("If true, return analysis without generating patches"),
|
|
2477
|
+
llmProvider: z7.enum(["remote", "local"]).optional().describe("Optional provider override for patch generation"),
|
|
2478
|
+
model: z7.string().optional().describe("Optional model override for patch generation"),
|
|
2479
|
+
policy: z7.string().optional().describe("Optional policy file path for model default resolution"),
|
|
2480
|
+
cwd: z7.string().optional().describe("Optional working directory for policy/model resolution"),
|
|
2481
|
+
providerSafetyProfile: z7.enum(["strict", "relaxed"]).optional().describe("Confidence threshold profile for patch acceptance"),
|
|
2482
|
+
patchConfidenceThresholds: z7.object({
|
|
2483
|
+
low: z7.number().min(0).max(1).optional(),
|
|
2484
|
+
medium: z7.number().min(0).max(1).optional(),
|
|
2485
|
+
high: z7.number().min(0).max(1).optional()
|
|
2216
2486
|
}).optional().describe("Optional per-risk confidence thresholds for patch acceptance"),
|
|
2217
|
-
dynamicModelRouting:
|
|
2218
|
-
dynamicRoutingThresholdChars:
|
|
2219
|
-
modelPersonality:
|
|
2487
|
+
dynamicModelRouting: z7.boolean().optional().describe("Enable dynamic model routing by input size"),
|
|
2488
|
+
dynamicRoutingThresholdChars: z7.number().int().positive().optional().describe("Threshold for dynamic model routing"),
|
|
2489
|
+
modelPersonality: z7.enum(["analytical", "pragmatic", "balanced"]).optional().describe("Prompt personality for patch-generation guidance")
|
|
2220
2490
|
}),
|
|
2221
2491
|
execute: async ({
|
|
2222
2492
|
packageName,
|
|
@@ -2366,17 +2636,18 @@ var generatePatchTool = tool5({
|
|
|
2366
2636
|
});
|
|
2367
2637
|
|
|
2368
2638
|
// src/remediation/tools/apply-patch-file/index.ts
|
|
2369
|
-
import { tool as
|
|
2370
|
-
import { z as
|
|
2639
|
+
import { tool as tool8 } from "ai";
|
|
2640
|
+
import { z as z8 } from "zod";
|
|
2371
2641
|
import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
|
|
2372
|
-
import { join as
|
|
2642
|
+
import { join as join14 } from "path";
|
|
2373
2643
|
import { execa as execa10 } from "execa";
|
|
2374
2644
|
|
|
2375
2645
|
// src/remediation/tools/apply-patch-file/helpers.ts
|
|
2376
2646
|
import { existsSync as existsSync7 } from "fs";
|
|
2377
2647
|
import { mkdtemp, readFile as readFile4, rm as rm3, writeFile } from "fs/promises";
|
|
2648
|
+
import { createHash } from "crypto";
|
|
2378
2649
|
import { tmpdir } from "os";
|
|
2379
|
-
import { join as
|
|
2650
|
+
import { join as join13 } from "path";
|
|
2380
2651
|
import { execa as execa9 } from "execa";
|
|
2381
2652
|
async function resolvePatchMode(packageManager, cwd) {
|
|
2382
2653
|
if (packageManager === "npm") return "patch-package";
|
|
@@ -2403,11 +2674,15 @@ function extractPatchedFiles(patchContent) {
|
|
|
2403
2674
|
function countPatchHunks(patchContent) {
|
|
2404
2675
|
return patchContent.split(/\r?\n/).filter((line) => line.startsWith("@@ ")).length;
|
|
2405
2676
|
}
|
|
2677
|
+
function computePatchIntegrity(patchContent) {
|
|
2678
|
+
const hex = createHash("sha256").update(patchContent, "utf8").digest("hex");
|
|
2679
|
+
return `sha256:${hex}`;
|
|
2680
|
+
}
|
|
2406
2681
|
async function writePatchManifest(manifestFilePath, artifact) {
|
|
2407
2682
|
await writeFile(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
|
|
2408
2683
|
}
|
|
2409
2684
|
async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
2410
|
-
const pkgJsonPath =
|
|
2685
|
+
const pkgJsonPath = join13(cwd, "package.json");
|
|
2411
2686
|
let pkgJson;
|
|
2412
2687
|
try {
|
|
2413
2688
|
pkgJson = JSON.parse(await readFile4(pkgJsonPath, "utf8"));
|
|
@@ -2447,7 +2722,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
|
2447
2722
|
return { success: true };
|
|
2448
2723
|
}
|
|
2449
2724
|
async function capturePackageJsonSnapshot(cwd) {
|
|
2450
|
-
const path =
|
|
2725
|
+
const path = join13(cwd, "package.json");
|
|
2451
2726
|
try {
|
|
2452
2727
|
const content = await readFile4(path, "utf8");
|
|
2453
2728
|
return { path, content };
|
|
@@ -2517,8 +2792,8 @@ ${createResult.stderr}`);
|
|
|
2517
2792
|
error: `Could not determine native patch directory for ${packageSpec}.`
|
|
2518
2793
|
};
|
|
2519
2794
|
}
|
|
2520
|
-
const tempPatchDir = await mkdtemp(
|
|
2521
|
-
const tempPatchFile =
|
|
2795
|
+
const tempPatchDir = await mkdtemp(join13(tmpdir(), "autoremediator-native-patch-"));
|
|
2796
|
+
const tempPatchFile = join13(tempPatchDir, "change.patch");
|
|
2522
2797
|
try {
|
|
2523
2798
|
await writeFile(tempPatchFile, patchContent, "utf8");
|
|
2524
2799
|
await execa9("patch", ["-p1", "-i", tempPatchFile], {
|
|
@@ -2603,31 +2878,31 @@ function extractFailedTests(output) {
|
|
|
2603
2878
|
}
|
|
2604
2879
|
|
|
2605
2880
|
// src/remediation/tools/apply-patch-file/index.ts
|
|
2606
|
-
var applyPatchFileTool =
|
|
2881
|
+
var applyPatchFileTool = tool8({
|
|
2607
2882
|
description: "Write generated patch file and apply it using package-manager-native patch flow when available, falling back to patch-package when needed.",
|
|
2608
|
-
parameters:
|
|
2609
|
-
packageName:
|
|
2610
|
-
vulnerableVersion:
|
|
2611
|
-
patchContent:
|
|
2612
|
-
cveId:
|
|
2613
|
-
confidence:
|
|
2614
|
-
riskLevel:
|
|
2615
|
-
patches:
|
|
2616
|
-
|
|
2617
|
-
filePath:
|
|
2618
|
-
unifiedDiff:
|
|
2883
|
+
parameters: z8.object({
|
|
2884
|
+
packageName: z8.string().min(1).describe("The npm package name"),
|
|
2885
|
+
vulnerableVersion: z8.string().describe("The vulnerable version string"),
|
|
2886
|
+
patchContent: z8.string().min(10).optional().describe("Unified diff patch content from generate-patch"),
|
|
2887
|
+
cveId: z8.string().optional().describe("Optional CVE ID associated with this patch artifact"),
|
|
2888
|
+
confidence: z8.number().min(0).max(1).optional().describe("Optional patch confidence score from generate-patch"),
|
|
2889
|
+
riskLevel: z8.enum(["low", "medium", "high"]).optional().describe("Optional risk level from generate-patch"),
|
|
2890
|
+
patches: z8.array(
|
|
2891
|
+
z8.object({
|
|
2892
|
+
filePath: z8.string().min(1),
|
|
2893
|
+
unifiedDiff: z8.string().min(10)
|
|
2619
2894
|
})
|
|
2620
2895
|
).optional().describe("Patch list from generate-patch; first patch is applied"),
|
|
2621
|
-
patchesDir:
|
|
2622
|
-
cwd:
|
|
2623
|
-
packageManager:
|
|
2624
|
-
policy:
|
|
2625
|
-
installMode:
|
|
2626
|
-
installPreferOffline:
|
|
2627
|
-
enforceFrozenLockfile:
|
|
2628
|
-
workspace:
|
|
2629
|
-
validateWithTests:
|
|
2630
|
-
dryRun:
|
|
2896
|
+
patchesDir: z8.string().optional().default("./patches").describe("Directory to store patch files"),
|
|
2897
|
+
cwd: z8.string().describe("Project root directory (for package.json)"),
|
|
2898
|
+
packageManager: z8.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
|
|
2899
|
+
policy: z8.string().optional().describe("Optional path to .autoremediator policy file"),
|
|
2900
|
+
installMode: z8.enum(["standard", "prefer-offline", "deterministic"]).optional(),
|
|
2901
|
+
installPreferOffline: z8.boolean().optional(),
|
|
2902
|
+
enforceFrozenLockfile: z8.boolean().optional(),
|
|
2903
|
+
workspace: z8.string().optional(),
|
|
2904
|
+
validateWithTests: z8.boolean().optional().default(true).describe("Run package manager test command to validate patch doesn't break anything"),
|
|
2905
|
+
dryRun: z8.boolean().optional().default(false).describe("If true, report but do not mutate files")
|
|
2631
2906
|
}).refine((value) => Boolean(value.patchContent || value.patches && value.patches.length > 0), {
|
|
2632
2907
|
message: "Either patchContent or patches must be provided"
|
|
2633
2908
|
}),
|
|
@@ -2698,7 +2973,7 @@ var applyPatchFileTool = tool6({
|
|
|
2698
2973
|
};
|
|
2699
2974
|
}
|
|
2700
2975
|
const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
|
|
2701
|
-
const patchFilePath =
|
|
2976
|
+
const patchFilePath = join14(cwd, patchesDir, patchFileName);
|
|
2702
2977
|
const manifestFilePath = `${patchFilePath}.json`;
|
|
2703
2978
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2704
2979
|
const baseArtifact = {
|
|
@@ -2717,6 +2992,7 @@ var applyPatchFileTool = tool6({
|
|
|
2717
2992
|
hunkCount,
|
|
2718
2993
|
applied: false,
|
|
2719
2994
|
dryRun,
|
|
2995
|
+
integrity: computePatchIntegrity(selectedPatch),
|
|
2720
2996
|
validationPhases
|
|
2721
2997
|
};
|
|
2722
2998
|
if (dryRun) {
|
|
@@ -2736,7 +3012,7 @@ var applyPatchFileTool = tool6({
|
|
|
2736
3012
|
}
|
|
2737
3013
|
return withRepoLock(cwd, async () => {
|
|
2738
3014
|
const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
|
|
2739
|
-
const patchesDirPath =
|
|
3015
|
+
const patchesDirPath = join14(cwd, patchesDir);
|
|
2740
3016
|
await mkdir3(patchesDirPath, { recursive: true });
|
|
2741
3017
|
await writeFile2(patchFilePath, selectedPatch, "utf8");
|
|
2742
3018
|
validationPhases.push({
|
|
@@ -3171,7 +3447,14 @@ function resolveLocalRunOptions(options) {
|
|
|
3171
3447
|
...options.patchConfidenceThresholds
|
|
3172
3448
|
},
|
|
3173
3449
|
dynamicModelRouting: options.dynamicModelRouting ?? loadedPolicy.dynamicModelRouting ?? false,
|
|
3174
|
-
dynamicRoutingThresholdChars: options.dynamicRoutingThresholdChars ?? loadedPolicy.dynamicRoutingThresholdChars
|
|
3450
|
+
dynamicRoutingThresholdChars: options.dynamicRoutingThresholdChars ?? loadedPolicy.dynamicRoutingThresholdChars,
|
|
3451
|
+
exploitSignalOverride: options.exploitSignalOverride ?? loadedPolicy.exploitSignalOverride,
|
|
3452
|
+
suppressions: loadedPolicy.suppressions ?? [],
|
|
3453
|
+
suppressionsFile: options.suppressionsFile,
|
|
3454
|
+
slaCheck: options.slaCheck ?? false,
|
|
3455
|
+
slaPolicy: loadedPolicy.sla,
|
|
3456
|
+
skipUnreachable: options.skipUnreachable ?? loadedPolicy.skipUnreachable ?? false,
|
|
3457
|
+
regressionCheck: options.regressionCheck ?? false
|
|
3175
3458
|
};
|
|
3176
3459
|
}
|
|
3177
3460
|
|
|
@@ -3228,7 +3511,14 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3228
3511
|
consensusModel,
|
|
3229
3512
|
patchConfidenceThresholds,
|
|
3230
3513
|
dynamicModelRouting,
|
|
3231
|
-
dynamicRoutingThresholdChars
|
|
3514
|
+
dynamicRoutingThresholdChars,
|
|
3515
|
+
exploitSignalOverride,
|
|
3516
|
+
suppressions,
|
|
3517
|
+
suppressionsFile,
|
|
3518
|
+
slaCheck,
|
|
3519
|
+
slaPolicy,
|
|
3520
|
+
skipUnreachable,
|
|
3521
|
+
regressionCheck
|
|
3232
3522
|
} = resolved;
|
|
3233
3523
|
const collectedResults = [];
|
|
3234
3524
|
const llmUsage = [];
|
|
@@ -3267,6 +3557,29 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3267
3557
|
cveDetails = mergeGhDataIntoCveDetails(cveDetails, ghPackages);
|
|
3268
3558
|
}
|
|
3269
3559
|
cveDetails = await enrichWithNvd(cveDetails);
|
|
3560
|
+
const preflight = await runSecOpsPreflight(normalizedId, cveDetails, {
|
|
3561
|
+
suppressions,
|
|
3562
|
+
suppressionsFile,
|
|
3563
|
+
exploitSignalOverride,
|
|
3564
|
+
slaCheck,
|
|
3565
|
+
slaPolicy
|
|
3566
|
+
});
|
|
3567
|
+
if (preflight.suppressed) {
|
|
3568
|
+
return {
|
|
3569
|
+
cveId,
|
|
3570
|
+
cveDetails,
|
|
3571
|
+
vulnerablePackages: [],
|
|
3572
|
+
results: [],
|
|
3573
|
+
agentSteps,
|
|
3574
|
+
summary: preflight.summary,
|
|
3575
|
+
correlation: {
|
|
3576
|
+
requestId: options.requestId,
|
|
3577
|
+
sessionId: options.sessionId,
|
|
3578
|
+
parentRunId: options.parentRunId
|
|
3579
|
+
}
|
|
3580
|
+
};
|
|
3581
|
+
}
|
|
3582
|
+
const { exploitSignalTriggered, slaBreaches } = preflight;
|
|
3270
3583
|
if (cveDetails.affectedPackages.length === 0) {
|
|
3271
3584
|
return {
|
|
3272
3585
|
cveId,
|
|
@@ -3275,6 +3588,8 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3275
3588
|
results: collectedResults,
|
|
3276
3589
|
agentSteps,
|
|
3277
3590
|
summary: `Local mode lookup succeeded but no npm affected packages were found for ${normalizedId}.`,
|
|
3591
|
+
exploitSignalTriggered,
|
|
3592
|
+
slaBreaches,
|
|
3278
3593
|
correlation: {
|
|
3279
3594
|
requestId: options.requestId,
|
|
3280
3595
|
sessionId: options.sessionId,
|
|
@@ -3297,6 +3612,8 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3297
3612
|
results: collectedResults,
|
|
3298
3613
|
agentSteps,
|
|
3299
3614
|
summary: `Local mode failed at check-inventory: ${inventory.error}`,
|
|
3615
|
+
exploitSignalTriggered,
|
|
3616
|
+
slaBreaches,
|
|
3300
3617
|
correlation: {
|
|
3301
3618
|
requestId: options.requestId,
|
|
3302
3619
|
sessionId: options.sessionId,
|
|
@@ -3308,6 +3625,21 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3308
3625
|
vulnerablePackages = findVulnerablePackages(cveDetails, installedPackages);
|
|
3309
3626
|
agentSteps += 1;
|
|
3310
3627
|
for (const vulnerable of vulnerablePackages) {
|
|
3628
|
+
if (skipUnreachable) {
|
|
3629
|
+
const reach = assessPackageReachability(cwd, vulnerable.installed.name);
|
|
3630
|
+
if (reach.status === "not-reachable") {
|
|
3631
|
+
collectedResults.push({
|
|
3632
|
+
packageName: vulnerable.installed.name,
|
|
3633
|
+
fromVersion: vulnerable.installed.version,
|
|
3634
|
+
strategy: "none",
|
|
3635
|
+
applied: false,
|
|
3636
|
+
dryRun,
|
|
3637
|
+
message: `Skipped: '${vulnerable.installed.name}' is not reachable from source code.`,
|
|
3638
|
+
reachability: reach
|
|
3639
|
+
});
|
|
3640
|
+
continue;
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3311
3643
|
const primary = await resolvePrimaryResult({
|
|
3312
3644
|
vulnerable,
|
|
3313
3645
|
cwd,
|
|
@@ -3353,11 +3685,22 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3353
3685
|
}
|
|
3354
3686
|
continue;
|
|
3355
3687
|
}
|
|
3356
|
-
|
|
3688
|
+
const primaryResult = {
|
|
3357
3689
|
...primary.result,
|
|
3358
3690
|
dependencyScope: vulnerable.installed.type === "direct" ? "direct" : "transitive"
|
|
3359
|
-
}
|
|
3691
|
+
};
|
|
3692
|
+
if (regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
|
|
3693
|
+
try {
|
|
3694
|
+
if (semver5.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, { includePrerelease: false })) {
|
|
3695
|
+
primaryResult.regressionDetected = true;
|
|
3696
|
+
}
|
|
3697
|
+
} catch {
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
collectedResults.push(primaryResult);
|
|
3360
3701
|
}
|
|
3702
|
+
const vulnerableNames = new Set(vulnerablePackages.map((v) => v.installed.name));
|
|
3703
|
+
const sbom = buildSbom(installedPackages, vulnerableNames, collectedResults);
|
|
3361
3704
|
return {
|
|
3362
3705
|
cveId,
|
|
3363
3706
|
cveDetails,
|
|
@@ -3366,6 +3709,9 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3366
3709
|
agentSteps,
|
|
3367
3710
|
summary: buildLocalSummary(vulnerablePackages, collectedResults),
|
|
3368
3711
|
llmUsage: llmUsage.length > 0 ? llmUsage : void 0,
|
|
3712
|
+
exploitSignalTriggered,
|
|
3713
|
+
slaBreaches,
|
|
3714
|
+
sbom,
|
|
3369
3715
|
correlation: {
|
|
3370
3716
|
requestId: options.requestId,
|
|
3371
3717
|
sessionId: options.sessionId,
|
|
@@ -3440,8 +3786,8 @@ function accumulateStepResults(params) {
|
|
|
3440
3786
|
}
|
|
3441
3787
|
|
|
3442
3788
|
// src/remediation/orchestration-prompt.ts
|
|
3443
|
-
import { existsSync as existsSync8, readFileSync as
|
|
3444
|
-
import { join as
|
|
3789
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
|
|
3790
|
+
import { join as join15 } from "path";
|
|
3445
3791
|
function buildProviderAddendum(provider, personality = "balanced") {
|
|
3446
3792
|
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.";
|
|
3447
3793
|
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.";
|
|
@@ -3452,7 +3798,7 @@ Provider profile:
|
|
|
3452
3798
|
- ${personalityDirective}`;
|
|
3453
3799
|
}
|
|
3454
3800
|
function loadOrchestrationPrompt(ctx) {
|
|
3455
|
-
const promptPath =
|
|
3801
|
+
const promptPath = join15(process.cwd(), ".github", "instructions", "orchestration.instructions.md");
|
|
3456
3802
|
if (!existsSync8(promptPath)) {
|
|
3457
3803
|
return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
|
|
3458
3804
|
Working directory: ${ctx.cwd}
|
|
@@ -3479,7 +3825,7 @@ Fallback sequence (when neither version bump nor override can be applied):
|
|
|
3479
3825
|
|
|
3480
3826
|
Always respect dryRun and policy constraints.`;
|
|
3481
3827
|
}
|
|
3482
|
-
const template =
|
|
3828
|
+
const template = readFileSync9(promptPath, "utf8");
|
|
3483
3829
|
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("{{directDependenciesOnly}}", String(ctx.constraints.directDependenciesOnly ?? false)).replaceAll("{{preferVersionBump}}", String(ctx.constraints.preferVersionBump ?? false)) + buildProviderAddendum(ctx.llmProvider, ctx.modelPersonality);
|
|
3484
3830
|
}
|
|
3485
3831
|
|
|
@@ -3498,8 +3844,8 @@ function createProgressEmitter(options) {
|
|
|
3498
3844
|
}
|
|
3499
3845
|
|
|
3500
3846
|
// src/remediation/tools/lookup-cve.ts
|
|
3501
|
-
import { tool as
|
|
3502
|
-
import { z as
|
|
3847
|
+
import { tool as tool9 } from "ai";
|
|
3848
|
+
import { z as z9 } from "zod";
|
|
3503
3849
|
|
|
3504
3850
|
// src/intelligence/sources/cisa-kev.ts
|
|
3505
3851
|
var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
|
|
@@ -3777,10 +4123,10 @@ async function enrichWithExternalFeeds(details) {
|
|
|
3777
4123
|
}
|
|
3778
4124
|
|
|
3779
4125
|
// src/remediation/tools/lookup-cve.ts
|
|
3780
|
-
var lookupCveTool =
|
|
4126
|
+
var lookupCveTool = tool9({
|
|
3781
4127
|
description: "Look up a CVE ID and return the list of affected npm packages, their vulnerable version ranges, and the first patched version. Always call this first.",
|
|
3782
|
-
parameters:
|
|
3783
|
-
cveId:
|
|
4128
|
+
parameters: z9.object({
|
|
4129
|
+
cveId: z9.string().regex(/^CVE-\d{4}-\d+$/i, "Must be a valid CVE ID like CVE-2021-23337")
|
|
3784
4130
|
}),
|
|
3785
4131
|
execute: async ({ cveId }) => {
|
|
3786
4132
|
const normalizedId = cveId.toUpperCase();
|
|
@@ -3846,26 +4192,26 @@ var lookupCveTool = tool7({
|
|
|
3846
4192
|
});
|
|
3847
4193
|
|
|
3848
4194
|
// src/remediation/tools/check-version-match.ts
|
|
3849
|
-
import { tool as
|
|
3850
|
-
import { z as
|
|
3851
|
-
import
|
|
3852
|
-
var affectedPackageSchema =
|
|
3853
|
-
name:
|
|
3854
|
-
ecosystem:
|
|
3855
|
-
vulnerableRange:
|
|
3856
|
-
firstPatchedVersion:
|
|
3857
|
-
source:
|
|
4195
|
+
import { tool as tool10 } from "ai";
|
|
4196
|
+
import { z as z10 } from "zod";
|
|
4197
|
+
import semver6 from "semver";
|
|
4198
|
+
var affectedPackageSchema = z10.object({
|
|
4199
|
+
name: z10.string(),
|
|
4200
|
+
ecosystem: z10.literal("npm"),
|
|
4201
|
+
vulnerableRange: z10.string(),
|
|
4202
|
+
firstPatchedVersion: z10.string().optional(),
|
|
4203
|
+
source: z10.enum(["osv", "github-advisory"])
|
|
3858
4204
|
});
|
|
3859
|
-
var inventoryPackageSchema =
|
|
3860
|
-
name:
|
|
3861
|
-
version:
|
|
3862
|
-
type:
|
|
4205
|
+
var inventoryPackageSchema = z10.object({
|
|
4206
|
+
name: z10.string(),
|
|
4207
|
+
version: z10.string(),
|
|
4208
|
+
type: z10.enum(["direct", "indirect"])
|
|
3863
4209
|
});
|
|
3864
|
-
var checkVersionMatchTool =
|
|
4210
|
+
var checkVersionMatchTool = tool10({
|
|
3865
4211
|
description: "Check which of the project's installed packages fall within the CVE's vulnerable version ranges. Returns only the packages that are actually vulnerable.",
|
|
3866
|
-
parameters:
|
|
3867
|
-
installedPackages:
|
|
3868
|
-
affectedPackages:
|
|
4212
|
+
parameters: z10.object({
|
|
4213
|
+
installedPackages: z10.array(inventoryPackageSchema).describe("Output from the check-inventory tool"),
|
|
4214
|
+
affectedPackages: z10.array(affectedPackageSchema).describe("affectedPackages array from the lookup-cve tool result")
|
|
3869
4215
|
}),
|
|
3870
4216
|
execute: async ({ installedPackages, affectedPackages }) => {
|
|
3871
4217
|
const vulnerable = [];
|
|
@@ -3874,10 +4220,10 @@ var checkVersionMatchTool = tool8({
|
|
|
3874
4220
|
(p) => p.name === affected.name
|
|
3875
4221
|
);
|
|
3876
4222
|
for (const installed of matches) {
|
|
3877
|
-
if (!
|
|
4223
|
+
if (!semver6.valid(installed.version)) continue;
|
|
3878
4224
|
let isVulnerable = false;
|
|
3879
4225
|
try {
|
|
3880
|
-
isVulnerable =
|
|
4226
|
+
isVulnerable = semver6.satisfies(installed.version, affected.vulnerableRange, {
|
|
3881
4227
|
includePrerelease: false
|
|
3882
4228
|
});
|
|
3883
4229
|
} catch {
|
|
@@ -3896,17 +4242,17 @@ var checkVersionMatchTool = tool8({
|
|
|
3896
4242
|
});
|
|
3897
4243
|
|
|
3898
4244
|
// src/remediation/tools/find-fixed-version.ts
|
|
3899
|
-
import { tool as
|
|
3900
|
-
import { z as
|
|
3901
|
-
var findFixedVersionTool =
|
|
4245
|
+
import { tool as tool11 } from "ai";
|
|
4246
|
+
import { z as z11 } from "zod";
|
|
4247
|
+
var findFixedVersionTool = tool11({
|
|
3902
4248
|
description: "Query the npm registry to find the safest published upgrade version for a package that is >= the first patched version. Prefer patch upgrades first, then minor, and only fall back to major when no same-major fix exists.",
|
|
3903
|
-
parameters:
|
|
3904
|
-
packageName:
|
|
3905
|
-
installedVersion:
|
|
3906
|
-
firstPatchedVersion:
|
|
4249
|
+
parameters: z11.object({
|
|
4250
|
+
packageName: z11.string().describe("The npm package name"),
|
|
4251
|
+
installedVersion: z11.string().describe("The currently installed version (exact semver)"),
|
|
4252
|
+
firstPatchedVersion: z11.string().describe(
|
|
3907
4253
|
"The first version that is NOT vulnerable (from lookup-cve). Use this as the floor."
|
|
3908
4254
|
),
|
|
3909
|
-
vulnerableRange:
|
|
4255
|
+
vulnerableRange: z11.string().optional().describe("Optional vulnerable semver range used to exclude still-vulnerable versions")
|
|
3910
4256
|
}),
|
|
3911
4257
|
execute: async ({
|
|
3912
4258
|
packageName,
|
|
@@ -3943,6 +4289,36 @@ var findFixedVersionTool = tool9({
|
|
|
3943
4289
|
}
|
|
3944
4290
|
});
|
|
3945
4291
|
|
|
4292
|
+
// src/remediation/tools/check-suppression.ts
|
|
4293
|
+
import { tool as tool12 } from "ai";
|
|
4294
|
+
import { z as z12 } from "zod";
|
|
4295
|
+
var suppressionSchema = z12.object({
|
|
4296
|
+
cveId: z12.string(),
|
|
4297
|
+
justification: z12.enum(["not_affected", "fixed", "mitigated", "under_investigation"]),
|
|
4298
|
+
notes: z12.string().optional(),
|
|
4299
|
+
expiresAt: z12.string().optional()
|
|
4300
|
+
});
|
|
4301
|
+
var checkSuppressionTool = tool12({
|
|
4302
|
+
description: "Check whether a CVE is suppressed by an active VEX suppression entry in the policy. Returns suppressed=true and the justification if an active match is found. Call this after check-version-match.",
|
|
4303
|
+
parameters: z12.object({
|
|
4304
|
+
cveId: z12.string().describe("The CVE ID to look up in suppressions"),
|
|
4305
|
+
suppressions: z12.array(suppressionSchema).describe("The suppressions array from the loaded policy")
|
|
4306
|
+
}),
|
|
4307
|
+
execute: async ({ cveId, suppressions }) => {
|
|
4308
|
+
const match = suppressions.find(
|
|
4309
|
+
(s) => s.cveId === cveId && isActiveSuppression(s)
|
|
4310
|
+
);
|
|
4311
|
+
if (!match) {
|
|
4312
|
+
return { suppressed: false };
|
|
4313
|
+
}
|
|
4314
|
+
return {
|
|
4315
|
+
suppressed: true,
|
|
4316
|
+
justification: match.justification,
|
|
4317
|
+
notes: match.notes
|
|
4318
|
+
};
|
|
4319
|
+
}
|
|
4320
|
+
});
|
|
4321
|
+
|
|
3946
4322
|
// src/remediation/runtime-tools.ts
|
|
3947
4323
|
function buildRuntimeTools(ctx) {
|
|
3948
4324
|
const tools = {
|
|
@@ -3950,7 +4326,10 @@ function buildRuntimeTools(ctx) {
|
|
|
3950
4326
|
"check-inventory": ctx.checkInventoryToolForRun,
|
|
3951
4327
|
"check-version-match": checkVersionMatchTool,
|
|
3952
4328
|
"find-fixed-version": findFixedVersionTool,
|
|
3953
|
-
"apply-version-bump": ctx.applyVersionBumpToolForRun
|
|
4329
|
+
"apply-version-bump": ctx.applyVersionBumpToolForRun,
|
|
4330
|
+
"check-suppression": checkSuppressionTool,
|
|
4331
|
+
"check-exploit-signal": checkExploitSignalTool,
|
|
4332
|
+
"check-reachability": checkReachabilityTool
|
|
3954
4333
|
};
|
|
3955
4334
|
if (!ctx.constraints.directDependenciesOnly && !ctx.constraints.preferVersionBump) {
|
|
3956
4335
|
tools["apply-package-override"] = ctx.applyPackageOverrideToolForRun;
|
|
@@ -4172,7 +4551,7 @@ function resolveConstraints(options, cwd) {
|
|
|
4172
4551
|
|
|
4173
4552
|
// src/platform/evidence.ts
|
|
4174
4553
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
4175
|
-
import { join as
|
|
4554
|
+
import { join as join16 } from "path";
|
|
4176
4555
|
function createEvidenceLog(cwd, cveIds, context = {}) {
|
|
4177
4556
|
return {
|
|
4178
4557
|
runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -4203,9 +4582,9 @@ function finalizeEvidence(log) {
|
|
|
4203
4582
|
return log;
|
|
4204
4583
|
}
|
|
4205
4584
|
function writeEvidenceLog(cwd, log) {
|
|
4206
|
-
const dir =
|
|
4585
|
+
const dir = join16(cwd, ".autoremediator", "evidence");
|
|
4207
4586
|
mkdirSync3(dir, { recursive: true });
|
|
4208
|
-
const filePath =
|
|
4587
|
+
const filePath = join16(dir, `${log.runId}.json`);
|
|
4209
4588
|
writeFileSync5(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
|
|
4210
4589
|
return filePath;
|
|
4211
4590
|
}
|
|
@@ -4261,7 +4640,9 @@ function addRemediateResultSteps(evidence, cveId, report) {
|
|
|
4261
4640
|
dryRun: result.dryRun,
|
|
4262
4641
|
unresolvedReason: result.unresolvedReason,
|
|
4263
4642
|
reachability: result.reachability?.status,
|
|
4264
|
-
hasAlternatives: Boolean(result.alternativeSuggestions?.length)
|
|
4643
|
+
hasAlternatives: Boolean(result.alternativeSuggestions?.length),
|
|
4644
|
+
suppressedBy: result.suppressedBy?.justification,
|
|
4645
|
+
regressionDetected: result.regressionDetected
|
|
4265
4646
|
}
|
|
4266
4647
|
);
|
|
4267
4648
|
}
|
|
@@ -4272,7 +4653,11 @@ function addRemediateResultSteps(evidence, cveId, report) {
|
|
|
4272
4653
|
{
|
|
4273
4654
|
resultCount: report.results.length,
|
|
4274
4655
|
vulnerableCount: report.vulnerablePackages.length,
|
|
4275
|
-
llmUsage: report.llmUsage
|
|
4656
|
+
llmUsage: report.llmUsage,
|
|
4657
|
+
exploitSignalTriggered: report.exploitSignalTriggered ?? false,
|
|
4658
|
+
slaBreachCount: report.slaBreaches?.length ?? 0,
|
|
4659
|
+
regressionDetectedCount: report.results.filter((r) => r.regressionDetected).length,
|
|
4660
|
+
sbomEntryCount: report.sbom?.length ?? 0
|
|
4276
4661
|
}
|
|
4277
4662
|
);
|
|
4278
4663
|
finalizeEvidence(evidence);
|
|
@@ -4381,12 +4766,12 @@ async function planRemediation(cveId, options = {}) {
|
|
|
4381
4766
|
}
|
|
4382
4767
|
|
|
4383
4768
|
// src/scanner/parse-input.ts
|
|
4384
|
-
import { extname } from "path";
|
|
4385
|
-
import { readFileSync as
|
|
4769
|
+
import { extname as extname2 } from "path";
|
|
4770
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
4386
4771
|
import { execa as execa11 } from "execa";
|
|
4387
4772
|
|
|
4388
4773
|
// src/scanner/adapters/npm-audit.ts
|
|
4389
|
-
import { readFileSync as
|
|
4774
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
4390
4775
|
var CVE_REGEX = /CVE-\d{4}-\d+/gi;
|
|
4391
4776
|
function normalizeSeverity(raw) {
|
|
4392
4777
|
if (!raw) return "UNKNOWN";
|
|
@@ -4420,12 +4805,12 @@ function parseNpmAuditJsonFromString(content) {
|
|
|
4420
4805
|
return findings;
|
|
4421
4806
|
}
|
|
4422
4807
|
function parseNpmAuditJsonFile(filePath) {
|
|
4423
|
-
const content =
|
|
4808
|
+
const content = readFileSync10(filePath, "utf8");
|
|
4424
4809
|
return parseNpmAuditJsonFromString(content);
|
|
4425
4810
|
}
|
|
4426
4811
|
|
|
4427
4812
|
// src/scanner/adapters/yarn-audit.ts
|
|
4428
|
-
import { readFileSync as
|
|
4813
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
4429
4814
|
var CVE_REGEX2 = /CVE-\d{4}-\d+/gi;
|
|
4430
4815
|
function normalizeSeverity2(raw) {
|
|
4431
4816
|
if (!raw) return "UNKNOWN";
|
|
@@ -4468,12 +4853,12 @@ function parseYarnAuditJsonFromString(content) {
|
|
|
4468
4853
|
return findings;
|
|
4469
4854
|
}
|
|
4470
4855
|
function parseYarnAuditJsonFile(filePath) {
|
|
4471
|
-
const content =
|
|
4856
|
+
const content = readFileSync11(filePath, "utf8");
|
|
4472
4857
|
return parseYarnAuditJsonFromString(content);
|
|
4473
4858
|
}
|
|
4474
4859
|
|
|
4475
4860
|
// src/scanner/adapters/sarif.ts
|
|
4476
|
-
import { readFileSync as
|
|
4861
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
4477
4862
|
var CVE_REGEX3 = /CVE-\d{4}-\d+/gi;
|
|
4478
4863
|
function extractPackageName(result) {
|
|
4479
4864
|
const pkg = result.properties?.["packageName"];
|
|
@@ -4505,7 +4890,7 @@ function parseSarifFromString(content) {
|
|
|
4505
4890
|
return findings;
|
|
4506
4891
|
}
|
|
4507
4892
|
function parseSarifFile(filePath) {
|
|
4508
|
-
const content =
|
|
4893
|
+
const content = readFileSync12(filePath, "utf8");
|
|
4509
4894
|
return parseSarifFromString(content);
|
|
4510
4895
|
}
|
|
4511
4896
|
|
|
@@ -4568,10 +4953,10 @@ function ensureAuditFormatCompatibility(pm, resolved) {
|
|
|
4568
4953
|
}
|
|
4569
4954
|
}
|
|
4570
4955
|
function inferFormat(filePath) {
|
|
4571
|
-
const ext =
|
|
4956
|
+
const ext = extname2(filePath).toLowerCase();
|
|
4572
4957
|
if (ext === ".sarif") return "sarif";
|
|
4573
4958
|
try {
|
|
4574
|
-
const content =
|
|
4959
|
+
const content = readFileSync13(filePath, "utf8");
|
|
4575
4960
|
const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
|
|
4576
4961
|
if (firstLine) {
|
|
4577
4962
|
const parsed = JSON.parse(firstLine);
|
|
@@ -4866,14 +5251,14 @@ async function remediateFromScan(inputPath, options = {}) {
|
|
|
4866
5251
|
}
|
|
4867
5252
|
|
|
4868
5253
|
// src/api/update-outdated/index.ts
|
|
4869
|
-
import { join as
|
|
4870
|
-
import { readFileSync as
|
|
5254
|
+
import { join as join17 } from "path";
|
|
5255
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
|
|
4871
5256
|
import { execa as execa12 } from "execa";
|
|
4872
5257
|
async function applyBump(params) {
|
|
4873
|
-
const pkgPath =
|
|
5258
|
+
const pkgPath = join17(params.cwd, "package.json");
|
|
4874
5259
|
let pkgJson;
|
|
4875
5260
|
try {
|
|
4876
|
-
pkgJson = JSON.parse(
|
|
5261
|
+
pkgJson = JSON.parse(readFileSync14(pkgPath, "utf8"));
|
|
4877
5262
|
} catch {
|
|
4878
5263
|
return {
|
|
4879
5264
|
applied: false,
|
|
@@ -5151,9 +5536,9 @@ function toSarifOutput(report) {
|
|
|
5151
5536
|
}
|
|
5152
5537
|
|
|
5153
5538
|
// src/version.ts
|
|
5154
|
-
import { readFileSync as
|
|
5539
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
5155
5540
|
function readPackageVersion() {
|
|
5156
|
-
const raw =
|
|
5541
|
+
const raw = readFileSync15(new URL("../package.json", import.meta.url), "utf8");
|
|
5157
5542
|
const metadata = JSON.parse(raw);
|
|
5158
5543
|
if (!metadata.version) {
|
|
5159
5544
|
throw new Error("packages/core/package.json is missing a version field.");
|
|
@@ -5180,4 +5565,4 @@ export {
|
|
|
5180
5565
|
updateOutdated,
|
|
5181
5566
|
PACKAGE_VERSION
|
|
5182
5567
|
};
|
|
5183
|
-
//# sourceMappingURL=chunk-
|
|
5568
|
+
//# sourceMappingURL=chunk-GYCZ6L3O.js.map
|