autoremediator 0.10.0 → 0.12.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-575GEUAY.js} +941 -206
- package/dist/chunk-575GEUAY.js.map +1 -0
- package/dist/cli.js +205 -16
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +127 -7
- package/dist/index.js +938 -203
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +51 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/openapi/server.js +86 -4
- package/dist/openapi/server.js.map +1 -1
- package/llms.txt +25 -4
- package/package.json +2 -2
- package/dist/chunk-F7W4EYJL.js.map +0 -1
|
@@ -40,8 +40,21 @@ var OPTION_DESCRIPTIONS = {
|
|
|
40
40
|
installPreferOffline: "Override prefer-offline flag behavior for install commands",
|
|
41
41
|
enforceFrozenLockfile: "Override frozen lockfile behavior for install commands",
|
|
42
42
|
workspace: "Workspace/package selector for scoped remediation in monorepos",
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
createChangeRequest: "Enable creation of native pull request / merge request after remediation",
|
|
44
|
+
changeRequestProvider: "Change request provider (github|gitlab)",
|
|
45
|
+
changeRequestGrouping: "Grouping strategy for change requests (all|per-cve|per-package)",
|
|
46
|
+
changeRequestRepository: "Repository slug override for change request creation",
|
|
47
|
+
changeRequestBaseBranch: "Base branch used for change request targeting",
|
|
48
|
+
changeRequestBranchPrefix: "Branch prefix for generated change request branches",
|
|
49
|
+
changeRequestTitlePrefix: "Title prefix for generated change requests",
|
|
50
|
+
includeTransitive: "Include transitive dependencies in the outdated check. Default: false.",
|
|
51
|
+
updateOutdated: "Run in update-outdated mode: bump all outdated npm packages without requiring a CVE.",
|
|
52
|
+
kevMandatory: "If true, CVEs with active CISA KEV status bypass severity filtering and are treated as mandatory",
|
|
53
|
+
epssThreshold: "EPSS probability threshold (0..1) above which a CVE is treated as mandatory regardless of severity",
|
|
54
|
+
suppressionsFile: "Path to a YAML file containing additional VEX suppression entries to merge with policy-inline suppressions",
|
|
55
|
+
slaCheck: "Compare CVE publication dates against configured SLA windows and include breach records in the report output",
|
|
56
|
+
skipUnreachable: "Skip remediation for CVEs where the vulnerable package cannot be reached from any project entry point (requires static import analysis)",
|
|
57
|
+
regressionCheck: "After applying a fix, verify the patched version falls outside the CVE's vulnerable range and flag any regression in the report"
|
|
45
58
|
};
|
|
46
59
|
function createConstraintSchemaProperties() {
|
|
47
60
|
return {
|
|
@@ -97,14 +110,41 @@ function createRemediateOptionSchemaProperties(options) {
|
|
|
97
110
|
constraints: {
|
|
98
111
|
type: "object",
|
|
99
112
|
properties: createConstraintSchemaProperties()
|
|
100
|
-
}
|
|
113
|
+
},
|
|
114
|
+
changeRequest: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
enabled: { type: "boolean", description: OPTION_DESCRIPTIONS.createChangeRequest },
|
|
118
|
+
provider: {
|
|
119
|
+
type: "string",
|
|
120
|
+
enum: ["github", "gitlab"],
|
|
121
|
+
description: OPTION_DESCRIPTIONS.changeRequestProvider
|
|
122
|
+
},
|
|
123
|
+
grouping: {
|
|
124
|
+
type: "string",
|
|
125
|
+
enum: ["all", "per-cve", "per-package"],
|
|
126
|
+
description: OPTION_DESCRIPTIONS.changeRequestGrouping
|
|
127
|
+
},
|
|
128
|
+
repository: { type: "string", description: OPTION_DESCRIPTIONS.changeRequestRepository },
|
|
129
|
+
baseBranch: { type: "string", description: OPTION_DESCRIPTIONS.changeRequestBaseBranch },
|
|
130
|
+
branchPrefix: { type: "string", description: OPTION_DESCRIPTIONS.changeRequestBranchPrefix },
|
|
131
|
+
titlePrefix: { type: "string", description: OPTION_DESCRIPTIONS.changeRequestTitlePrefix }
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
kevMandatory: { type: "boolean", description: OPTION_DESCRIPTIONS.kevMandatory },
|
|
135
|
+
epssThreshold: { type: "number", minimum: 0, maximum: 1, description: OPTION_DESCRIPTIONS.epssThreshold },
|
|
136
|
+
suppressionsFile: { type: "string", description: OPTION_DESCRIPTIONS.suppressionsFile },
|
|
137
|
+
slaCheck: { type: "boolean", description: OPTION_DESCRIPTIONS.slaCheck },
|
|
138
|
+
skipUnreachable: { type: "boolean", description: OPTION_DESCRIPTIONS.skipUnreachable },
|
|
139
|
+
regressionCheck: { type: "boolean", description: OPTION_DESCRIPTIONS.regressionCheck }
|
|
101
140
|
};
|
|
102
141
|
}
|
|
103
142
|
function createScanOptionSchemaProperties() {
|
|
104
143
|
return {
|
|
105
144
|
...createRemediateOptionSchemaProperties({ includeEvidence: true }),
|
|
106
145
|
format: { type: "string", enum: ["npm-audit", "yarn-audit", "sarif", "auto"], description: OPTION_DESCRIPTIONS.format },
|
|
107
|
-
audit: { type: "boolean", description: OPTION_DESCRIPTIONS.audit }
|
|
146
|
+
audit: { type: "boolean", description: OPTION_DESCRIPTIONS.audit },
|
|
147
|
+
slaCheck: { type: "boolean", description: OPTION_DESCRIPTIONS.slaCheck }
|
|
108
148
|
};
|
|
109
149
|
}
|
|
110
150
|
function createScanReportSchemaProperties() {
|
|
@@ -139,7 +179,8 @@ function createScanReportSchemaProperties() {
|
|
|
139
179
|
idempotencyKey: { type: "string" },
|
|
140
180
|
llmUsageCount: { type: "number" },
|
|
141
181
|
estimatedCostUsd: { type: "number" },
|
|
142
|
-
totalLlmLatencyMs: { type: "number" }
|
|
182
|
+
totalLlmLatencyMs: { type: "number" },
|
|
183
|
+
changeRequests: { type: "array", items: { type: "object" } }
|
|
143
184
|
};
|
|
144
185
|
}
|
|
145
186
|
function createUpdateOutdatedOptionSchemaProperties() {
|
|
@@ -464,8 +505,13 @@ async function inspectPatchArtifact(patchFilePath, options = {}) {
|
|
|
464
505
|
};
|
|
465
506
|
}
|
|
466
507
|
|
|
467
|
-
// src/remediation/tools/
|
|
508
|
+
// src/remediation/tools/tool-compat.ts
|
|
468
509
|
import { tool } from "ai";
|
|
510
|
+
function defineTool(config) {
|
|
511
|
+
return tool(config);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// src/remediation/tools/check-inventory.ts
|
|
469
515
|
import { z } from "zod";
|
|
470
516
|
import { readFileSync as readFileSync3 } from "fs";
|
|
471
517
|
import { join as join5 } from "path";
|
|
@@ -494,7 +540,11 @@ var DEFAULT_POLICY = {
|
|
|
494
540
|
consensusModel: void 0,
|
|
495
541
|
patchConfidenceThresholds: {},
|
|
496
542
|
dynamicModelRouting: false,
|
|
497
|
-
dynamicRoutingThresholdChars: 18e3
|
|
543
|
+
dynamicRoutingThresholdChars: 18e3,
|
|
544
|
+
exploitSignalOverride: void 0,
|
|
545
|
+
suppressions: [],
|
|
546
|
+
sla: void 0,
|
|
547
|
+
skipUnreachable: false
|
|
498
548
|
};
|
|
499
549
|
function loadPolicy(cwd, explicitPath) {
|
|
500
550
|
const candidate = explicitPath ?? join4(cwd, ".github", "autoremediator.yml");
|
|
@@ -527,7 +577,19 @@ function loadPolicy(cwd, explicitPath) {
|
|
|
527
577
|
high: parsed.patchConfidenceThresholds?.high ?? DEFAULT_POLICY.patchConfidenceThresholds?.high
|
|
528
578
|
},
|
|
529
579
|
dynamicModelRouting: parsed.dynamicModelRouting ?? DEFAULT_POLICY.dynamicModelRouting,
|
|
530
|
-
dynamicRoutingThresholdChars: parsed.dynamicRoutingThresholdChars ?? DEFAULT_POLICY.dynamicRoutingThresholdChars
|
|
580
|
+
dynamicRoutingThresholdChars: parsed.dynamicRoutingThresholdChars ?? DEFAULT_POLICY.dynamicRoutingThresholdChars,
|
|
581
|
+
exploitSignalOverride: parsed.exploitSignalOverride ? {
|
|
582
|
+
kev: parsed.exploitSignalOverride.kev ?? void 0,
|
|
583
|
+
epss: parsed.exploitSignalOverride.epss ?? void 0
|
|
584
|
+
} : void 0,
|
|
585
|
+
suppressions: Array.isArray(parsed.suppressions) ? parsed.suppressions : DEFAULT_POLICY.suppressions,
|
|
586
|
+
sla: parsed.sla ? {
|
|
587
|
+
critical: parsed.sla.critical,
|
|
588
|
+
high: parsed.sla.high,
|
|
589
|
+
medium: parsed.sla.medium,
|
|
590
|
+
low: parsed.sla.low
|
|
591
|
+
} : void 0,
|
|
592
|
+
skipUnreachable: parsed.skipUnreachable ?? DEFAULT_POLICY.skipUnreachable
|
|
531
593
|
};
|
|
532
594
|
} catch {
|
|
533
595
|
return DEFAULT_POLICY;
|
|
@@ -540,9 +602,40 @@ function isPackageAllowed(policy, packageName) {
|
|
|
540
602
|
}
|
|
541
603
|
return true;
|
|
542
604
|
}
|
|
605
|
+
function isActiveSuppression(suppression) {
|
|
606
|
+
if (!suppression.expiresAt) return true;
|
|
607
|
+
return new Date(suppression.expiresAt) > /* @__PURE__ */ new Date();
|
|
608
|
+
}
|
|
609
|
+
function loadSuppressionsFile(filePath) {
|
|
610
|
+
try {
|
|
611
|
+
const content = readFileSync2(filePath, "utf8");
|
|
612
|
+
const parsed = yamlParse(content);
|
|
613
|
+
return Array.isArray(parsed?.suppressions) ? parsed.suppressions : [];
|
|
614
|
+
} catch {
|
|
615
|
+
return [];
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
|
|
619
|
+
const severityKey = severity.toLowerCase();
|
|
620
|
+
const deadlineHours = slaPolicy[severityKey];
|
|
621
|
+
if (typeof deadlineHours !== "number") return null;
|
|
622
|
+
const publishedMs = new Date(publishedAt).getTime();
|
|
623
|
+
if (isNaN(publishedMs)) return null;
|
|
624
|
+
const deadlineMs = publishedMs + deadlineHours * 60 * 60 * 1e3;
|
|
625
|
+
const nowMs = Date.now();
|
|
626
|
+
if (nowMs <= deadlineMs) return null;
|
|
627
|
+
const hoursOverdue = Math.round((nowMs - deadlineMs) / (60 * 60 * 1e3));
|
|
628
|
+
return {
|
|
629
|
+
cveId,
|
|
630
|
+
severity,
|
|
631
|
+
publishedAt,
|
|
632
|
+
deadlineAt: new Date(deadlineMs).toISOString(),
|
|
633
|
+
hoursOverdue
|
|
634
|
+
};
|
|
635
|
+
}
|
|
543
636
|
|
|
544
637
|
// src/remediation/tools/check-inventory.ts
|
|
545
|
-
var checkInventoryTool =
|
|
638
|
+
var checkInventoryTool = defineTool({
|
|
546
639
|
description: "Read the project's package.json and installed dependencies to list packages and exact versions. Must be called before checking version matches.",
|
|
547
640
|
parameters: z.object({
|
|
548
641
|
cwd: z.string().describe("Absolute path to the consumer project's root directory"),
|
|
@@ -583,7 +676,7 @@ var checkInventoryTool = tool({
|
|
|
583
676
|
packages.push({
|
|
584
677
|
name,
|
|
585
678
|
version,
|
|
586
|
-
type: isDirect ? "direct" : "
|
|
679
|
+
type: isDirect ? "direct" : "transitive"
|
|
587
680
|
});
|
|
588
681
|
}
|
|
589
682
|
if (packages.length === 0) {
|
|
@@ -864,6 +957,9 @@ function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
|
|
|
864
957
|
// src/remediation/pipeline.ts
|
|
865
958
|
import { generateText as generateText2 } from "ai";
|
|
866
959
|
|
|
960
|
+
// src/remediation/local/run.ts
|
|
961
|
+
import semver5 from "semver";
|
|
962
|
+
|
|
867
963
|
// src/platform/http-client.ts
|
|
868
964
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
869
965
|
async function requestWithTimeout(url, init, timeoutMs) {
|
|
@@ -1124,9 +1220,214 @@ async function enrichWithNvd(details) {
|
|
|
1124
1220
|
return details;
|
|
1125
1221
|
}
|
|
1126
1222
|
|
|
1223
|
+
// src/remediation/tools/check-reachability.ts
|
|
1224
|
+
import { z as z2 } from "zod";
|
|
1225
|
+
import { readdirSync, readFileSync as readFileSync5, statSync } from "fs";
|
|
1226
|
+
import { join as join7, extname } from "path";
|
|
1227
|
+
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
1228
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", ".git", "coverage", ".cache"]);
|
|
1229
|
+
var MAX_FILES = 500;
|
|
1230
|
+
function collectSourceFiles(dir, files = []) {
|
|
1231
|
+
if (files.length >= MAX_FILES) return files;
|
|
1232
|
+
let entries;
|
|
1233
|
+
try {
|
|
1234
|
+
entries = readdirSync(dir);
|
|
1235
|
+
} catch {
|
|
1236
|
+
return files;
|
|
1237
|
+
}
|
|
1238
|
+
for (const entry of entries) {
|
|
1239
|
+
if (files.length >= MAX_FILES) break;
|
|
1240
|
+
const full = join7(dir, entry);
|
|
1241
|
+
let stat2;
|
|
1242
|
+
try {
|
|
1243
|
+
stat2 = statSync(full);
|
|
1244
|
+
} catch {
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
if (stat2.isDirectory()) {
|
|
1248
|
+
if (!SKIP_DIRS.has(entry)) collectSourceFiles(full, files);
|
|
1249
|
+
} else if (SOURCE_EXTENSIONS.has(extname(entry))) {
|
|
1250
|
+
files.push(full);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
return files;
|
|
1254
|
+
}
|
|
1255
|
+
function assessPackageReachability(cwd, packageName) {
|
|
1256
|
+
const files = collectSourceFiles(cwd);
|
|
1257
|
+
if (files.length === 0) {
|
|
1258
|
+
return {
|
|
1259
|
+
packageName,
|
|
1260
|
+
status: "unknown",
|
|
1261
|
+
reason: "No source files found to scan."
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1265
|
+
const pattern = new RegExp(
|
|
1266
|
+
`(?:import\\s[^'"]*from\\s['"]${escaped}(?:/[^'"]*)?['"]|require\\(['"]${escaped}(?:/[^'"]*)?['"]\\)|from\\s['"]${escaped}(?:/[^'"]*)?['"])`,
|
|
1267
|
+
"m"
|
|
1268
|
+
);
|
|
1269
|
+
const evidence = [];
|
|
1270
|
+
for (const filePath of files) {
|
|
1271
|
+
let content;
|
|
1272
|
+
try {
|
|
1273
|
+
content = readFileSync5(filePath, "utf8");
|
|
1274
|
+
} catch {
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
if (pattern.test(content)) {
|
|
1278
|
+
const relative = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
1279
|
+
const matchType = content.includes(`require('${packageName}`) || content.includes(`require("${packageName}`) ? "require" : content.includes(`import(`) ? "dynamic-import" : "import";
|
|
1280
|
+
evidence.push({ filePath: relative, matchType });
|
|
1281
|
+
if (evidence.length >= 3) break;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (evidence.length > 0) {
|
|
1285
|
+
return {
|
|
1286
|
+
packageName,
|
|
1287
|
+
status: "reachable",
|
|
1288
|
+
reason: `Found ${evidence.length} import reference(s) in source files.`,
|
|
1289
|
+
reachabilityBasis: "import-present",
|
|
1290
|
+
evidence
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
packageName,
|
|
1295
|
+
status: "not-reachable",
|
|
1296
|
+
reason: `No import or require of '${packageName}' found in ${files.length} source file(s).`
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
var checkReachabilityTool = defineTool({
|
|
1300
|
+
description: "Assess whether an npm package is statically reachable (imported) from the project's source files. Returns reachable, not-reachable, or unknown.",
|
|
1301
|
+
parameters: z2.object({
|
|
1302
|
+
cwd: z2.string().describe("Project root directory"),
|
|
1303
|
+
packageName: z2.string().describe("The npm package name to search for")
|
|
1304
|
+
}),
|
|
1305
|
+
execute: async ({ cwd, packageName }) => {
|
|
1306
|
+
return assessPackageReachability(cwd, packageName);
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
// src/remediation/tools/check-exploit-signal.ts
|
|
1311
|
+
import { z as z3 } from "zod";
|
|
1312
|
+
var kevSchema = z3.object({
|
|
1313
|
+
knownExploited: z3.boolean(),
|
|
1314
|
+
dateAdded: z3.string().optional(),
|
|
1315
|
+
dueDate: z3.string().optional(),
|
|
1316
|
+
requiredAction: z3.string().optional(),
|
|
1317
|
+
knownRansomwareCampaignUse: z3.string().optional()
|
|
1318
|
+
}).passthrough();
|
|
1319
|
+
var epssSchema = z3.object({
|
|
1320
|
+
score: z3.number(),
|
|
1321
|
+
percentile: z3.number(),
|
|
1322
|
+
date: z3.string().optional()
|
|
1323
|
+
}).passthrough();
|
|
1324
|
+
var exploitSignalPolicySchema = z3.object({
|
|
1325
|
+
kev: z3.object({ mandatory: z3.boolean() }).optional(),
|
|
1326
|
+
epss: z3.object({ mandatory: z3.boolean(), threshold: z3.number() }).optional()
|
|
1327
|
+
}).optional();
|
|
1328
|
+
var checkExploitSignalTool = defineTool({
|
|
1329
|
+
description: "Evaluate KEV and EPSS exploit signals for a CVE against policy thresholds. Returns whether the exploit signal overrides severity filtering (mandatory gate).",
|
|
1330
|
+
parameters: z3.object({
|
|
1331
|
+
cveDetails: z3.object({
|
|
1332
|
+
id: z3.string(),
|
|
1333
|
+
kev: kevSchema.optional(),
|
|
1334
|
+
epss: epssSchema.optional()
|
|
1335
|
+
}).passthrough().describe("CveDetails object from lookup-cve"),
|
|
1336
|
+
policy: z3.object({
|
|
1337
|
+
exploitSignalOverride: exploitSignalPolicySchema
|
|
1338
|
+
}).passthrough().describe("Policy object containing exploitSignalOverride configuration")
|
|
1339
|
+
}),
|
|
1340
|
+
execute: async ({ cveDetails, policy }) => {
|
|
1341
|
+
const override = policy.exploitSignalOverride;
|
|
1342
|
+
if (!override) {
|
|
1343
|
+
return {
|
|
1344
|
+
exploitSignalTriggered: false,
|
|
1345
|
+
reason: "No exploitSignalOverride policy configured."
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
if (override.kev?.mandatory && cveDetails.kev?.knownExploited === true) {
|
|
1349
|
+
return {
|
|
1350
|
+
exploitSignalTriggered: true,
|
|
1351
|
+
reason: `CVE ${cveDetails.id} is in the CISA Known Exploited Vulnerabilities (KEV) catalog and kev.mandatory is enabled.`
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
if (override.epss?.mandatory && typeof override.epss.threshold === "number" && typeof cveDetails.epss?.score === "number" && cveDetails.epss.score >= override.epss.threshold) {
|
|
1355
|
+
return {
|
|
1356
|
+
exploitSignalTriggered: true,
|
|
1357
|
+
reason: `CVE ${cveDetails.id} EPSS score ${cveDetails.epss.score} meets or exceeds threshold ${override.epss.threshold} and epss.mandatory is enabled.`
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
return {
|
|
1361
|
+
exploitSignalTriggered: false,
|
|
1362
|
+
reason: "Exploit signal thresholds not met."
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
// src/remediation/local/secops-preflight.ts
|
|
1368
|
+
async function runSecOpsPreflight(normalizedId, cveDetails, opts) {
|
|
1369
|
+
const allSuppressions = opts.suppressionsFile ? [...opts.suppressions, ...loadSuppressionsFile(opts.suppressionsFile)] : opts.suppressions;
|
|
1370
|
+
const activeSuppression = allSuppressions.find(
|
|
1371
|
+
(s) => s.cveId === normalizedId && isActiveSuppression(s)
|
|
1372
|
+
);
|
|
1373
|
+
if (activeSuppression) {
|
|
1374
|
+
return {
|
|
1375
|
+
suppressed: true,
|
|
1376
|
+
summary: `CVE ${normalizedId} suppressed by VEX policy: ${activeSuppression.justification}${activeSuppression.notes ? ` \u2014 ${activeSuppression.notes}` : ""}`
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
let exploitSignalTriggered;
|
|
1380
|
+
if (opts.exploitSignalOverride) {
|
|
1381
|
+
const result = await checkExploitSignalTool.execute({
|
|
1382
|
+
cveDetails,
|
|
1383
|
+
policy: { exploitSignalOverride: opts.exploitSignalOverride }
|
|
1384
|
+
});
|
|
1385
|
+
if (result.exploitSignalTriggered) {
|
|
1386
|
+
exploitSignalTriggered = true;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
let slaBreaches;
|
|
1390
|
+
if (opts.slaCheck && opts.slaPolicy && cveDetails.publishedAt) {
|
|
1391
|
+
const breach = checkSlaBreach(normalizedId, cveDetails.severity, cveDetails.publishedAt, opts.slaPolicy);
|
|
1392
|
+
if (breach) {
|
|
1393
|
+
slaBreaches = [breach];
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
return { suppressed: false, exploitSignalTriggered, slaBreaches };
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// src/remediation/local/sbom.ts
|
|
1400
|
+
function buildSbom(packages, vulnerableNames, results) {
|
|
1401
|
+
const statusByPackage = /* @__PURE__ */ new Map();
|
|
1402
|
+
for (const result of results) {
|
|
1403
|
+
if (!vulnerableNames.has(result.packageName)) continue;
|
|
1404
|
+
if (result.suppressedBy) {
|
|
1405
|
+
statusByPackage.set(result.packageName, "suppressed");
|
|
1406
|
+
} else if (!result.applied && result.strategy === "none") {
|
|
1407
|
+
statusByPackage.set(result.packageName, "skipped");
|
|
1408
|
+
} else if (result.applied) {
|
|
1409
|
+
statusByPackage.set(result.packageName, "patched");
|
|
1410
|
+
} else {
|
|
1411
|
+
statusByPackage.set(result.packageName, "unpatched");
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
return packages.map((pkg) => {
|
|
1415
|
+
const isVulnerable = vulnerableNames.has(pkg.name);
|
|
1416
|
+
const entry = {
|
|
1417
|
+
name: pkg.name,
|
|
1418
|
+
version: pkg.version,
|
|
1419
|
+
scope: pkg.type === "direct" ? "direct" : "transitive"
|
|
1420
|
+
};
|
|
1421
|
+
if (isVulnerable) {
|
|
1422
|
+
entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
|
|
1423
|
+
}
|
|
1424
|
+
return entry;
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1127
1428
|
// src/intelligence/sources/registry.ts
|
|
1128
|
-
import { readFileSync as
|
|
1129
|
-
import { join as
|
|
1429
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
1430
|
+
import { join as join8 } from "path";
|
|
1130
1431
|
import { execa as execa4 } from "execa";
|
|
1131
1432
|
import semver from "semver";
|
|
1132
1433
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
@@ -1295,7 +1596,7 @@ async function queryOutdatedPackages(cwd, options = {}) {
|
|
|
1295
1596
|
}
|
|
1296
1597
|
let directDeps;
|
|
1297
1598
|
try {
|
|
1298
|
-
const pkgRaw = JSON.parse(
|
|
1599
|
+
const pkgRaw = JSON.parse(readFileSync6(join8(cwd, "package.json"), "utf8"));
|
|
1299
1600
|
directDeps = /* @__PURE__ */ new Set([
|
|
1300
1601
|
...Object.keys(pkgRaw.dependencies ?? {}),
|
|
1301
1602
|
...Object.keys(pkgRaw.devDependencies ?? {})
|
|
@@ -1309,8 +1610,8 @@ async function queryOutdatedPackages(cwd, options = {}) {
|
|
|
1309
1610
|
const wantedVersion = entry.wanted ?? currentVersion;
|
|
1310
1611
|
const latestVersion = entry.latest ?? currentVersion;
|
|
1311
1612
|
if (!currentVersion || !latestVersion) continue;
|
|
1312
|
-
const dependencyScope = directDeps.has(name) ? "direct" : "
|
|
1313
|
-
if (!options.includeTransitive && dependencyScope === "
|
|
1613
|
+
const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
|
|
1614
|
+
if (!options.includeTransitive && dependencyScope === "transitive") continue;
|
|
1314
1615
|
const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
|
|
1315
1616
|
result.set(name, {
|
|
1316
1617
|
currentVersion,
|
|
@@ -1324,24 +1625,23 @@ async function queryOutdatedPackages(cwd, options = {}) {
|
|
|
1324
1625
|
}
|
|
1325
1626
|
|
|
1326
1627
|
// src/remediation/tools/apply-version-bump.ts
|
|
1327
|
-
import {
|
|
1328
|
-
import {
|
|
1329
|
-
import {
|
|
1330
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
1628
|
+
import { z as z4 } from "zod";
|
|
1629
|
+
import { join as join10 } from "path";
|
|
1630
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
|
|
1331
1631
|
import { execa as execa5 } from "execa";
|
|
1332
1632
|
import semver2 from "semver";
|
|
1333
1633
|
|
|
1334
1634
|
// src/platform/repo-lock.ts
|
|
1335
1635
|
import { mkdir, rm } from "fs/promises";
|
|
1336
|
-
import { join as
|
|
1636
|
+
import { join as join9 } from "path";
|
|
1337
1637
|
async function sleep(ms) {
|
|
1338
1638
|
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1339
1639
|
}
|
|
1340
1640
|
async function acquireRepoLock(cwd, options = {}) {
|
|
1341
1641
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
1342
1642
|
const retryDelayMs = options.retryDelayMs ?? 125;
|
|
1343
|
-
const lockRoot =
|
|
1344
|
-
const lockPath =
|
|
1643
|
+
const lockRoot = join9(cwd, ".autoremediator", "locks");
|
|
1644
|
+
const lockPath = join9(cwd, ".autoremediator", "locks", "remediation.lock");
|
|
1345
1645
|
const startedAt = Date.now();
|
|
1346
1646
|
await mkdir(lockRoot, { recursive: true });
|
|
1347
1647
|
while (true) {
|
|
@@ -1371,21 +1671,21 @@ async function withRepoLock(cwd, fn, options) {
|
|
|
1371
1671
|
}
|
|
1372
1672
|
|
|
1373
1673
|
// src/remediation/tools/apply-version-bump.ts
|
|
1374
|
-
var applyVersionBumpTool =
|
|
1674
|
+
var applyVersionBumpTool = defineTool({
|
|
1375
1675
|
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:
|
|
1676
|
+
parameters: z4.object({
|
|
1677
|
+
cwd: z4.string().describe("Absolute path to the consumer project root"),
|
|
1678
|
+
packageManager: z4.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
|
|
1679
|
+
packageName: z4.string().describe("The npm package to upgrade"),
|
|
1680
|
+
fromVersion: z4.string().describe("The currently installed vulnerable version"),
|
|
1681
|
+
toVersion: z4.string().describe("The safe target version to upgrade to"),
|
|
1682
|
+
dryRun: z4.boolean().default(false).describe("If true, report changes but do not write"),
|
|
1683
|
+
policy: z4.string().optional().describe("Optional path to .autoremediator policy file"),
|
|
1684
|
+
runTests: z4.boolean().default(false).describe("If true, run test validation after applying the fix"),
|
|
1685
|
+
installMode: z4.enum(["standard", "prefer-offline", "deterministic"]).optional(),
|
|
1686
|
+
installPreferOffline: z4.boolean().optional(),
|
|
1687
|
+
enforceFrozenLockfile: z4.boolean().optional(),
|
|
1688
|
+
workspace: z4.string().optional()
|
|
1389
1689
|
}),
|
|
1390
1690
|
execute: async ({
|
|
1391
1691
|
cwd,
|
|
@@ -1402,7 +1702,7 @@ var applyVersionBumpTool = tool2({
|
|
|
1402
1702
|
workspace
|
|
1403
1703
|
}) => {
|
|
1404
1704
|
const pm = packageManager ?? detectPackageManager(cwd);
|
|
1405
|
-
const pkgPath =
|
|
1705
|
+
const pkgPath = join10(cwd, "package.json");
|
|
1406
1706
|
const loadedPolicy = loadPolicy(cwd, policy);
|
|
1407
1707
|
const commandConstraints = {
|
|
1408
1708
|
...loadedPolicy.constraints,
|
|
@@ -1442,7 +1742,7 @@ var applyVersionBumpTool = tool2({
|
|
|
1442
1742
|
}
|
|
1443
1743
|
let pkgJson;
|
|
1444
1744
|
try {
|
|
1445
|
-
pkgJson = JSON.parse(
|
|
1745
|
+
pkgJson = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
1446
1746
|
} catch {
|
|
1447
1747
|
return {
|
|
1448
1748
|
packageName,
|
|
@@ -1464,8 +1764,8 @@ var applyVersionBumpTool = tool2({
|
|
|
1464
1764
|
fromVersion,
|
|
1465
1765
|
applied: false,
|
|
1466
1766
|
dryRun,
|
|
1467
|
-
unresolvedReason: "
|
|
1468
|
-
message: `"${packageName}" was not found in package.json dependencies (it may be a transitive
|
|
1767
|
+
unresolvedReason: "transitive-dependency",
|
|
1768
|
+
message: `"${packageName}" was not found in package.json dependencies (it may be a transitive dependency). Cannot auto-bump.`
|
|
1469
1769
|
};
|
|
1470
1770
|
}
|
|
1471
1771
|
const currentRange = pkgJson[depField][packageName];
|
|
@@ -1565,10 +1865,9 @@ var applyVersionBumpTool = tool2({
|
|
|
1565
1865
|
});
|
|
1566
1866
|
|
|
1567
1867
|
// src/remediation/tools/apply-package-override/index.ts
|
|
1568
|
-
import {
|
|
1569
|
-
import {
|
|
1570
|
-
import {
|
|
1571
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
1868
|
+
import { z as z5 } from "zod";
|
|
1869
|
+
import { join as join11 } from "path";
|
|
1870
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
1572
1871
|
import { execa as execa7 } from "execa";
|
|
1573
1872
|
import semver3 from "semver";
|
|
1574
1873
|
|
|
@@ -1648,22 +1947,22 @@ function restoreRecord(record, key, previousValue) {
|
|
|
1648
1947
|
}
|
|
1649
1948
|
|
|
1650
1949
|
// src/remediation/tools/apply-package-override/index.ts
|
|
1651
|
-
var applyPackageOverrideTool =
|
|
1950
|
+
var applyPackageOverrideTool = defineTool({
|
|
1652
1951
|
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:
|
|
1952
|
+
parameters: z5.object({
|
|
1953
|
+
cwd: z5.string().describe("Absolute path to the consumer project root"),
|
|
1954
|
+
packageManager: z5.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
|
|
1955
|
+
packageName: z5.string().describe("The npm package to override"),
|
|
1956
|
+
selector: z5.string().optional().describe("Optional manager-native override selector key (for nested or scoped overrides)"),
|
|
1957
|
+
fromVersion: z5.string().describe("The currently installed vulnerable version"),
|
|
1958
|
+
toVersion: z5.string().describe("The safe target version to override to"),
|
|
1959
|
+
dryRun: z5.boolean().default(false).describe("If true, report changes but do not write"),
|
|
1960
|
+
policy: z5.string().optional().describe("Optional path to .autoremediator policy file"),
|
|
1961
|
+
runTests: z5.boolean().default(false).describe("If true, run test validation after applying the override"),
|
|
1962
|
+
installMode: z5.enum(["standard", "prefer-offline", "deterministic"]).optional(),
|
|
1963
|
+
installPreferOffline: z5.boolean().optional(),
|
|
1964
|
+
enforceFrozenLockfile: z5.boolean().optional(),
|
|
1965
|
+
workspace: z5.string().optional()
|
|
1667
1966
|
}),
|
|
1668
1967
|
execute: async ({
|
|
1669
1968
|
cwd,
|
|
@@ -1681,7 +1980,7 @@ var applyPackageOverrideTool = tool3({
|
|
|
1681
1980
|
workspace
|
|
1682
1981
|
}) => {
|
|
1683
1982
|
const pm = packageManager ?? detectPackageManager(cwd);
|
|
1684
|
-
const pkgPath =
|
|
1983
|
+
const pkgPath = join11(cwd, "package.json");
|
|
1685
1984
|
const loadedPolicy = loadPolicy(cwd, policy);
|
|
1686
1985
|
const commandConstraints = {
|
|
1687
1986
|
...loadedPolicy.constraints,
|
|
@@ -1722,7 +2021,7 @@ var applyPackageOverrideTool = tool3({
|
|
|
1722
2021
|
}
|
|
1723
2022
|
let pkgJson;
|
|
1724
2023
|
try {
|
|
1725
|
-
pkgJson = JSON.parse(
|
|
2024
|
+
pkgJson = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
1726
2025
|
} catch {
|
|
1727
2026
|
return {
|
|
1728
2027
|
packageName,
|
|
@@ -1822,7 +2121,7 @@ async function resolvePrimaryResult(params) {
|
|
|
1822
2121
|
const { vulnerable, cwd, packageManager, dryRun, policy, runTests, constraints } = params;
|
|
1823
2122
|
const pkg = vulnerable.installed;
|
|
1824
2123
|
const firstPatchedVersion = vulnerable.affected.firstPatchedVersion;
|
|
1825
|
-
if (pkg.type === "
|
|
2124
|
+
if (pkg.type === "transitive") {
|
|
1826
2125
|
if (constraints.directDependenciesOnly) {
|
|
1827
2126
|
return {
|
|
1828
2127
|
steps: 0,
|
|
@@ -1833,7 +2132,7 @@ async function resolvePrimaryResult(params) {
|
|
|
1833
2132
|
applied: false,
|
|
1834
2133
|
dryRun,
|
|
1835
2134
|
unresolvedReason: "constraint-blocked",
|
|
1836
|
-
message: `Constraint blocked remediation for
|
|
2135
|
+
message: `Constraint blocked remediation for transitive dependency "${pkg.name}".`
|
|
1837
2136
|
}
|
|
1838
2137
|
};
|
|
1839
2138
|
}
|
|
@@ -1959,17 +2258,16 @@ async function resolvePrimaryResult(params) {
|
|
|
1959
2258
|
}
|
|
1960
2259
|
|
|
1961
2260
|
// src/remediation/tools/fetch-package-source.ts
|
|
1962
|
-
import {
|
|
1963
|
-
import { z as z4 } from "zod";
|
|
2261
|
+
import { z as z6 } from "zod";
|
|
1964
2262
|
import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, rm as rm2 } from "fs/promises";
|
|
1965
|
-
import { join as
|
|
2263
|
+
import { join as join12 } from "path";
|
|
1966
2264
|
import { execa as execa8 } from "execa";
|
|
1967
|
-
var fetchPackageSourceTool =
|
|
2265
|
+
var fetchPackageSourceTool = defineTool({
|
|
1968
2266
|
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:
|
|
2267
|
+
parameters: z6.object({
|
|
2268
|
+
packageName: z6.string().min(1).describe("The npm package name (e.g., 'lodash', '@scope/package')"),
|
|
2269
|
+
version: z6.string().regex(/^\d+\.\d+\.\d+/, "Must be a valid semver version").describe("Exact package version to download"),
|
|
2270
|
+
filePatterns: z6.array(z6.string()).optional().default(["*.js", "*.ts"]).describe(
|
|
1973
2271
|
"File patterns to extract (glob patterns, default: *.js, *.ts)"
|
|
1974
2272
|
)
|
|
1975
2273
|
}),
|
|
@@ -1979,23 +2277,23 @@ var fetchPackageSourceTool = tool4({
|
|
|
1979
2277
|
filePatterns
|
|
1980
2278
|
}) => {
|
|
1981
2279
|
const tempBaseDir = `/tmp/autoremediator-pkg-${Date.now()}`;
|
|
1982
|
-
const extractDir =
|
|
2280
|
+
const extractDir = join12(tempBaseDir, "out");
|
|
1983
2281
|
try {
|
|
1984
2282
|
const npmUrl = `https://registry.npmjs.org/${packageName}/-/${packageName.split("/").pop()}-${version}.tgz`;
|
|
1985
2283
|
await mkdir2(tempBaseDir, { recursive: true });
|
|
1986
|
-
const tarballPath =
|
|
2284
|
+
const tarballPath = join12(tempBaseDir, "package.tgz");
|
|
1987
2285
|
await execa8("curl", ["-L", "-o", tarballPath, npmUrl]);
|
|
1988
2286
|
await mkdir2(extractDir, { recursive: true });
|
|
1989
2287
|
await execa8("tar", ["-xzf", tarballPath, "-C", extractDir]);
|
|
1990
2288
|
const extractedContents = await readdir2(extractDir);
|
|
1991
|
-
const packageRootDir = extractedContents.includes("package") ?
|
|
2289
|
+
const packageRootDir = extractedContents.includes("package") ? join12(extractDir, "package") : extractDir;
|
|
1992
2290
|
const sourceCode = {};
|
|
1993
2291
|
async function walkDir(dir, relativeBase) {
|
|
1994
2292
|
try {
|
|
1995
2293
|
const files = await readdir2(dir, { withFileTypes: true });
|
|
1996
2294
|
for (const file of files) {
|
|
1997
|
-
const fullPath =
|
|
1998
|
-
const relPath =
|
|
2295
|
+
const fullPath = join12(dir, file.name);
|
|
2296
|
+
const relPath = join12(relativeBase, file.name);
|
|
1999
2297
|
if (file.isDirectory()) {
|
|
2000
2298
|
if (![
|
|
2001
2299
|
"node_modules",
|
|
@@ -2058,8 +2356,7 @@ var fetchPackageSourceTool = tool4({
|
|
|
2058
2356
|
});
|
|
2059
2357
|
|
|
2060
2358
|
// src/remediation/tools/generate-patch/index.ts
|
|
2061
|
-
import {
|
|
2062
|
-
import { z as z5 } from "zod";
|
|
2359
|
+
import { z as z7 } from "zod";
|
|
2063
2360
|
import { generateText } from "ai";
|
|
2064
2361
|
|
|
2065
2362
|
// src/remediation/strategies/patch-synthesis-prompt.ts
|
|
@@ -2192,31 +2489,31 @@ function generateUnifiedDiff(original, fixed, filePath) {
|
|
|
2192
2489
|
}
|
|
2193
2490
|
|
|
2194
2491
|
// src/remediation/tools/generate-patch/index.ts
|
|
2195
|
-
var generatePatchTool =
|
|
2492
|
+
var generatePatchTool = defineTool({
|
|
2196
2493
|
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:
|
|
2494
|
+
parameters: z7.object({
|
|
2495
|
+
packageName: z7.string().min(1).describe("The npm package name"),
|
|
2496
|
+
vulnerableVersion: z7.string().describe("The vulnerable version string"),
|
|
2497
|
+
cveId: z7.string().regex(/^CVE-\d{4}-\d+$/i).describe("CVE ID (e.g., CVE-2021-23337)"),
|
|
2498
|
+
cveSummary: z7.string().min(10).describe("CVE description and impact"),
|
|
2499
|
+
sourceFiles: z7.record(z7.string(), z7.string()).describe(
|
|
2203
2500
|
"Map of file paths to source code contents from fetch-package-source"
|
|
2204
2501
|
),
|
|
2205
|
-
vulnerabilityCategory:
|
|
2206
|
-
dryRun:
|
|
2207
|
-
llmProvider:
|
|
2208
|
-
model:
|
|
2209
|
-
policy:
|
|
2210
|
-
cwd:
|
|
2211
|
-
providerSafetyProfile:
|
|
2212
|
-
patchConfidenceThresholds:
|
|
2213
|
-
low:
|
|
2214
|
-
medium:
|
|
2215
|
-
high:
|
|
2502
|
+
vulnerabilityCategory: z7.enum(["redos", "code-injection", "path-traversal", "unknown"]).optional().default("unknown").describe("Category of the vulnerability for better context"),
|
|
2503
|
+
dryRun: z7.boolean().optional().default(false).describe("If true, return analysis without generating patches"),
|
|
2504
|
+
llmProvider: z7.enum(["remote", "local"]).optional().describe("Optional provider override for patch generation"),
|
|
2505
|
+
model: z7.string().optional().describe("Optional model override for patch generation"),
|
|
2506
|
+
policy: z7.string().optional().describe("Optional policy file path for model default resolution"),
|
|
2507
|
+
cwd: z7.string().optional().describe("Optional working directory for policy/model resolution"),
|
|
2508
|
+
providerSafetyProfile: z7.enum(["strict", "relaxed"]).optional().describe("Confidence threshold profile for patch acceptance"),
|
|
2509
|
+
patchConfidenceThresholds: z7.object({
|
|
2510
|
+
low: z7.number().min(0).max(1).optional(),
|
|
2511
|
+
medium: z7.number().min(0).max(1).optional(),
|
|
2512
|
+
high: z7.number().min(0).max(1).optional()
|
|
2216
2513
|
}).optional().describe("Optional per-risk confidence thresholds for patch acceptance"),
|
|
2217
|
-
dynamicModelRouting:
|
|
2218
|
-
dynamicRoutingThresholdChars:
|
|
2219
|
-
modelPersonality:
|
|
2514
|
+
dynamicModelRouting: z7.boolean().optional().describe("Enable dynamic model routing by input size"),
|
|
2515
|
+
dynamicRoutingThresholdChars: z7.number().int().positive().optional().describe("Threshold for dynamic model routing"),
|
|
2516
|
+
modelPersonality: z7.enum(["analytical", "pragmatic", "balanced"]).optional().describe("Prompt personality for patch-generation guidance")
|
|
2220
2517
|
}),
|
|
2221
2518
|
execute: async ({
|
|
2222
2519
|
packageName,
|
|
@@ -2261,7 +2558,7 @@ var generatePatchTool = tool5({
|
|
|
2261
2558
|
}
|
|
2262
2559
|
const inputChars = JSON.stringify(resolvedSourceFiles).length + cveSummary.length;
|
|
2263
2560
|
const modelInstance = await createModel(effectiveOptions, { inputChars });
|
|
2264
|
-
const modelName = modelInstance.modelId
|
|
2561
|
+
const modelName = typeof modelInstance === "string" ? modelInstance : "modelId" in modelInstance && typeof modelInstance.modelId === "string" ? modelInstance.modelId : "unknown-model";
|
|
2265
2562
|
const prompt = buildPatchPrompt({
|
|
2266
2563
|
cveId,
|
|
2267
2564
|
packageName,
|
|
@@ -2366,17 +2663,17 @@ var generatePatchTool = tool5({
|
|
|
2366
2663
|
});
|
|
2367
2664
|
|
|
2368
2665
|
// src/remediation/tools/apply-patch-file/index.ts
|
|
2369
|
-
import {
|
|
2370
|
-
import { z as z6 } from "zod";
|
|
2666
|
+
import { z as z8 } from "zod";
|
|
2371
2667
|
import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
|
|
2372
|
-
import { join as
|
|
2668
|
+
import { join as join14 } from "path";
|
|
2373
2669
|
import { execa as execa10 } from "execa";
|
|
2374
2670
|
|
|
2375
2671
|
// src/remediation/tools/apply-patch-file/helpers.ts
|
|
2376
2672
|
import { existsSync as existsSync7 } from "fs";
|
|
2377
2673
|
import { mkdtemp, readFile as readFile4, rm as rm3, writeFile } from "fs/promises";
|
|
2674
|
+
import { createHash } from "crypto";
|
|
2378
2675
|
import { tmpdir } from "os";
|
|
2379
|
-
import { join as
|
|
2676
|
+
import { join as join13 } from "path";
|
|
2380
2677
|
import { execa as execa9 } from "execa";
|
|
2381
2678
|
async function resolvePatchMode(packageManager, cwd) {
|
|
2382
2679
|
if (packageManager === "npm") return "patch-package";
|
|
@@ -2403,11 +2700,15 @@ function extractPatchedFiles(patchContent) {
|
|
|
2403
2700
|
function countPatchHunks(patchContent) {
|
|
2404
2701
|
return patchContent.split(/\r?\n/).filter((line) => line.startsWith("@@ ")).length;
|
|
2405
2702
|
}
|
|
2703
|
+
function computePatchIntegrity(patchContent) {
|
|
2704
|
+
const hex = createHash("sha256").update(patchContent, "utf8").digest("hex");
|
|
2705
|
+
return `sha256:${hex}`;
|
|
2706
|
+
}
|
|
2406
2707
|
async function writePatchManifest(manifestFilePath, artifact) {
|
|
2407
2708
|
await writeFile(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
|
|
2408
2709
|
}
|
|
2409
2710
|
async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
2410
|
-
const pkgJsonPath =
|
|
2711
|
+
const pkgJsonPath = join13(cwd, "package.json");
|
|
2411
2712
|
let pkgJson;
|
|
2412
2713
|
try {
|
|
2413
2714
|
pkgJson = JSON.parse(await readFile4(pkgJsonPath, "utf8"));
|
|
@@ -2447,7 +2748,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
|
2447
2748
|
return { success: true };
|
|
2448
2749
|
}
|
|
2449
2750
|
async function capturePackageJsonSnapshot(cwd) {
|
|
2450
|
-
const path =
|
|
2751
|
+
const path = join13(cwd, "package.json");
|
|
2451
2752
|
try {
|
|
2452
2753
|
const content = await readFile4(path, "utf8");
|
|
2453
2754
|
return { path, content };
|
|
@@ -2517,8 +2818,8 @@ ${createResult.stderr}`);
|
|
|
2517
2818
|
error: `Could not determine native patch directory for ${packageSpec}.`
|
|
2518
2819
|
};
|
|
2519
2820
|
}
|
|
2520
|
-
const tempPatchDir = await mkdtemp(
|
|
2521
|
-
const tempPatchFile =
|
|
2821
|
+
const tempPatchDir = await mkdtemp(join13(tmpdir(), "autoremediator-native-patch-"));
|
|
2822
|
+
const tempPatchFile = join13(tempPatchDir, "change.patch");
|
|
2522
2823
|
try {
|
|
2523
2824
|
await writeFile(tempPatchFile, patchContent, "utf8");
|
|
2524
2825
|
await execa9("patch", ["-p1", "-i", tempPatchFile], {
|
|
@@ -2603,31 +2904,31 @@ function extractFailedTests(output) {
|
|
|
2603
2904
|
}
|
|
2604
2905
|
|
|
2605
2906
|
// src/remediation/tools/apply-patch-file/index.ts
|
|
2606
|
-
var applyPatchFileTool =
|
|
2907
|
+
var applyPatchFileTool = defineTool({
|
|
2607
2908
|
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:
|
|
2909
|
+
parameters: z8.object({
|
|
2910
|
+
packageName: z8.string().min(1).describe("The npm package name"),
|
|
2911
|
+
vulnerableVersion: z8.string().describe("The vulnerable version string"),
|
|
2912
|
+
patchContent: z8.string().min(10).optional().describe("Unified diff patch content from generate-patch"),
|
|
2913
|
+
cveId: z8.string().optional().describe("Optional CVE ID associated with this patch artifact"),
|
|
2914
|
+
confidence: z8.number().min(0).max(1).optional().describe("Optional patch confidence score from generate-patch"),
|
|
2915
|
+
riskLevel: z8.enum(["low", "medium", "high"]).optional().describe("Optional risk level from generate-patch"),
|
|
2916
|
+
patches: z8.array(
|
|
2917
|
+
z8.object({
|
|
2918
|
+
filePath: z8.string().min(1),
|
|
2919
|
+
unifiedDiff: z8.string().min(10)
|
|
2619
2920
|
})
|
|
2620
2921
|
).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:
|
|
2922
|
+
patchesDir: z8.string().optional().default("./patches").describe("Directory to store patch files"),
|
|
2923
|
+
cwd: z8.string().describe("Project root directory (for package.json)"),
|
|
2924
|
+
packageManager: z8.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
|
|
2925
|
+
policy: z8.string().optional().describe("Optional path to .autoremediator policy file"),
|
|
2926
|
+
installMode: z8.enum(["standard", "prefer-offline", "deterministic"]).optional(),
|
|
2927
|
+
installPreferOffline: z8.boolean().optional(),
|
|
2928
|
+
enforceFrozenLockfile: z8.boolean().optional(),
|
|
2929
|
+
workspace: z8.string().optional(),
|
|
2930
|
+
validateWithTests: z8.boolean().optional().default(true).describe("Run package manager test command to validate patch doesn't break anything"),
|
|
2931
|
+
dryRun: z8.boolean().optional().default(false).describe("If true, report but do not mutate files")
|
|
2631
2932
|
}).refine((value) => Boolean(value.patchContent || value.patches && value.patches.length > 0), {
|
|
2632
2933
|
message: "Either patchContent or patches must be provided"
|
|
2633
2934
|
}),
|
|
@@ -2698,7 +2999,7 @@ var applyPatchFileTool = tool6({
|
|
|
2698
2999
|
};
|
|
2699
3000
|
}
|
|
2700
3001
|
const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
|
|
2701
|
-
const patchFilePath =
|
|
3002
|
+
const patchFilePath = join14(cwd, patchesDir, patchFileName);
|
|
2702
3003
|
const manifestFilePath = `${patchFilePath}.json`;
|
|
2703
3004
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2704
3005
|
const baseArtifact = {
|
|
@@ -2717,6 +3018,7 @@ var applyPatchFileTool = tool6({
|
|
|
2717
3018
|
hunkCount,
|
|
2718
3019
|
applied: false,
|
|
2719
3020
|
dryRun,
|
|
3021
|
+
integrity: computePatchIntegrity(selectedPatch),
|
|
2720
3022
|
validationPhases
|
|
2721
3023
|
};
|
|
2722
3024
|
if (dryRun) {
|
|
@@ -2736,7 +3038,7 @@ var applyPatchFileTool = tool6({
|
|
|
2736
3038
|
}
|
|
2737
3039
|
return withRepoLock(cwd, async () => {
|
|
2738
3040
|
const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
|
|
2739
|
-
const patchesDirPath =
|
|
3041
|
+
const patchesDirPath = join14(cwd, patchesDir);
|
|
2740
3042
|
await mkdir3(patchesDirPath, { recursive: true });
|
|
2741
3043
|
await writeFile2(patchFilePath, selectedPatch, "utf8");
|
|
2742
3044
|
validationPhases.push({
|
|
@@ -2948,7 +3250,7 @@ function resolvePatchProvider(provider) {
|
|
|
2948
3250
|
function shouldAttemptPatchFallback(result, preferVersionBump) {
|
|
2949
3251
|
if (preferVersionBump) return false;
|
|
2950
3252
|
if (result.applied || result.dryRun) return false;
|
|
2951
|
-
return result.unresolvedReason === "no-safe-version" || result.unresolvedReason === "install-failed" || result.unresolvedReason === "override-apply-failed" || result.unresolvedReason === "validation-failed" || result.unresolvedReason === "major-bump-required" || result.unresolvedReason === "
|
|
3253
|
+
return result.unresolvedReason === "no-safe-version" || result.unresolvedReason === "install-failed" || result.unresolvedReason === "override-apply-failed" || result.unresolvedReason === "validation-failed" || result.unresolvedReason === "major-bump-required" || result.unresolvedReason === "transitive-dependency";
|
|
2952
3254
|
}
|
|
2953
3255
|
async function tryLocalPatchFallback(params) {
|
|
2954
3256
|
const usage = [];
|
|
@@ -3171,7 +3473,14 @@ function resolveLocalRunOptions(options) {
|
|
|
3171
3473
|
...options.patchConfidenceThresholds
|
|
3172
3474
|
},
|
|
3173
3475
|
dynamicModelRouting: options.dynamicModelRouting ?? loadedPolicy.dynamicModelRouting ?? false,
|
|
3174
|
-
dynamicRoutingThresholdChars: options.dynamicRoutingThresholdChars ?? loadedPolicy.dynamicRoutingThresholdChars
|
|
3476
|
+
dynamicRoutingThresholdChars: options.dynamicRoutingThresholdChars ?? loadedPolicy.dynamicRoutingThresholdChars,
|
|
3477
|
+
exploitSignalOverride: options.exploitSignalOverride ?? loadedPolicy.exploitSignalOverride,
|
|
3478
|
+
suppressions: loadedPolicy.suppressions ?? [],
|
|
3479
|
+
suppressionsFile: options.suppressionsFile,
|
|
3480
|
+
slaCheck: options.slaCheck ?? false,
|
|
3481
|
+
slaPolicy: loadedPolicy.sla,
|
|
3482
|
+
skipUnreachable: options.skipUnreachable ?? loadedPolicy.skipUnreachable ?? false,
|
|
3483
|
+
regressionCheck: options.regressionCheck ?? false
|
|
3175
3484
|
};
|
|
3176
3485
|
}
|
|
3177
3486
|
|
|
@@ -3228,7 +3537,14 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3228
3537
|
consensusModel,
|
|
3229
3538
|
patchConfidenceThresholds,
|
|
3230
3539
|
dynamicModelRouting,
|
|
3231
|
-
dynamicRoutingThresholdChars
|
|
3540
|
+
dynamicRoutingThresholdChars,
|
|
3541
|
+
exploitSignalOverride,
|
|
3542
|
+
suppressions,
|
|
3543
|
+
suppressionsFile,
|
|
3544
|
+
slaCheck,
|
|
3545
|
+
slaPolicy,
|
|
3546
|
+
skipUnreachable,
|
|
3547
|
+
regressionCheck
|
|
3232
3548
|
} = resolved;
|
|
3233
3549
|
const collectedResults = [];
|
|
3234
3550
|
const llmUsage = [];
|
|
@@ -3267,6 +3583,29 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3267
3583
|
cveDetails = mergeGhDataIntoCveDetails(cveDetails, ghPackages);
|
|
3268
3584
|
}
|
|
3269
3585
|
cveDetails = await enrichWithNvd(cveDetails);
|
|
3586
|
+
const preflight = await runSecOpsPreflight(normalizedId, cveDetails, {
|
|
3587
|
+
suppressions,
|
|
3588
|
+
suppressionsFile,
|
|
3589
|
+
exploitSignalOverride,
|
|
3590
|
+
slaCheck,
|
|
3591
|
+
slaPolicy
|
|
3592
|
+
});
|
|
3593
|
+
if (preflight.suppressed) {
|
|
3594
|
+
return {
|
|
3595
|
+
cveId,
|
|
3596
|
+
cveDetails,
|
|
3597
|
+
vulnerablePackages: [],
|
|
3598
|
+
results: [],
|
|
3599
|
+
agentSteps,
|
|
3600
|
+
summary: preflight.summary,
|
|
3601
|
+
correlation: {
|
|
3602
|
+
requestId: options.requestId,
|
|
3603
|
+
sessionId: options.sessionId,
|
|
3604
|
+
parentRunId: options.parentRunId
|
|
3605
|
+
}
|
|
3606
|
+
};
|
|
3607
|
+
}
|
|
3608
|
+
const { exploitSignalTriggered, slaBreaches } = preflight;
|
|
3270
3609
|
if (cveDetails.affectedPackages.length === 0) {
|
|
3271
3610
|
return {
|
|
3272
3611
|
cveId,
|
|
@@ -3275,6 +3614,8 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3275
3614
|
results: collectedResults,
|
|
3276
3615
|
agentSteps,
|
|
3277
3616
|
summary: `Local mode lookup succeeded but no npm affected packages were found for ${normalizedId}.`,
|
|
3617
|
+
exploitSignalTriggered,
|
|
3618
|
+
slaBreaches,
|
|
3278
3619
|
correlation: {
|
|
3279
3620
|
requestId: options.requestId,
|
|
3280
3621
|
sessionId: options.sessionId,
|
|
@@ -3297,6 +3638,8 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3297
3638
|
results: collectedResults,
|
|
3298
3639
|
agentSteps,
|
|
3299
3640
|
summary: `Local mode failed at check-inventory: ${inventory.error}`,
|
|
3641
|
+
exploitSignalTriggered,
|
|
3642
|
+
slaBreaches,
|
|
3300
3643
|
correlation: {
|
|
3301
3644
|
requestId: options.requestId,
|
|
3302
3645
|
sessionId: options.sessionId,
|
|
@@ -3308,6 +3651,21 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3308
3651
|
vulnerablePackages = findVulnerablePackages(cveDetails, installedPackages);
|
|
3309
3652
|
agentSteps += 1;
|
|
3310
3653
|
for (const vulnerable of vulnerablePackages) {
|
|
3654
|
+
if (skipUnreachable) {
|
|
3655
|
+
const reach = assessPackageReachability(cwd, vulnerable.installed.name);
|
|
3656
|
+
if (reach.status === "not-reachable") {
|
|
3657
|
+
collectedResults.push({
|
|
3658
|
+
packageName: vulnerable.installed.name,
|
|
3659
|
+
fromVersion: vulnerable.installed.version,
|
|
3660
|
+
strategy: "none",
|
|
3661
|
+
applied: false,
|
|
3662
|
+
dryRun,
|
|
3663
|
+
message: `Skipped: '${vulnerable.installed.name}' is not reachable from source code.`,
|
|
3664
|
+
reachability: reach
|
|
3665
|
+
});
|
|
3666
|
+
continue;
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3311
3669
|
const primary = await resolvePrimaryResult({
|
|
3312
3670
|
vulnerable,
|
|
3313
3671
|
cwd,
|
|
@@ -3353,11 +3711,22 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3353
3711
|
}
|
|
3354
3712
|
continue;
|
|
3355
3713
|
}
|
|
3356
|
-
|
|
3714
|
+
const primaryResult = {
|
|
3357
3715
|
...primary.result,
|
|
3358
3716
|
dependencyScope: vulnerable.installed.type === "direct" ? "direct" : "transitive"
|
|
3359
|
-
}
|
|
3717
|
+
};
|
|
3718
|
+
if (regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
|
|
3719
|
+
try {
|
|
3720
|
+
if (semver5.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, { includePrerelease: false })) {
|
|
3721
|
+
primaryResult.regressionDetected = true;
|
|
3722
|
+
}
|
|
3723
|
+
} catch {
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
collectedResults.push(primaryResult);
|
|
3360
3727
|
}
|
|
3728
|
+
const vulnerableNames = new Set(vulnerablePackages.map((v) => v.installed.name));
|
|
3729
|
+
const sbom = buildSbom(installedPackages, vulnerableNames, collectedResults);
|
|
3361
3730
|
return {
|
|
3362
3731
|
cveId,
|
|
3363
3732
|
cveDetails,
|
|
@@ -3366,6 +3735,9 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
3366
3735
|
agentSteps,
|
|
3367
3736
|
summary: buildLocalSummary(vulnerablePackages, collectedResults),
|
|
3368
3737
|
llmUsage: llmUsage.length > 0 ? llmUsage : void 0,
|
|
3738
|
+
exploitSignalTriggered,
|
|
3739
|
+
slaBreaches,
|
|
3740
|
+
sbom,
|
|
3369
3741
|
correlation: {
|
|
3370
3742
|
requestId: options.requestId,
|
|
3371
3743
|
sessionId: options.sessionId,
|
|
@@ -3440,8 +3812,8 @@ function accumulateStepResults(params) {
|
|
|
3440
3812
|
}
|
|
3441
3813
|
|
|
3442
3814
|
// src/remediation/orchestration-prompt.ts
|
|
3443
|
-
import { existsSync as existsSync8, readFileSync as
|
|
3444
|
-
import { join as
|
|
3815
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
|
|
3816
|
+
import { join as join15 } from "path";
|
|
3445
3817
|
function buildProviderAddendum(provider, personality = "balanced") {
|
|
3446
3818
|
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
3819
|
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 +3824,7 @@ Provider profile:
|
|
|
3452
3824
|
- ${personalityDirective}`;
|
|
3453
3825
|
}
|
|
3454
3826
|
function loadOrchestrationPrompt(ctx) {
|
|
3455
|
-
const promptPath =
|
|
3827
|
+
const promptPath = join15(process.cwd(), ".github", "instructions", "orchestration.instructions.md");
|
|
3456
3828
|
if (!existsSync8(promptPath)) {
|
|
3457
3829
|
return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
|
|
3458
3830
|
Working directory: ${ctx.cwd}
|
|
@@ -3479,7 +3851,7 @@ Fallback sequence (when neither version bump nor override can be applied):
|
|
|
3479
3851
|
|
|
3480
3852
|
Always respect dryRun and policy constraints.`;
|
|
3481
3853
|
}
|
|
3482
|
-
const template =
|
|
3854
|
+
const template = readFileSync9(promptPath, "utf8");
|
|
3483
3855
|
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
3856
|
}
|
|
3485
3857
|
|
|
@@ -3498,8 +3870,7 @@ function createProgressEmitter(options) {
|
|
|
3498
3870
|
}
|
|
3499
3871
|
|
|
3500
3872
|
// src/remediation/tools/lookup-cve.ts
|
|
3501
|
-
import {
|
|
3502
|
-
import { z as z7 } from "zod";
|
|
3873
|
+
import { z as z9 } from "zod";
|
|
3503
3874
|
|
|
3504
3875
|
// src/intelligence/sources/cisa-kev.ts
|
|
3505
3876
|
var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
|
|
@@ -3777,10 +4148,10 @@ async function enrichWithExternalFeeds(details) {
|
|
|
3777
4148
|
}
|
|
3778
4149
|
|
|
3779
4150
|
// src/remediation/tools/lookup-cve.ts
|
|
3780
|
-
var lookupCveTool =
|
|
4151
|
+
var lookupCveTool = defineTool({
|
|
3781
4152
|
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:
|
|
4153
|
+
parameters: z9.object({
|
|
4154
|
+
cveId: z9.string().regex(/^CVE-\d{4}-\d+$/i, "Must be a valid CVE ID like CVE-2021-23337")
|
|
3784
4155
|
}),
|
|
3785
4156
|
execute: async ({ cveId }) => {
|
|
3786
4157
|
const normalizedId = cveId.toUpperCase();
|
|
@@ -3846,26 +4217,25 @@ var lookupCveTool = tool7({
|
|
|
3846
4217
|
});
|
|
3847
4218
|
|
|
3848
4219
|
// src/remediation/tools/check-version-match.ts
|
|
3849
|
-
import {
|
|
3850
|
-
import
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
source: z8.enum(["osv", "github-advisory"])
|
|
4220
|
+
import { z as z10 } from "zod";
|
|
4221
|
+
import semver6 from "semver";
|
|
4222
|
+
var affectedPackageSchema = z10.object({
|
|
4223
|
+
name: z10.string(),
|
|
4224
|
+
ecosystem: z10.literal("npm"),
|
|
4225
|
+
vulnerableRange: z10.string(),
|
|
4226
|
+
firstPatchedVersion: z10.string().optional(),
|
|
4227
|
+
source: z10.enum(["osv", "github-advisory"])
|
|
3858
4228
|
});
|
|
3859
|
-
var inventoryPackageSchema =
|
|
3860
|
-
name:
|
|
3861
|
-
version:
|
|
3862
|
-
type:
|
|
4229
|
+
var inventoryPackageSchema = z10.object({
|
|
4230
|
+
name: z10.string(),
|
|
4231
|
+
version: z10.string(),
|
|
4232
|
+
type: z10.enum(["direct", "transitive"])
|
|
3863
4233
|
});
|
|
3864
|
-
var checkVersionMatchTool =
|
|
4234
|
+
var checkVersionMatchTool = defineTool({
|
|
3865
4235
|
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:
|
|
4236
|
+
parameters: z10.object({
|
|
4237
|
+
installedPackages: z10.array(inventoryPackageSchema).describe("Output from the check-inventory tool"),
|
|
4238
|
+
affectedPackages: z10.array(affectedPackageSchema).describe("affectedPackages array from the lookup-cve tool result")
|
|
3869
4239
|
}),
|
|
3870
4240
|
execute: async ({ installedPackages, affectedPackages }) => {
|
|
3871
4241
|
const vulnerable = [];
|
|
@@ -3874,10 +4244,10 @@ var checkVersionMatchTool = tool8({
|
|
|
3874
4244
|
(p) => p.name === affected.name
|
|
3875
4245
|
);
|
|
3876
4246
|
for (const installed of matches) {
|
|
3877
|
-
if (!
|
|
4247
|
+
if (!semver6.valid(installed.version)) continue;
|
|
3878
4248
|
let isVulnerable = false;
|
|
3879
4249
|
try {
|
|
3880
|
-
isVulnerable =
|
|
4250
|
+
isVulnerable = semver6.satisfies(installed.version, affected.vulnerableRange, {
|
|
3881
4251
|
includePrerelease: false
|
|
3882
4252
|
});
|
|
3883
4253
|
} catch {
|
|
@@ -3896,17 +4266,16 @@ var checkVersionMatchTool = tool8({
|
|
|
3896
4266
|
});
|
|
3897
4267
|
|
|
3898
4268
|
// src/remediation/tools/find-fixed-version.ts
|
|
3899
|
-
import {
|
|
3900
|
-
|
|
3901
|
-
var findFixedVersionTool = tool9({
|
|
4269
|
+
import { z as z11 } from "zod";
|
|
4270
|
+
var findFixedVersionTool = defineTool({
|
|
3902
4271
|
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:
|
|
4272
|
+
parameters: z11.object({
|
|
4273
|
+
packageName: z11.string().describe("The npm package name"),
|
|
4274
|
+
installedVersion: z11.string().describe("The currently installed version (exact semver)"),
|
|
4275
|
+
firstPatchedVersion: z11.string().describe(
|
|
3907
4276
|
"The first version that is NOT vulnerable (from lookup-cve). Use this as the floor."
|
|
3908
4277
|
),
|
|
3909
|
-
vulnerableRange:
|
|
4278
|
+
vulnerableRange: z11.string().optional().describe("Optional vulnerable semver range used to exclude still-vulnerable versions")
|
|
3910
4279
|
}),
|
|
3911
4280
|
execute: async ({
|
|
3912
4281
|
packageName,
|
|
@@ -3943,6 +4312,35 @@ var findFixedVersionTool = tool9({
|
|
|
3943
4312
|
}
|
|
3944
4313
|
});
|
|
3945
4314
|
|
|
4315
|
+
// src/remediation/tools/check-suppression.ts
|
|
4316
|
+
import { z as z12 } from "zod";
|
|
4317
|
+
var suppressionSchema = z12.object({
|
|
4318
|
+
cveId: z12.string(),
|
|
4319
|
+
justification: z12.enum(["not_affected", "fixed", "mitigated", "under_investigation"]),
|
|
4320
|
+
notes: z12.string().optional(),
|
|
4321
|
+
expiresAt: z12.string().optional()
|
|
4322
|
+
});
|
|
4323
|
+
var checkSuppressionTool = defineTool({
|
|
4324
|
+
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.",
|
|
4325
|
+
parameters: z12.object({
|
|
4326
|
+
cveId: z12.string().describe("The CVE ID to look up in suppressions"),
|
|
4327
|
+
suppressions: z12.array(suppressionSchema).describe("The suppressions array from the loaded policy")
|
|
4328
|
+
}),
|
|
4329
|
+
execute: async ({ cveId, suppressions }) => {
|
|
4330
|
+
const match = suppressions.find(
|
|
4331
|
+
(s) => s.cveId === cveId && isActiveSuppression(s)
|
|
4332
|
+
);
|
|
4333
|
+
if (!match) {
|
|
4334
|
+
return { suppressed: false };
|
|
4335
|
+
}
|
|
4336
|
+
return {
|
|
4337
|
+
suppressed: true,
|
|
4338
|
+
justification: match.justification,
|
|
4339
|
+
notes: match.notes
|
|
4340
|
+
};
|
|
4341
|
+
}
|
|
4342
|
+
});
|
|
4343
|
+
|
|
3946
4344
|
// src/remediation/runtime-tools.ts
|
|
3947
4345
|
function buildRuntimeTools(ctx) {
|
|
3948
4346
|
const tools = {
|
|
@@ -3950,7 +4348,10 @@ function buildRuntimeTools(ctx) {
|
|
|
3950
4348
|
"check-inventory": ctx.checkInventoryToolForRun,
|
|
3951
4349
|
"check-version-match": checkVersionMatchTool,
|
|
3952
4350
|
"find-fixed-version": findFixedVersionTool,
|
|
3953
|
-
"apply-version-bump": ctx.applyVersionBumpToolForRun
|
|
4351
|
+
"apply-version-bump": ctx.applyVersionBumpToolForRun,
|
|
4352
|
+
"check-suppression": checkSuppressionTool,
|
|
4353
|
+
"check-exploit-signal": checkExploitSignalTool,
|
|
4354
|
+
"check-reachability": checkReachabilityTool
|
|
3954
4355
|
};
|
|
3955
4356
|
if (!ctx.constraints.directDependenciesOnly && !ctx.constraints.preferVersionBump) {
|
|
3956
4357
|
tools["apply-package-override"] = ctx.applyPackageOverrideToolForRun;
|
|
@@ -4034,7 +4435,7 @@ async function createPipelineRuntime(cveId, options) {
|
|
|
4034
4435
|
const constraints = options.constraints ?? {};
|
|
4035
4436
|
const prompt = `Patch vulnerable dependencies affected by ${cveId} in the project at: ${cwd}. Package manager: ${packageManager}.`;
|
|
4036
4437
|
const model = await createModel(options, { inputChars: prompt.length });
|
|
4037
|
-
const modelName = model.modelId
|
|
4438
|
+
const modelName = typeof model === "string" ? model : "modelId" in model && typeof model.modelId === "string" ? model.modelId : "unknown-model";
|
|
4038
4439
|
const systemPrompt = loadOrchestrationPrompt({
|
|
4039
4440
|
cveId,
|
|
4040
4441
|
cwd,
|
|
@@ -4086,7 +4487,7 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
4086
4487
|
system: systemPrompt,
|
|
4087
4488
|
prompt,
|
|
4088
4489
|
tools,
|
|
4089
|
-
|
|
4490
|
+
stopWhen: ({ steps }) => steps.length >= 25,
|
|
4090
4491
|
onStepFinish(stepResult) {
|
|
4091
4492
|
agentSteps += 1;
|
|
4092
4493
|
const toolResults = stepResult.toolResults ?? [];
|
|
@@ -4141,6 +4542,188 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
4141
4542
|
};
|
|
4142
4543
|
}
|
|
4143
4544
|
|
|
4545
|
+
// src/api/change-request/index.ts
|
|
4546
|
+
import { execa as execa11 } from "execa";
|
|
4547
|
+
function sanitizeBranchToken(value) {
|
|
4548
|
+
return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
4549
|
+
}
|
|
4550
|
+
function buildPlan(reports) {
|
|
4551
|
+
const cveIds = Array.from(new Set(reports.map((report) => report.cveId)));
|
|
4552
|
+
const packageNames = Array.from(
|
|
4553
|
+
new Set(
|
|
4554
|
+
reports.flatMap((report) => report.results.map((result) => result.packageName))
|
|
4555
|
+
)
|
|
4556
|
+
);
|
|
4557
|
+
const titleSuffix = cveIds.length === 1 ? cveIds[0] : `${cveIds.length} CVEs`;
|
|
4558
|
+
const title = `security remediation: ${titleSuffix}`;
|
|
4559
|
+
const body = [
|
|
4560
|
+
"Automated remediation generated by autoremediator.",
|
|
4561
|
+
"",
|
|
4562
|
+
`CVEs: ${cveIds.join(", ")}`,
|
|
4563
|
+
`Packages: ${packageNames.join(", ")}`
|
|
4564
|
+
].join("\n");
|
|
4565
|
+
return {
|
|
4566
|
+
cveIds,
|
|
4567
|
+
packageNames,
|
|
4568
|
+
title,
|
|
4569
|
+
body
|
|
4570
|
+
};
|
|
4571
|
+
}
|
|
4572
|
+
function resolveProvider2(options) {
|
|
4573
|
+
return options.provider ?? "github";
|
|
4574
|
+
}
|
|
4575
|
+
function resolveGrouping(options) {
|
|
4576
|
+
if (options.grouping && options.grouping !== "all") {
|
|
4577
|
+
throw new Error(
|
|
4578
|
+
"changeRequest.grouping currently supports only 'all' for deterministic branch creation."
|
|
4579
|
+
);
|
|
4580
|
+
}
|
|
4581
|
+
return "all";
|
|
4582
|
+
}
|
|
4583
|
+
async function detectCurrentBranch(cwd) {
|
|
4584
|
+
const result = await execa11("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
4585
|
+
cwd,
|
|
4586
|
+
stdio: "pipe"
|
|
4587
|
+
});
|
|
4588
|
+
return result.stdout.trim();
|
|
4589
|
+
}
|
|
4590
|
+
async function ensureRepoHasChanges(cwd) {
|
|
4591
|
+
const diff = await execa11("git", ["status", "--porcelain"], {
|
|
4592
|
+
cwd,
|
|
4593
|
+
stdio: "pipe"
|
|
4594
|
+
});
|
|
4595
|
+
return diff.stdout.trim().length > 0;
|
|
4596
|
+
}
|
|
4597
|
+
function toBranchName(prefix, cveIds) {
|
|
4598
|
+
const token = sanitizeBranchToken(cveIds.join("-") || "remediation");
|
|
4599
|
+
return `${sanitizeBranchToken(prefix)}/${token}-${Date.now()}`;
|
|
4600
|
+
}
|
|
4601
|
+
async function createGitHubRequest(params) {
|
|
4602
|
+
const args = ["pr", "create", "--head", params.head, "--base", params.base, "--title", params.title, "--body", params.body];
|
|
4603
|
+
if (params.repository) {
|
|
4604
|
+
args.push("--repo", params.repository);
|
|
4605
|
+
}
|
|
4606
|
+
if (params.draft) {
|
|
4607
|
+
args.push("--draft");
|
|
4608
|
+
}
|
|
4609
|
+
const result = await execa11("gh", args, {
|
|
4610
|
+
cwd: params.cwd,
|
|
4611
|
+
stdio: "pipe"
|
|
4612
|
+
});
|
|
4613
|
+
return result.stdout.trim() || void 0;
|
|
4614
|
+
}
|
|
4615
|
+
async function createGitLabRequest(params) {
|
|
4616
|
+
const args = [
|
|
4617
|
+
"mr",
|
|
4618
|
+
"create",
|
|
4619
|
+
"--source-branch",
|
|
4620
|
+
params.source,
|
|
4621
|
+
"--target-branch",
|
|
4622
|
+
params.target,
|
|
4623
|
+
"--title",
|
|
4624
|
+
params.title,
|
|
4625
|
+
"--description",
|
|
4626
|
+
params.body
|
|
4627
|
+
];
|
|
4628
|
+
if (params.repository) {
|
|
4629
|
+
args.push("-R", params.repository);
|
|
4630
|
+
}
|
|
4631
|
+
if (params.draft) {
|
|
4632
|
+
args.push("--draft");
|
|
4633
|
+
}
|
|
4634
|
+
const result = await execa11("glab", args, {
|
|
4635
|
+
cwd: params.cwd,
|
|
4636
|
+
stdio: "pipe"
|
|
4637
|
+
});
|
|
4638
|
+
return result.stdout.trim() || void 0;
|
|
4639
|
+
}
|
|
4640
|
+
async function createChangeRequestsForReports(params) {
|
|
4641
|
+
const { cwd, options, reports } = params;
|
|
4642
|
+
const provider = resolveProvider2(options);
|
|
4643
|
+
const grouping = resolveGrouping(options);
|
|
4644
|
+
const plan = buildPlan(reports);
|
|
4645
|
+
const titlePrefix = options.titlePrefix?.trim();
|
|
4646
|
+
const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
|
|
4647
|
+
const body = options.bodyFooter ? `${plan.body}
|
|
4648
|
+
|
|
4649
|
+
${options.bodyFooter}` : plan.body;
|
|
4650
|
+
try {
|
|
4651
|
+
const hasChanges = await ensureRepoHasChanges(cwd);
|
|
4652
|
+
if (!hasChanges) {
|
|
4653
|
+
return [
|
|
4654
|
+
{
|
|
4655
|
+
provider,
|
|
4656
|
+
grouping,
|
|
4657
|
+
repository: options.repository,
|
|
4658
|
+
branchName: "",
|
|
4659
|
+
title,
|
|
4660
|
+
body,
|
|
4661
|
+
created: false,
|
|
4662
|
+
cveIds: plan.cveIds,
|
|
4663
|
+
packageNames: plan.packageNames,
|
|
4664
|
+
error: "No repository changes to include in change request."
|
|
4665
|
+
}
|
|
4666
|
+
];
|
|
4667
|
+
}
|
|
4668
|
+
const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd);
|
|
4669
|
+
const branchPrefix = options.branchPrefix ?? "autoremediator";
|
|
4670
|
+
const branchName = toBranchName(branchPrefix, plan.cveIds);
|
|
4671
|
+
const pushRemote = options.pushRemote ?? "origin";
|
|
4672
|
+
await execa11("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
|
|
4673
|
+
await execa11("git", ["add", "-A"], { cwd, stdio: "pipe" });
|
|
4674
|
+
await execa11("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
|
|
4675
|
+
await execa11("git", ["push", "-u", pushRemote, branchName], { cwd, stdio: "pipe" });
|
|
4676
|
+
const url = provider === "github" ? await createGitHubRequest({
|
|
4677
|
+
cwd,
|
|
4678
|
+
repository: options.repository,
|
|
4679
|
+
head: branchName,
|
|
4680
|
+
base: baseBranch,
|
|
4681
|
+
title,
|
|
4682
|
+
body,
|
|
4683
|
+
draft: options.draft
|
|
4684
|
+
}) : await createGitLabRequest({
|
|
4685
|
+
cwd,
|
|
4686
|
+
repository: options.repository,
|
|
4687
|
+
source: branchName,
|
|
4688
|
+
target: baseBranch,
|
|
4689
|
+
title,
|
|
4690
|
+
body,
|
|
4691
|
+
draft: options.draft
|
|
4692
|
+
});
|
|
4693
|
+
return [
|
|
4694
|
+
{
|
|
4695
|
+
provider,
|
|
4696
|
+
grouping,
|
|
4697
|
+
repository: options.repository,
|
|
4698
|
+
branchName,
|
|
4699
|
+
title,
|
|
4700
|
+
body,
|
|
4701
|
+
created: true,
|
|
4702
|
+
draft: options.draft,
|
|
4703
|
+
url,
|
|
4704
|
+
cveIds: plan.cveIds,
|
|
4705
|
+
packageNames: plan.packageNames
|
|
4706
|
+
}
|
|
4707
|
+
];
|
|
4708
|
+
} catch (error) {
|
|
4709
|
+
return [
|
|
4710
|
+
{
|
|
4711
|
+
provider,
|
|
4712
|
+
grouping,
|
|
4713
|
+
repository: options.repository,
|
|
4714
|
+
branchName: "",
|
|
4715
|
+
title,
|
|
4716
|
+
body,
|
|
4717
|
+
created: false,
|
|
4718
|
+
draft: options.draft,
|
|
4719
|
+
cveIds: plan.cveIds,
|
|
4720
|
+
packageNames: plan.packageNames,
|
|
4721
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4722
|
+
}
|
|
4723
|
+
];
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
|
|
4144
4727
|
// src/api/context.ts
|
|
4145
4728
|
function buildRequestId() {
|
|
4146
4729
|
return `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -4172,7 +4755,7 @@ function resolveConstraints(options, cwd) {
|
|
|
4172
4755
|
|
|
4173
4756
|
// src/platform/evidence.ts
|
|
4174
4757
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
4175
|
-
import { join as
|
|
4758
|
+
import { join as join16 } from "path";
|
|
4176
4759
|
function createEvidenceLog(cwd, cveIds, context = {}) {
|
|
4177
4760
|
return {
|
|
4178
4761
|
runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -4203,9 +4786,9 @@ function finalizeEvidence(log) {
|
|
|
4203
4786
|
return log;
|
|
4204
4787
|
}
|
|
4205
4788
|
function writeEvidenceLog(cwd, log) {
|
|
4206
|
-
const dir =
|
|
4789
|
+
const dir = join16(cwd, ".autoremediator", "evidence");
|
|
4207
4790
|
mkdirSync3(dir, { recursive: true });
|
|
4208
|
-
const filePath =
|
|
4791
|
+
const filePath = join16(dir, `${log.runId}.json`);
|
|
4209
4792
|
writeFileSync5(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
|
|
4210
4793
|
return filePath;
|
|
4211
4794
|
}
|
|
@@ -4261,7 +4844,9 @@ function addRemediateResultSteps(evidence, cveId, report) {
|
|
|
4261
4844
|
dryRun: result.dryRun,
|
|
4262
4845
|
unresolvedReason: result.unresolvedReason,
|
|
4263
4846
|
reachability: result.reachability?.status,
|
|
4264
|
-
hasAlternatives: Boolean(result.alternativeSuggestions?.length)
|
|
4847
|
+
hasAlternatives: Boolean(result.alternativeSuggestions?.length),
|
|
4848
|
+
suppressedBy: result.suppressedBy?.justification,
|
|
4849
|
+
regressionDetected: result.regressionDetected
|
|
4265
4850
|
}
|
|
4266
4851
|
);
|
|
4267
4852
|
}
|
|
@@ -4272,7 +4857,11 @@ function addRemediateResultSteps(evidence, cveId, report) {
|
|
|
4272
4857
|
{
|
|
4273
4858
|
resultCount: report.results.length,
|
|
4274
4859
|
vulnerableCount: report.vulnerablePackages.length,
|
|
4275
|
-
llmUsage: report.llmUsage
|
|
4860
|
+
llmUsage: report.llmUsage,
|
|
4861
|
+
exploitSignalTriggered: report.exploitSignalTriggered ?? false,
|
|
4862
|
+
slaBreachCount: report.slaBreaches?.length ?? 0,
|
|
4863
|
+
regressionDetectedCount: report.results.filter((r) => r.regressionDetected).length,
|
|
4864
|
+
sbomEntryCount: report.sbom?.length ?? 0
|
|
4276
4865
|
}
|
|
4277
4866
|
);
|
|
4278
4867
|
finalizeEvidence(evidence);
|
|
@@ -4367,12 +4956,24 @@ async function remediate(cveId, options = {}) {
|
|
|
4367
4956
|
constraints,
|
|
4368
4957
|
resumedFromCache: false
|
|
4369
4958
|
};
|
|
4959
|
+
if (options.changeRequest?.enabled) {
|
|
4960
|
+
finalReport.changeRequests = await createChangeRequestsForReports({
|
|
4961
|
+
cwd,
|
|
4962
|
+
options: options.changeRequest,
|
|
4963
|
+
reports: [finalReport]
|
|
4964
|
+
});
|
|
4965
|
+
}
|
|
4370
4966
|
if (options.idempotencyKey && !options.dryRun && !options.preview) {
|
|
4371
4967
|
storeIdempotentReport(cwd, options.idempotencyKey, normalizedCveId, finalReport);
|
|
4372
4968
|
}
|
|
4373
4969
|
return finalReport;
|
|
4374
4970
|
}
|
|
4375
4971
|
async function planRemediation(cveId, options = {}) {
|
|
4972
|
+
if (Object.hasOwn(options, "dryRun") || Object.hasOwn(options, "preview")) {
|
|
4973
|
+
throw new Error(
|
|
4974
|
+
"planRemediation always runs with dryRun=true and preview=true. Remove dryRun/preview from options."
|
|
4975
|
+
);
|
|
4976
|
+
}
|
|
4376
4977
|
return remediate(cveId, {
|
|
4377
4978
|
...options,
|
|
4378
4979
|
preview: true,
|
|
@@ -4381,12 +4982,12 @@ async function planRemediation(cveId, options = {}) {
|
|
|
4381
4982
|
}
|
|
4382
4983
|
|
|
4383
4984
|
// src/scanner/parse-input.ts
|
|
4384
|
-
import { extname } from "path";
|
|
4385
|
-
import { readFileSync as
|
|
4386
|
-
import { execa as
|
|
4985
|
+
import { extname as extname2 } from "path";
|
|
4986
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
4987
|
+
import { execa as execa12 } from "execa";
|
|
4387
4988
|
|
|
4388
4989
|
// src/scanner/adapters/npm-audit.ts
|
|
4389
|
-
import { readFileSync as
|
|
4990
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
4390
4991
|
var CVE_REGEX = /CVE-\d{4}-\d+/gi;
|
|
4391
4992
|
function normalizeSeverity(raw) {
|
|
4392
4993
|
if (!raw) return "UNKNOWN";
|
|
@@ -4420,12 +5021,12 @@ function parseNpmAuditJsonFromString(content) {
|
|
|
4420
5021
|
return findings;
|
|
4421
5022
|
}
|
|
4422
5023
|
function parseNpmAuditJsonFile(filePath) {
|
|
4423
|
-
const content =
|
|
5024
|
+
const content = readFileSync10(filePath, "utf8");
|
|
4424
5025
|
return parseNpmAuditJsonFromString(content);
|
|
4425
5026
|
}
|
|
4426
5027
|
|
|
4427
5028
|
// src/scanner/adapters/yarn-audit.ts
|
|
4428
|
-
import { readFileSync as
|
|
5029
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
4429
5030
|
var CVE_REGEX2 = /CVE-\d{4}-\d+/gi;
|
|
4430
5031
|
function normalizeSeverity2(raw) {
|
|
4431
5032
|
if (!raw) return "UNKNOWN";
|
|
@@ -4468,12 +5069,12 @@ function parseYarnAuditJsonFromString(content) {
|
|
|
4468
5069
|
return findings;
|
|
4469
5070
|
}
|
|
4470
5071
|
function parseYarnAuditJsonFile(filePath) {
|
|
4471
|
-
const content =
|
|
5072
|
+
const content = readFileSync11(filePath, "utf8");
|
|
4472
5073
|
return parseYarnAuditJsonFromString(content);
|
|
4473
5074
|
}
|
|
4474
5075
|
|
|
4475
5076
|
// src/scanner/adapters/sarif.ts
|
|
4476
|
-
import { readFileSync as
|
|
5077
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
4477
5078
|
var CVE_REGEX3 = /CVE-\d{4}-\d+/gi;
|
|
4478
5079
|
function extractPackageName(result) {
|
|
4479
5080
|
const pkg = result.properties?.["packageName"];
|
|
@@ -4505,7 +5106,7 @@ function parseSarifFromString(content) {
|
|
|
4505
5106
|
return findings;
|
|
4506
5107
|
}
|
|
4507
5108
|
function parseSarifFile(filePath) {
|
|
4508
|
-
const content =
|
|
5109
|
+
const content = readFileSync12(filePath, "utf8");
|
|
4509
5110
|
return parseSarifFromString(content);
|
|
4510
5111
|
}
|
|
4511
5112
|
|
|
@@ -4532,7 +5133,7 @@ async function parseScanInputFromAudit(params) {
|
|
|
4532
5133
|
}
|
|
4533
5134
|
const command = resolveAuditCommand(pm, { workspace: params.workspace });
|
|
4534
5135
|
const [cmd, ...args] = command;
|
|
4535
|
-
const result = await
|
|
5136
|
+
const result = await execa12(cmd, args, {
|
|
4536
5137
|
cwd: params.cwd,
|
|
4537
5138
|
stdio: "pipe",
|
|
4538
5139
|
reject: false
|
|
@@ -4568,10 +5169,10 @@ function ensureAuditFormatCompatibility(pm, resolved) {
|
|
|
4568
5169
|
}
|
|
4569
5170
|
}
|
|
4570
5171
|
function inferFormat(filePath) {
|
|
4571
|
-
const ext =
|
|
5172
|
+
const ext = extname2(filePath).toLowerCase();
|
|
4572
5173
|
if (ext === ".sarif") return "sarif";
|
|
4573
5174
|
try {
|
|
4574
|
-
const content =
|
|
5175
|
+
const content = readFileSync13(filePath, "utf8");
|
|
4575
5176
|
const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
|
|
4576
5177
|
if (firstLine) {
|
|
4577
5178
|
const parsed = JSON.parse(firstLine);
|
|
@@ -4839,6 +5440,11 @@ async function remediateFromScan(inputPath, options = {}) {
|
|
|
4839
5440
|
};
|
|
4840
5441
|
finalizeEvidence(evidence);
|
|
4841
5442
|
const evidenceFile = options.evidence === false ? void 0 : writeEvidenceLog(cwd, evidence);
|
|
5443
|
+
const changeRequests = options.changeRequest?.enabled ? await createChangeRequestsForReports({
|
|
5444
|
+
cwd,
|
|
5445
|
+
options: options.changeRequest,
|
|
5446
|
+
reports
|
|
5447
|
+
}) : void 0;
|
|
4842
5448
|
return {
|
|
4843
5449
|
schemaVersion: "1.0",
|
|
4844
5450
|
status,
|
|
@@ -4861,19 +5467,124 @@ async function remediateFromScan(inputPath, options = {}) {
|
|
|
4861
5467
|
idempotencyKey: options.idempotencyKey,
|
|
4862
5468
|
llmUsageCount: llmUsageTotals.llmUsageCount > 0 ? llmUsageTotals.llmUsageCount : void 0,
|
|
4863
5469
|
estimatedCostUsd: llmUsageTotals.estimatedCostUsd,
|
|
4864
|
-
totalLlmLatencyMs: llmUsageTotals.totalLlmLatencyMs
|
|
5470
|
+
totalLlmLatencyMs: llmUsageTotals.totalLlmLatencyMs,
|
|
5471
|
+
changeRequests
|
|
5472
|
+
};
|
|
5473
|
+
}
|
|
5474
|
+
|
|
5475
|
+
// src/api/portfolio/index.ts
|
|
5476
|
+
function toTargetStatusFromRemediation(report) {
|
|
5477
|
+
const failed = report.results.some((result) => !result.applied && !result.dryRun);
|
|
5478
|
+
const succeeded = report.results.some((result) => result.applied || result.dryRun);
|
|
5479
|
+
if (!failed) return "ok";
|
|
5480
|
+
return succeeded ? "partial" : "failed";
|
|
5481
|
+
}
|
|
5482
|
+
function normalizeTargetInput(target) {
|
|
5483
|
+
return {
|
|
5484
|
+
cveId: target.cveId?.trim() || void 0,
|
|
5485
|
+
inputPath: target.inputPath?.trim() || void 0,
|
|
5486
|
+
audit: target.audit ?? false
|
|
5487
|
+
};
|
|
5488
|
+
}
|
|
5489
|
+
function validateTarget(target, index) {
|
|
5490
|
+
if (!target.cwd || typeof target.cwd !== "string") {
|
|
5491
|
+
throw new Error(`Invalid portfolio target at index ${index}: cwd is required.`);
|
|
5492
|
+
}
|
|
5493
|
+
const { cveId, inputPath, audit } = normalizeTargetInput(target);
|
|
5494
|
+
const hasScanInput = Boolean(inputPath) || audit;
|
|
5495
|
+
if (Boolean(cveId) === hasScanInput) {
|
|
5496
|
+
throw new Error(
|
|
5497
|
+
`Invalid portfolio target at index ${index}: provide exactly one mode (cveId OR inputPath/audit).`
|
|
5498
|
+
);
|
|
5499
|
+
}
|
|
5500
|
+
}
|
|
5501
|
+
async function remediatePortfolio(targets, options = {}) {
|
|
5502
|
+
const normalizedTargets = Array.isArray(targets) ? targets : [];
|
|
5503
|
+
if (normalizedTargets.length === 0) {
|
|
5504
|
+
throw new Error("Portfolio requires at least one target.");
|
|
5505
|
+
}
|
|
5506
|
+
normalizedTargets.forEach((target, index) => validateTarget(target, index));
|
|
5507
|
+
const results = [];
|
|
5508
|
+
const changeRequests = [];
|
|
5509
|
+
let successCount = 0;
|
|
5510
|
+
let failedCount = 0;
|
|
5511
|
+
for (const target of normalizedTargets) {
|
|
5512
|
+
const { cveId, inputPath, audit } = normalizeTargetInput(target);
|
|
5513
|
+
try {
|
|
5514
|
+
if (cveId) {
|
|
5515
|
+
const remediationReport = await remediate(cveId, {
|
|
5516
|
+
...options,
|
|
5517
|
+
cwd: target.cwd,
|
|
5518
|
+
source: options.source
|
|
5519
|
+
});
|
|
5520
|
+
const status = toTargetStatusFromRemediation(remediationReport);
|
|
5521
|
+
results.push({
|
|
5522
|
+
target,
|
|
5523
|
+
status,
|
|
5524
|
+
remediationReport,
|
|
5525
|
+
changeRequests: remediationReport.changeRequests
|
|
5526
|
+
});
|
|
5527
|
+
if (remediationReport.changeRequests?.length) {
|
|
5528
|
+
changeRequests.push(...remediationReport.changeRequests);
|
|
5529
|
+
}
|
|
5530
|
+
if (status === "failed") {
|
|
5531
|
+
failedCount += 1;
|
|
5532
|
+
} else {
|
|
5533
|
+
successCount += 1;
|
|
5534
|
+
}
|
|
5535
|
+
continue;
|
|
5536
|
+
}
|
|
5537
|
+
const scanReport = await remediateFromScan(inputPath ?? "", {
|
|
5538
|
+
...options,
|
|
5539
|
+
cwd: target.cwd,
|
|
5540
|
+
audit,
|
|
5541
|
+
format: target.format,
|
|
5542
|
+
source: options.source
|
|
5543
|
+
});
|
|
5544
|
+
results.push({
|
|
5545
|
+
target,
|
|
5546
|
+
status: scanReport.status,
|
|
5547
|
+
scanReport,
|
|
5548
|
+
changeRequests: scanReport.changeRequests
|
|
5549
|
+
});
|
|
5550
|
+
if (scanReport.changeRequests?.length) {
|
|
5551
|
+
changeRequests.push(...scanReport.changeRequests);
|
|
5552
|
+
}
|
|
5553
|
+
if (scanReport.status === "failed") {
|
|
5554
|
+
failedCount += 1;
|
|
5555
|
+
} else {
|
|
5556
|
+
successCount += 1;
|
|
5557
|
+
}
|
|
5558
|
+
} catch (error) {
|
|
5559
|
+
results.push({
|
|
5560
|
+
target,
|
|
5561
|
+
status: "failed",
|
|
5562
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5563
|
+
});
|
|
5564
|
+
failedCount += 1;
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5567
|
+
const overallStatus = failedCount === 0 ? "ok" : successCount > 0 ? "partial" : "failed";
|
|
5568
|
+
return {
|
|
5569
|
+
schemaVersion: "1.0",
|
|
5570
|
+
status: overallStatus,
|
|
5571
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5572
|
+
targets: results,
|
|
5573
|
+
successCount,
|
|
5574
|
+
failedCount,
|
|
5575
|
+
changeRequests: changeRequests.length > 0 ? changeRequests : void 0
|
|
4865
5576
|
};
|
|
4866
5577
|
}
|
|
4867
5578
|
|
|
4868
5579
|
// src/api/update-outdated/index.ts
|
|
4869
|
-
import { join as
|
|
4870
|
-
import { readFileSync as
|
|
4871
|
-
import { execa as
|
|
5580
|
+
import { join as join17 } from "path";
|
|
5581
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
|
|
5582
|
+
import { execa as execa13 } from "execa";
|
|
4872
5583
|
async function applyBump(params) {
|
|
4873
|
-
const pkgPath =
|
|
5584
|
+
const pkgPath = join17(params.cwd, "package.json");
|
|
4874
5585
|
let pkgJson;
|
|
4875
5586
|
try {
|
|
4876
|
-
pkgJson = JSON.parse(
|
|
5587
|
+
pkgJson = JSON.parse(readFileSync14(pkgPath, "utf8"));
|
|
4877
5588
|
} catch {
|
|
4878
5589
|
return {
|
|
4879
5590
|
applied: false,
|
|
@@ -4898,7 +5609,7 @@ async function applyBump(params) {
|
|
|
4898
5609
|
writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
4899
5610
|
try {
|
|
4900
5611
|
const [installCmd, ...installArgs] = params.installCommand;
|
|
4901
|
-
await
|
|
5612
|
+
await execa13(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
|
|
4902
5613
|
} catch (err) {
|
|
4903
5614
|
pkgJson[depField][params.packageName] = currentRange;
|
|
4904
5615
|
writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
@@ -4911,13 +5622,13 @@ async function applyBump(params) {
|
|
|
4911
5622
|
if (params.runTests) {
|
|
4912
5623
|
try {
|
|
4913
5624
|
const [testCmd, ...testArgs] = params.testCommand;
|
|
4914
|
-
await
|
|
5625
|
+
await execa13(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
|
|
4915
5626
|
} catch (err) {
|
|
4916
5627
|
pkgJson[depField][params.packageName] = currentRange;
|
|
4917
5628
|
writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
4918
5629
|
try {
|
|
4919
5630
|
const [rollbackCmd, ...rollbackArgs] = params.installCommand;
|
|
4920
|
-
await
|
|
5631
|
+
await execa13(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
|
|
4921
5632
|
} catch {
|
|
4922
5633
|
}
|
|
4923
5634
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -5070,6 +5781,28 @@ async function updateOutdated(options = {}) {
|
|
|
5070
5781
|
};
|
|
5071
5782
|
finalizeEvidence(evidence);
|
|
5072
5783
|
const evidenceFile = options.evidence === false ? void 0 : writeEvidenceLog(cwd, evidence);
|
|
5784
|
+
const changeRequests = options.changeRequest?.enabled && successCount > 0 ? await createChangeRequestsForReports({
|
|
5785
|
+
cwd,
|
|
5786
|
+
options: options.changeRequest,
|
|
5787
|
+
reports: [
|
|
5788
|
+
{
|
|
5789
|
+
cveId: "UPDATE-OUTDATED",
|
|
5790
|
+
cveDetails: null,
|
|
5791
|
+
vulnerablePackages: [],
|
|
5792
|
+
results: outdatedPackages.filter((pkg) => !pkg.isMajorBump).map((pkg) => ({
|
|
5793
|
+
packageName: pkg.name,
|
|
5794
|
+
strategy: "version-bump",
|
|
5795
|
+
fromVersion: pkg.currentVersion,
|
|
5796
|
+
toVersion: pkg.latestVersion,
|
|
5797
|
+
applied: true,
|
|
5798
|
+
dryRun: Boolean(options.dryRun),
|
|
5799
|
+
message: `Updated ${pkg.name} to ${pkg.latestVersion}`
|
|
5800
|
+
})),
|
|
5801
|
+
agentSteps: 0,
|
|
5802
|
+
summary: "update-outdated"
|
|
5803
|
+
}
|
|
5804
|
+
]
|
|
5805
|
+
}) : void 0;
|
|
5073
5806
|
return {
|
|
5074
5807
|
schemaVersion: "1.0",
|
|
5075
5808
|
status,
|
|
@@ -5083,7 +5816,8 @@ async function updateOutdated(options = {}) {
|
|
|
5083
5816
|
patchCount: 0,
|
|
5084
5817
|
constraints,
|
|
5085
5818
|
correlation,
|
|
5086
|
-
provenance
|
|
5819
|
+
provenance,
|
|
5820
|
+
changeRequests
|
|
5087
5821
|
};
|
|
5088
5822
|
}
|
|
5089
5823
|
|
|
@@ -5151,9 +5885,9 @@ function toSarifOutput(report) {
|
|
|
5151
5885
|
}
|
|
5152
5886
|
|
|
5153
5887
|
// src/version.ts
|
|
5154
|
-
import { readFileSync as
|
|
5888
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
5155
5889
|
function readPackageVersion() {
|
|
5156
|
-
const raw =
|
|
5890
|
+
const raw = readFileSync15(new URL("../package.json", import.meta.url), "utf8");
|
|
5157
5891
|
const metadata = JSON.parse(raw);
|
|
5158
5892
|
if (!metadata.version) {
|
|
5159
5893
|
throw new Error("packages/core/package.json is missing a version field.");
|
|
@@ -5177,7 +5911,8 @@ export {
|
|
|
5177
5911
|
remediate,
|
|
5178
5912
|
planRemediation,
|
|
5179
5913
|
remediateFromScan,
|
|
5914
|
+
remediatePortfolio,
|
|
5180
5915
|
updateOutdated,
|
|
5181
5916
|
PACKAGE_VERSION
|
|
5182
5917
|
};
|
|
5183
|
-
//# sourceMappingURL=chunk-
|
|
5918
|
+
//# sourceMappingURL=chunk-575GEUAY.js.map
|