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.
@@ -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 readFileSync5 } from "fs";
1129
- import { join as join7 } from "path";
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(readFileSync5(join7(cwd, "package.json"), "utf8"));
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 tool2 } from "ai";
1328
- import { z as z2 } from "zod";
1329
- import { join as join9 } from "path";
1330
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
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 join8 } from "path";
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 = join8(cwd, ".autoremediator", "locks");
1344
- const lockPath = join8(cwd, ".autoremediator", "locks", "remediation.lock");
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 = tool2({
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: z2.object({
1377
- cwd: z2.string().describe("Absolute path to the consumer project root"),
1378
- packageManager: z2.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
1379
- packageName: z2.string().describe("The npm package to upgrade"),
1380
- fromVersion: z2.string().describe("The currently installed vulnerable version"),
1381
- toVersion: z2.string().describe("The safe target version to upgrade to"),
1382
- dryRun: z2.boolean().default(false).describe("If true, report changes but do not write"),
1383
- policy: z2.string().optional().describe("Optional path to .autoremediator policy file"),
1384
- runTests: z2.boolean().default(false).describe("If true, run test validation after applying the fix"),
1385
- installMode: z2.enum(["standard", "prefer-offline", "deterministic"]).optional(),
1386
- installPreferOffline: z2.boolean().optional(),
1387
- enforceFrozenLockfile: z2.boolean().optional(),
1388
- workspace: z2.string().optional()
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 = join9(cwd, "package.json");
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(readFileSync6(pkgPath, "utf8"));
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 tool3 } from "ai";
1569
- import { z as z3 } from "zod";
1570
- import { join as join10 } from "path";
1571
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
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 = tool3({
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: z3.object({
1654
- cwd: z3.string().describe("Absolute path to the consumer project root"),
1655
- packageManager: z3.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
1656
- packageName: z3.string().describe("The npm package to override"),
1657
- selector: z3.string().optional().describe("Optional manager-native override selector key (for nested or scoped overrides)"),
1658
- fromVersion: z3.string().describe("The currently installed vulnerable version"),
1659
- toVersion: z3.string().describe("The safe target version to override to"),
1660
- dryRun: z3.boolean().default(false).describe("If true, report changes but do not write"),
1661
- policy: z3.string().optional().describe("Optional path to .autoremediator policy file"),
1662
- runTests: z3.boolean().default(false).describe("If true, run test validation after applying the override"),
1663
- installMode: z3.enum(["standard", "prefer-offline", "deterministic"]).optional(),
1664
- installPreferOffline: z3.boolean().optional(),
1665
- enforceFrozenLockfile: z3.boolean().optional(),
1666
- workspace: z3.string().optional()
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 = join10(cwd, "package.json");
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(readFileSync7(pkgPath, "utf8"));
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 tool4 } from "ai";
1963
- import { z as z4 } from "zod";
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 join11 } from "path";
2235
+ import { join as join12 } from "path";
1966
2236
  import { execa as execa8 } from "execa";
1967
- var fetchPackageSourceTool = tool4({
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: z4.object({
1970
- packageName: z4.string().min(1).describe("The npm package name (e.g., 'lodash', '@scope/package')"),
1971
- version: z4.string().regex(/^\d+\.\d+\.\d+/, "Must be a valid semver version").describe("Exact package version to download"),
1972
- filePatterns: z4.array(z4.string()).optional().default(["*.js", "*.ts"]).describe(
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 = join11(tempBaseDir, "out");
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 = join11(tempBaseDir, "package.tgz");
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") ? join11(extractDir, "package") : extractDir;
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 = join11(dir, file.name);
1998
- const relPath = join11(relativeBase, file.name);
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 tool5 } from "ai";
2062
- import { z as z5 } from "zod";
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 = tool5({
2465
+ var generatePatchTool = tool7({
2196
2466
  description: "Generate a unified diff patch for a CVE using LLM analysis of vulnerable source code.",
2197
- parameters: z5.object({
2198
- packageName: z5.string().min(1).describe("The npm package name"),
2199
- vulnerableVersion: z5.string().describe("The vulnerable version string"),
2200
- cveId: z5.string().regex(/^CVE-\d{4}-\d+$/i).describe("CVE ID (e.g., CVE-2021-23337)"),
2201
- cveSummary: z5.string().min(10).describe("CVE description and impact"),
2202
- sourceFiles: z5.record(z5.string()).describe(
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: z5.enum(["redos", "code-injection", "path-traversal", "unknown"]).optional().default("unknown").describe("Category of the vulnerability for better context"),
2206
- dryRun: z5.boolean().optional().default(false).describe("If true, return analysis without generating patches"),
2207
- llmProvider: z5.enum(["remote", "local"]).optional().describe("Optional provider override for patch generation"),
2208
- model: z5.string().optional().describe("Optional model override for patch generation"),
2209
- policy: z5.string().optional().describe("Optional policy file path for model default resolution"),
2210
- cwd: z5.string().optional().describe("Optional working directory for policy/model resolution"),
2211
- providerSafetyProfile: z5.enum(["strict", "relaxed"]).optional().describe("Confidence threshold profile for patch acceptance"),
2212
- patchConfidenceThresholds: z5.object({
2213
- low: z5.number().min(0).max(1).optional(),
2214
- medium: z5.number().min(0).max(1).optional(),
2215
- high: z5.number().min(0).max(1).optional()
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: z5.boolean().optional().describe("Enable dynamic model routing by input size"),
2218
- dynamicRoutingThresholdChars: z5.number().int().positive().optional().describe("Threshold for dynamic model routing"),
2219
- modelPersonality: z5.enum(["analytical", "pragmatic", "balanced"]).optional().describe("Prompt personality for patch-generation guidance")
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 tool6 } from "ai";
2370
- import { z as z6 } from "zod";
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 join13 } from "path";
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 join12 } from "path";
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 = join12(cwd, "package.json");
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 = join12(cwd, "package.json");
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(join12(tmpdir(), "autoremediator-native-patch-"));
2521
- const tempPatchFile = join12(tempPatchDir, "change.patch");
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 = tool6({
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: z6.object({
2609
- packageName: z6.string().min(1).describe("The npm package name"),
2610
- vulnerableVersion: z6.string().describe("The vulnerable version string"),
2611
- patchContent: z6.string().min(10).optional().describe("Unified diff patch content from generate-patch"),
2612
- cveId: z6.string().optional().describe("Optional CVE ID associated with this patch artifact"),
2613
- confidence: z6.number().min(0).max(1).optional().describe("Optional patch confidence score from generate-patch"),
2614
- riskLevel: z6.enum(["low", "medium", "high"]).optional().describe("Optional risk level from generate-patch"),
2615
- patches: z6.array(
2616
- z6.object({
2617
- filePath: z6.string().min(1),
2618
- unifiedDiff: z6.string().min(10)
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: z6.string().optional().default("./patches").describe("Directory to store patch files"),
2622
- cwd: z6.string().describe("Project root directory (for package.json)"),
2623
- packageManager: z6.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
2624
- policy: z6.string().optional().describe("Optional path to .autoremediator policy file"),
2625
- installMode: z6.enum(["standard", "prefer-offline", "deterministic"]).optional(),
2626
- installPreferOffline: z6.boolean().optional(),
2627
- enforceFrozenLockfile: z6.boolean().optional(),
2628
- workspace: z6.string().optional(),
2629
- validateWithTests: z6.boolean().optional().default(true).describe("Run package manager test command to validate patch doesn't break anything"),
2630
- dryRun: z6.boolean().optional().default(false).describe("If true, report but do not mutate files")
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 = join13(cwd, patchesDir, patchFileName);
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 = join13(cwd, patchesDir);
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
- collectedResults.push({
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 readFileSync8 } from "fs";
3444
- import { join as join14 } from "path";
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 = join14(process.cwd(), ".github", "instructions", "orchestration.instructions.md");
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 = readFileSync8(promptPath, "utf8");
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 tool7 } from "ai";
3502
- import { z as z7 } from "zod";
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 = tool7({
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: z7.object({
3783
- cveId: z7.string().regex(/^CVE-\d{4}-\d+$/i, "Must be a valid CVE ID like CVE-2021-23337")
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 tool8 } from "ai";
3850
- import { z as z8 } from "zod";
3851
- import semver5 from "semver";
3852
- var affectedPackageSchema = z8.object({
3853
- name: z8.string(),
3854
- ecosystem: z8.literal("npm"),
3855
- vulnerableRange: z8.string(),
3856
- firstPatchedVersion: z8.string().optional(),
3857
- source: z8.enum(["osv", "github-advisory"])
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 = z8.object({
3860
- name: z8.string(),
3861
- version: z8.string(),
3862
- type: z8.enum(["direct", "indirect"])
4205
+ var inventoryPackageSchema = z10.object({
4206
+ name: z10.string(),
4207
+ version: z10.string(),
4208
+ type: z10.enum(["direct", "indirect"])
3863
4209
  });
3864
- var checkVersionMatchTool = tool8({
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: z8.object({
3867
- installedPackages: z8.array(inventoryPackageSchema).describe("Output from the check-inventory tool"),
3868
- affectedPackages: z8.array(affectedPackageSchema).describe("affectedPackages array from the lookup-cve tool result")
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 (!semver5.valid(installed.version)) continue;
4223
+ if (!semver6.valid(installed.version)) continue;
3878
4224
  let isVulnerable = false;
3879
4225
  try {
3880
- isVulnerable = semver5.satisfies(installed.version, affected.vulnerableRange, {
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 tool9 } from "ai";
3900
- import { z as z9 } from "zod";
3901
- var findFixedVersionTool = tool9({
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: z9.object({
3904
- packageName: z9.string().describe("The npm package name"),
3905
- installedVersion: z9.string().describe("The currently installed version (exact semver)"),
3906
- firstPatchedVersion: z9.string().describe(
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: z9.string().optional().describe("Optional vulnerable semver range used to exclude still-vulnerable versions")
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 join15 } from "path";
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 = join15(cwd, ".autoremediator", "evidence");
4585
+ const dir = join16(cwd, ".autoremediator", "evidence");
4207
4586
  mkdirSync3(dir, { recursive: true });
4208
- const filePath = join15(dir, `${log.runId}.json`);
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 readFileSync12 } from "fs";
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 readFileSync9 } from "fs";
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 = readFileSync9(filePath, "utf8");
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 readFileSync10 } from "fs";
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 = readFileSync10(filePath, "utf8");
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 readFileSync11 } from "fs";
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 = readFileSync11(filePath, "utf8");
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 = extname(filePath).toLowerCase();
4956
+ const ext = extname2(filePath).toLowerCase();
4572
4957
  if (ext === ".sarif") return "sarif";
4573
4958
  try {
4574
- const content = readFileSync12(filePath, "utf8");
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 join16 } from "path";
4870
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
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 = join16(params.cwd, "package.json");
5258
+ const pkgPath = join17(params.cwd, "package.json");
4874
5259
  let pkgJson;
4875
5260
  try {
4876
- pkgJson = JSON.parse(readFileSync13(pkgPath, "utf8"));
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 readFileSync14 } from "fs";
5539
+ import { readFileSync as readFileSync15 } from "fs";
5155
5540
  function readPackageVersion() {
5156
- const raw = readFileSync14(new URL("../package.json", import.meta.url), "utf8");
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-F7W4EYJL.js.map
5568
+ //# sourceMappingURL=chunk-GYCZ6L3O.js.map