autoremediator 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/api/change-request/index.d.ts +2 -1
- package/dist/api/change-request/index.d.ts.map +1 -1
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/patches/index.d.ts +1 -0
- package/dist/api/patches/index.d.ts.map +1 -1
- package/dist/api/patches/reconcile.d.ts +7 -0
- package/dist/api/patches/reconcile.d.ts.map +1 -0
- package/dist/api/patches/reconcile.test.d.ts +2 -0
- package/dist/api/patches/reconcile.test.d.ts.map +1 -0
- package/dist/api/remediate/validators.d.ts +3 -0
- package/dist/api/remediate/validators.d.ts.map +1 -1
- package/dist/{chunk-U4LIJIGD.js → chunk-FXOUJDQ3.js} +1712 -856
- package/dist/cli/program.d.ts.map +1 -1
- package/dist/cli/runners.d.ts +5 -0
- package/dist/cli/runners.d.ts.map +1 -1
- package/dist/cli/triage.d.ts +7 -0
- package/dist/cli/triage.d.ts.map +1 -0
- package/dist/cli/types.d.ts +2 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli.js +220 -3
- package/dist/index.js +1707 -852
- package/dist/index.js.map +1 -1
- package/dist/intelligence/sources/github-advisory.d.ts.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/openapi/server.js +1 -1
- package/dist/platform/package-manager/index.d.ts +1 -0
- package/dist/platform/package-manager/index.d.ts.map +1 -1
- package/dist/platform/supply-chain.d.ts +10 -0
- package/dist/platform/supply-chain.d.ts.map +1 -0
- package/dist/platform/supply-chain.test.d.ts +2 -0
- package/dist/platform/supply-chain.test.d.ts.map +1 -0
- package/dist/platform/types.d.ts +38 -4
- package/dist/platform/types.d.ts.map +1 -1
- package/dist/platform/workspace.d.ts +28 -0
- package/dist/platform/workspace.d.ts.map +1 -0
- package/dist/platform/workspace.test.d.ts +2 -0
- package/dist/platform/workspace.test.d.ts.map +1 -0
- package/dist/remediation/local/options.d.ts.map +1 -1
- package/dist/remediation/local/primary-strategy.d.ts +3 -1
- package/dist/remediation/local/primary-strategy.d.ts.map +1 -1
- package/dist/remediation/strategies/patch-synthesis-prompt.d.ts.map +1 -1
- package/dist/remediation/tools/apply-patch-file/index.d.ts +1 -0
- package/dist/remediation/tools/apply-patch-file/index.d.ts.map +1 -1
- package/dist/remediation/tools/apply-version-bump.d.ts +1 -0
- package/dist/remediation/tools/apply-version-bump.d.ts.map +1 -1
- package/dist/remediation/tools/check-inventory.d.ts.map +1 -1
- package/dist/remediation/tools/check-reachability.d.ts +6 -1
- package/dist/remediation/tools/check-reachability.d.ts.map +1 -1
- package/dist/remediation/tools/generate-patch/helpers.d.ts +1 -0
- package/dist/remediation/tools/generate-patch/helpers.d.ts.map +1 -1
- package/dist/remediation/tools/generate-patch/index.d.ts +1 -0
- package/dist/remediation/tools/generate-patch/index.d.ts.map +1 -1
- package/dist/scanner/adapters/npm-audit.d.ts.map +1 -1
- package/dist/scanner/adapters/yarn-audit.d.ts.map +1 -1
- package/dist/scanner/delta-scan.d.ts.map +1 -1
- package/dist/scanner/lockfile-diff.d.ts +10 -0
- package/dist/scanner/lockfile-diff.d.ts.map +1 -0
- package/dist/scanner/lockfile-diff.test.d.ts +2 -0
- package/dist/scanner/lockfile-diff.test.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -887,12 +887,12 @@ import { readdir, readFile as readFile2, stat } from "fs/promises";
|
|
|
887
887
|
import { join as join5 } from "path";
|
|
888
888
|
|
|
889
889
|
// src/remediation/strategies/patch-utils.ts
|
|
890
|
-
import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as
|
|
890
|
+
import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as readFileSync3 } from "fs";
|
|
891
891
|
import { join as join4 } from "path";
|
|
892
892
|
import { execa as execa2 } from "execa";
|
|
893
893
|
|
|
894
894
|
// src/platform/package-manager/index.ts
|
|
895
|
-
import { existsSync as existsSync2 } from "fs";
|
|
895
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
896
896
|
import { join as join3 } from "path";
|
|
897
897
|
import { execa } from "execa";
|
|
898
898
|
|
|
@@ -1085,6 +1085,32 @@ function resolveTestCommand(pm, constraints) {
|
|
|
1085
1085
|
const base = pm === "pnpm" ? ["pnpm", "test"] : pm === "yarn" ? ["yarn", "test"] : pm === "bun" ? ["bun", "test"] : pm === "deno" ? ["deno", "test"] : ["npm", "test"];
|
|
1086
1086
|
return withWorkspace(base, pm, constraints?.workspace);
|
|
1087
1087
|
}
|
|
1088
|
+
function resolveTypecheckCommand(pm, constraints, cwd = process.cwd()) {
|
|
1089
|
+
const pkgJsonPath = join3(cwd, "package.json");
|
|
1090
|
+
let hasTypecheckScript = false;
|
|
1091
|
+
const hasTsConfig = existsSync2(join3(cwd, "tsconfig.json"));
|
|
1092
|
+
if (existsSync2(pkgJsonPath)) {
|
|
1093
|
+
try {
|
|
1094
|
+
const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf8"));
|
|
1095
|
+
if (pkg.scripts?.typecheck) {
|
|
1096
|
+
hasTypecheckScript = true;
|
|
1097
|
+
}
|
|
1098
|
+
} catch {
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
if (hasTypecheckScript) {
|
|
1102
|
+
const base = [pm, "run", "typecheck"];
|
|
1103
|
+
return withWorkspace(base, pm, constraints?.workspace);
|
|
1104
|
+
}
|
|
1105
|
+
if (hasTsConfig) {
|
|
1106
|
+
if (pm === "deno") {
|
|
1107
|
+
return withWorkspace(["deno", "check"], pm, constraints?.workspace);
|
|
1108
|
+
}
|
|
1109
|
+
const base = ["npx", "tsc", "--noEmit"];
|
|
1110
|
+
return withWorkspace(base, pm, constraints?.workspace);
|
|
1111
|
+
}
|
|
1112
|
+
return [];
|
|
1113
|
+
}
|
|
1088
1114
|
function resolveAuditCommand(pm, constraints) {
|
|
1089
1115
|
if (pm === "deno") {
|
|
1090
1116
|
throw new Error(
|
|
@@ -1329,12 +1355,12 @@ function defineTool(config) {
|
|
|
1329
1355
|
|
|
1330
1356
|
// src/remediation/tools/check-inventory.ts
|
|
1331
1357
|
import { z } from "zod";
|
|
1332
|
-
import { readFileSync as
|
|
1333
|
-
import { join as
|
|
1358
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
1359
|
+
import { join as join8 } from "path";
|
|
1334
1360
|
import { execa as execa3 } from "execa";
|
|
1335
1361
|
|
|
1336
1362
|
// src/platform/policy.ts
|
|
1337
|
-
import { existsSync as existsSync6, readFileSync as
|
|
1363
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
1338
1364
|
import { join as join6 } from "path";
|
|
1339
1365
|
import { parse as yamlParse } from "yaml";
|
|
1340
1366
|
var DEFAULT_POLICY = {
|
|
@@ -1369,7 +1395,7 @@ function loadPolicy(cwd, explicitPath) {
|
|
|
1369
1395
|
const candidate = explicitPath ?? join6(cwd, ".github", "autoremediator.yml");
|
|
1370
1396
|
if (!existsSync6(candidate)) return DEFAULT_POLICY;
|
|
1371
1397
|
try {
|
|
1372
|
-
const parsed = yamlParse(
|
|
1398
|
+
const parsed = yamlParse(readFileSync4(candidate, "utf8"));
|
|
1373
1399
|
return {
|
|
1374
1400
|
allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
|
|
1375
1401
|
denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
|
|
@@ -1430,7 +1456,7 @@ function isActiveSuppression(suppression) {
|
|
|
1430
1456
|
}
|
|
1431
1457
|
function loadSuppressionsFile(filePath) {
|
|
1432
1458
|
try {
|
|
1433
|
-
const content =
|
|
1459
|
+
const content = readFileSync4(filePath, "utf8");
|
|
1434
1460
|
const parsed = yamlParse(content);
|
|
1435
1461
|
return Array.isArray(parsed?.suppressions) ? parsed.suppressions : [];
|
|
1436
1462
|
} catch {
|
|
@@ -1456,6 +1482,140 @@ function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
|
|
|
1456
1482
|
};
|
|
1457
1483
|
}
|
|
1458
1484
|
|
|
1485
|
+
// src/platform/workspace.ts
|
|
1486
|
+
import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync5, statSync } from "fs";
|
|
1487
|
+
import { join as join7, resolve as resolve2, relative } from "path";
|
|
1488
|
+
import yaml from "yaml";
|
|
1489
|
+
function findWorkspaceRoot(startDir) {
|
|
1490
|
+
let curr = resolve2(startDir);
|
|
1491
|
+
while (true) {
|
|
1492
|
+
if (existsSync7(join7(curr, "pnpm-workspace.yaml"))) return curr;
|
|
1493
|
+
const pkgPath = join7(curr, "package.json");
|
|
1494
|
+
if (existsSync7(pkgPath)) {
|
|
1495
|
+
try {
|
|
1496
|
+
const parsed = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
1497
|
+
if (parsed.workspaces) return curr;
|
|
1498
|
+
} catch {
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
const parent = resolve2(curr, "..");
|
|
1502
|
+
if (parent === curr) break;
|
|
1503
|
+
curr = parent;
|
|
1504
|
+
}
|
|
1505
|
+
return null;
|
|
1506
|
+
}
|
|
1507
|
+
function findWorkspacePackages(cwd) {
|
|
1508
|
+
const rootCwd = findWorkspaceRoot(cwd) ?? resolve2(cwd);
|
|
1509
|
+
const packages = [];
|
|
1510
|
+
const packageGlobs = [];
|
|
1511
|
+
const pnpmWorkspaceFile = join7(rootCwd, "pnpm-workspace.yaml");
|
|
1512
|
+
if (existsSync7(pnpmWorkspaceFile)) {
|
|
1513
|
+
try {
|
|
1514
|
+
const content = readFileSync5(pnpmWorkspaceFile, "utf8");
|
|
1515
|
+
const parsed = yaml.parse(content);
|
|
1516
|
+
if (Array.isArray(parsed?.packages)) {
|
|
1517
|
+
packageGlobs.push(...parsed.packages);
|
|
1518
|
+
}
|
|
1519
|
+
} catch {
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
const rootPkgFile = join7(rootCwd, "package.json");
|
|
1523
|
+
if (existsSync7(rootPkgFile)) {
|
|
1524
|
+
try {
|
|
1525
|
+
const rootPkg = JSON.parse(readFileSync5(rootPkgFile, "utf8"));
|
|
1526
|
+
if (Array.isArray(rootPkg.workspaces)) {
|
|
1527
|
+
packageGlobs.push(...rootPkg.workspaces);
|
|
1528
|
+
} else if (Array.isArray(rootPkg.workspaces?.packages)) {
|
|
1529
|
+
packageGlobs.push(...rootPkg.workspaces.packages);
|
|
1530
|
+
}
|
|
1531
|
+
} catch {
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
const denoJsonFile = join7(rootCwd, "deno.json");
|
|
1535
|
+
if (existsSync7(denoJsonFile)) {
|
|
1536
|
+
try {
|
|
1537
|
+
const denoJson = JSON.parse(readFileSync5(denoJsonFile, "utf8"));
|
|
1538
|
+
if (Array.isArray(denoJson.workspace)) {
|
|
1539
|
+
packageGlobs.push(...denoJson.workspace);
|
|
1540
|
+
}
|
|
1541
|
+
} catch {
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
if (packageGlobs.length === 0) {
|
|
1545
|
+
return packages;
|
|
1546
|
+
}
|
|
1547
|
+
const seenDirs = /* @__PURE__ */ new Set();
|
|
1548
|
+
for (const glob of packageGlobs) {
|
|
1549
|
+
const cleanGlob = glob.trim().replace(/\/+$/, "");
|
|
1550
|
+
if (!cleanGlob || cleanGlob.startsWith("!")) continue;
|
|
1551
|
+
if (cleanGlob.endsWith("/*")) {
|
|
1552
|
+
const parentDir = join7(rootCwd, cleanGlob.slice(0, -2));
|
|
1553
|
+
if (!existsSync7(parentDir)) continue;
|
|
1554
|
+
try {
|
|
1555
|
+
const entries = readdirSync(parentDir);
|
|
1556
|
+
for (const entry of entries) {
|
|
1557
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
1558
|
+
const subDir = join7(parentDir, entry);
|
|
1559
|
+
try {
|
|
1560
|
+
if (statSync(subDir).isDirectory()) {
|
|
1561
|
+
addWorkspacePackage(rootCwd, subDir, packages, seenDirs);
|
|
1562
|
+
}
|
|
1563
|
+
} catch {
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
} catch {
|
|
1567
|
+
}
|
|
1568
|
+
} else {
|
|
1569
|
+
const directDir = join7(rootCwd, cleanGlob);
|
|
1570
|
+
if (existsSync7(directDir)) {
|
|
1571
|
+
try {
|
|
1572
|
+
if (statSync(directDir).isDirectory()) {
|
|
1573
|
+
addWorkspacePackage(rootCwd, directDir, packages, seenDirs);
|
|
1574
|
+
}
|
|
1575
|
+
} catch {
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
return packages;
|
|
1581
|
+
}
|
|
1582
|
+
function addWorkspacePackage(rootCwd, dir, out, seen) {
|
|
1583
|
+
const norm = resolve2(dir);
|
|
1584
|
+
if (seen.has(norm)) return;
|
|
1585
|
+
seen.add(norm);
|
|
1586
|
+
const manifestPath = join7(dir, "package.json");
|
|
1587
|
+
if (!existsSync7(manifestPath)) return;
|
|
1588
|
+
try {
|
|
1589
|
+
const content = readFileSync5(manifestPath, "utf8");
|
|
1590
|
+
const manifest = JSON.parse(content);
|
|
1591
|
+
out.push({
|
|
1592
|
+
name: manifest.name ?? relative(rootCwd, dir),
|
|
1593
|
+
relativeDir: relative(rootCwd, dir),
|
|
1594
|
+
absoluteDir: norm,
|
|
1595
|
+
manifestPath,
|
|
1596
|
+
manifest
|
|
1597
|
+
});
|
|
1598
|
+
} catch {
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
function findDeclaringWorkspacePackage(cwd, dependencyName, filterWorkspace) {
|
|
1602
|
+
const workspaces = findWorkspacePackages(cwd);
|
|
1603
|
+
if (workspaces.length === 0) return null;
|
|
1604
|
+
const matchesFilter = (wp) => {
|
|
1605
|
+
if (!filterWorkspace) return true;
|
|
1606
|
+
const filter = filterWorkspace.trim();
|
|
1607
|
+
return wp.name === filter || wp.relativeDir === filter || wp.relativeDir.replace(/\\/g, "/") === filter.replace(/\\/g, "/");
|
|
1608
|
+
};
|
|
1609
|
+
for (const wp of workspaces) {
|
|
1610
|
+
if (!matchesFilter(wp)) continue;
|
|
1611
|
+
const isDeclared = Boolean(wp.manifest.dependencies?.[dependencyName]) || Boolean(wp.manifest.devDependencies?.[dependencyName]) || Boolean(wp.manifest.peerDependencies?.[dependencyName]);
|
|
1612
|
+
if (isDeclared) {
|
|
1613
|
+
return wp;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1459
1619
|
// src/remediation/tools/check-inventory.ts
|
|
1460
1620
|
var checkInventoryTool = defineTool({
|
|
1461
1621
|
description: "Read the project's package.json and installed dependencies to list packages and exact versions. Must be called before checking version matches.",
|
|
@@ -1473,7 +1633,7 @@ var checkInventoryTool = defineTool({
|
|
|
1473
1633
|
}) => {
|
|
1474
1634
|
let pkgJson;
|
|
1475
1635
|
try {
|
|
1476
|
-
pkgJson = JSON.parse(
|
|
1636
|
+
pkgJson = JSON.parse(readFileSync6(join8(cwd, "package.json"), "utf8"));
|
|
1477
1637
|
} catch {
|
|
1478
1638
|
return {
|
|
1479
1639
|
packages: [],
|
|
@@ -1505,7 +1665,8 @@ var checkInventoryTool = defineTool({
|
|
|
1505
1665
|
}
|
|
1506
1666
|
const packages = [];
|
|
1507
1667
|
for (const [name, version] of installedVersions.entries()) {
|
|
1508
|
-
const
|
|
1668
|
+
const declaringWorkspace = findDeclaringWorkspacePackage(cwd, name, workspace);
|
|
1669
|
+
const isDirect = Boolean(pkgJson.dependencies?.[name]) || Boolean(pkgJson.devDependencies?.[name]) || Boolean(pkgJson.peerDependencies?.[name]) || Boolean(declaringWorkspace);
|
|
1509
1670
|
packages.push({
|
|
1510
1671
|
name,
|
|
1511
1672
|
version,
|
|
@@ -1628,173 +1789,14 @@ async function validatePatchArtifact(patchFilePath, options = {}) {
|
|
|
1628
1789
|
};
|
|
1629
1790
|
}
|
|
1630
1791
|
|
|
1631
|
-
// src/
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
`Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
|
|
1637
|
-
);
|
|
1638
|
-
}
|
|
1639
|
-
return raw;
|
|
1640
|
-
}
|
|
1641
|
-
function resolveModelName(provider, options = {}, routing = {}) {
|
|
1642
|
-
if (options.model) return options.model;
|
|
1643
|
-
if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
|
|
1644
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1645
|
-
const policy = loadPolicy(cwd, options.policy);
|
|
1646
|
-
const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
|
|
1647
|
-
if (providerEnvModel) return providerEnvModel;
|
|
1648
|
-
const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
|
|
1649
|
-
if (policyPinnedModel) return policyPinnedModel;
|
|
1650
|
-
const defaults = {
|
|
1651
|
-
remote: "remote-default",
|
|
1652
|
-
local: "local"
|
|
1653
|
-
};
|
|
1654
|
-
const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
|
|
1655
|
-
const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
|
|
1656
|
-
if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
|
|
1657
|
-
return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
|
|
1658
|
-
}
|
|
1659
|
-
return defaults[provider];
|
|
1660
|
-
}
|
|
1661
|
-
async function loadRemoteFactory() {
|
|
1662
|
-
const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
|
|
1663
|
-
const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
|
|
1664
|
-
if (!moduleName) {
|
|
1665
|
-
throw new Error(
|
|
1666
|
-
"AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
|
|
1667
|
-
);
|
|
1668
|
-
}
|
|
1669
|
-
if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
|
|
1670
|
-
throw new Error(
|
|
1671
|
-
`AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
|
|
1672
|
-
);
|
|
1673
|
-
}
|
|
1674
|
-
const loaded = await import(moduleName);
|
|
1675
|
-
const factory = loaded[exportName];
|
|
1676
|
-
if (typeof factory !== "function") {
|
|
1677
|
-
throw new Error(
|
|
1678
|
-
`Remote client factory "${exportName}" was not found in module "${moduleName}".`
|
|
1679
|
-
);
|
|
1680
|
-
}
|
|
1681
|
-
return factory;
|
|
1682
|
-
}
|
|
1683
|
-
async function createModel(options = {}, routing = {}) {
|
|
1684
|
-
const provider = resolveProvider(options);
|
|
1685
|
-
if (provider === "local") {
|
|
1686
|
-
throw new Error(
|
|
1687
|
-
"Local provider does not create a language model. Use the deterministic pipeline path instead."
|
|
1688
|
-
);
|
|
1689
|
-
}
|
|
1690
|
-
const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
|
|
1691
|
-
if (!apiKey) {
|
|
1692
|
-
throw new Error(
|
|
1693
|
-
"AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
|
|
1694
|
-
);
|
|
1695
|
-
}
|
|
1696
|
-
const modelName = resolveModelName(provider, options, routing);
|
|
1697
|
-
const createRemoteClient = await loadRemoteFactory();
|
|
1698
|
-
const remoteClient = createRemoteClient({ apiKey });
|
|
1699
|
-
return remoteClient(modelName);
|
|
1700
|
-
}
|
|
1701
|
-
function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed", riskLevel = "medium", overrides) {
|
|
1702
|
-
const relaxed = {
|
|
1703
|
-
remote: { low: 0.65, medium: 0.7, high: 0.8 },
|
|
1704
|
-
local: { low: 0.65, medium: 0.7, high: 0.8 }
|
|
1705
|
-
};
|
|
1706
|
-
const strict = {
|
|
1707
|
-
remote: { low: 0.8, medium: 0.85, high: 0.9 },
|
|
1708
|
-
local: { low: 0.8, medium: 0.85, high: 0.9 }
|
|
1709
|
-
};
|
|
1710
|
-
const baseThreshold = safetyProfile === "strict" ? strict[provider][riskLevel] : relaxed[provider][riskLevel];
|
|
1711
|
-
const override = overrides?.[riskLevel];
|
|
1712
|
-
if (typeof override === "number" && Number.isFinite(override)) {
|
|
1713
|
-
return Math.max(0, Math.min(1, override));
|
|
1714
|
-
}
|
|
1715
|
-
return baseThreshold;
|
|
1716
|
-
}
|
|
1717
|
-
function estimateModelCostUsd(params) {
|
|
1718
|
-
const inputTokens = params.promptChars / 4;
|
|
1719
|
-
const outputTokens = params.completionChars / 4;
|
|
1720
|
-
const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
|
|
1721
|
-
return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
|
|
1722
|
-
}
|
|
1723
|
-
function getNvdConfig() {
|
|
1724
|
-
return {
|
|
1725
|
-
apiKey: process.env.AUTOREMEDIATOR_NVD_API_KEY
|
|
1726
|
-
};
|
|
1727
|
-
}
|
|
1728
|
-
function getGitHubToken() {
|
|
1729
|
-
return process.env.GITHUB_TOKEN;
|
|
1730
|
-
}
|
|
1731
|
-
function getIntelligenceSourceConfig() {
|
|
1732
|
-
return {
|
|
1733
|
-
gitLabAdvisoryApi: process.env.AUTOREMEDIATOR_GITLAB_ADVISORY_API ?? "https://advisories.gitlab.com/api/v1/advisories",
|
|
1734
|
-
certCcSearchUrl: process.env.AUTOREMEDIATOR_CERTCC_SEARCH_URL ?? "https://www.kb.cert.org/vuls/search",
|
|
1735
|
-
epssApi: process.env.AUTOREMEDIATOR_EPSS_API ?? "https://api.first.org/data/v1/epss",
|
|
1736
|
-
cveServicesApi: process.env.AUTOREMEDIATOR_CVE_SERVICES_API ?? "https://cveawg.mitre.org/api/cve",
|
|
1737
|
-
depsDevApi: process.env.AUTOREMEDIATOR_DEPSDEV_API ?? "https://api.deps.dev/v3",
|
|
1738
|
-
scorecardApi: process.env.AUTOREMEDIATOR_SCORECARD_API ?? "https://api.securityscorecards.dev",
|
|
1739
|
-
vendorAdvisoryFeeds: (process.env.AUTOREMEDIATOR_VENDOR_ADVISORY_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
|
|
1740
|
-
commercialFeeds: (process.env.AUTOREMEDIATOR_COMMERCIAL_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
|
|
1741
|
-
commercialFeedToken: process.env.AUTOREMEDIATOR_COMMERCIAL_FEED_TOKEN
|
|
1742
|
-
};
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
// src/platform/idempotency.ts
|
|
1746
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
1747
|
-
import { join as join8 } from "path";
|
|
1748
|
-
var DEFAULT_INDEX = {
|
|
1749
|
-
schemaVersion: "1.0",
|
|
1750
|
-
entries: {}
|
|
1751
|
-
};
|
|
1752
|
-
function indexFilePath(cwd) {
|
|
1753
|
-
return join8(cwd, ".autoremediator", "state", "idempotency.json");
|
|
1754
|
-
}
|
|
1755
|
-
function entryKey(idempotencyKey, cveId) {
|
|
1756
|
-
return `${idempotencyKey}::${cveId.toUpperCase()}`;
|
|
1757
|
-
}
|
|
1758
|
-
function loadIndex(cwd) {
|
|
1759
|
-
const filePath = indexFilePath(cwd);
|
|
1760
|
-
if (!existsSync7(filePath)) return DEFAULT_INDEX;
|
|
1761
|
-
try {
|
|
1762
|
-
const parsed = JSON.parse(readFileSync5(filePath, "utf8"));
|
|
1763
|
-
if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
|
|
1764
|
-
return parsed;
|
|
1765
|
-
}
|
|
1766
|
-
return DEFAULT_INDEX;
|
|
1767
|
-
} catch {
|
|
1768
|
-
return DEFAULT_INDEX;
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
function saveIndex(cwd, index) {
|
|
1772
|
-
const filePath = indexFilePath(cwd);
|
|
1773
|
-
mkdirSync2(join8(cwd, ".autoremediator", "state"), { recursive: true });
|
|
1774
|
-
writeFileSync2(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
|
|
1775
|
-
}
|
|
1776
|
-
function readIdempotentReport(cwd, idempotencyKey, cveId) {
|
|
1777
|
-
const index = loadIndex(cwd);
|
|
1778
|
-
const key = entryKey(idempotencyKey, cveId);
|
|
1779
|
-
return index.entries[key]?.report;
|
|
1780
|
-
}
|
|
1781
|
-
function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
|
|
1782
|
-
const index = loadIndex(cwd);
|
|
1783
|
-
const key = entryKey(idempotencyKey, cveId);
|
|
1784
|
-
index.entries[key] = {
|
|
1785
|
-
key: idempotencyKey,
|
|
1786
|
-
cveId: cveId.toUpperCase(),
|
|
1787
|
-
report,
|
|
1788
|
-
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1789
|
-
};
|
|
1790
|
-
saveIndex(cwd, index);
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
// src/remediation/pipeline.ts
|
|
1794
|
-
import { generateText as generateText2 } from "ai";
|
|
1792
|
+
// src/api/patches/reconcile.ts
|
|
1793
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1794
|
+
import { readFile as readFile3, rm, writeFile } from "fs/promises";
|
|
1795
|
+
import { join as join10 } from "path";
|
|
1796
|
+
import { execa as execa5 } from "execa";
|
|
1795
1797
|
|
|
1796
|
-
// src/remediation/
|
|
1797
|
-
import
|
|
1798
|
+
// src/remediation/evaluate-package.ts
|
|
1799
|
+
import semver2 from "semver";
|
|
1798
1800
|
|
|
1799
1801
|
// src/platform/http-client.ts
|
|
1800
1802
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
@@ -1976,26 +1978,750 @@ async function lookupCveOsv(cveId) {
|
|
|
1976
1978
|
return parseOsvVuln(vuln);
|
|
1977
1979
|
}
|
|
1978
1980
|
|
|
1979
|
-
// src/
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
};
|
|
1987
|
-
if (token) {
|
|
1988
|
-
headers["Authorization"] = `token ${token}`;
|
|
1981
|
+
// src/platform/config.ts
|
|
1982
|
+
function resolveProvider(options = {}) {
|
|
1983
|
+
const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "remote";
|
|
1984
|
+
if (raw !== "remote" && raw !== "local") {
|
|
1985
|
+
throw new Error(
|
|
1986
|
+
`Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
|
|
1987
|
+
);
|
|
1989
1988
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1989
|
+
return raw;
|
|
1990
|
+
}
|
|
1991
|
+
function resolveModelName(provider, options = {}, routing = {}) {
|
|
1992
|
+
if (options.model) return options.model;
|
|
1993
|
+
if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
|
|
1994
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1995
|
+
const policy = loadPolicy(cwd, options.policy);
|
|
1996
|
+
const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
|
|
1997
|
+
if (providerEnvModel) return providerEnvModel;
|
|
1998
|
+
const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
|
|
1999
|
+
if (policyPinnedModel) return policyPinnedModel;
|
|
2000
|
+
const defaults = {
|
|
2001
|
+
remote: "remote-default",
|
|
2002
|
+
local: "local"
|
|
2003
|
+
};
|
|
2004
|
+
const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
|
|
2005
|
+
const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
|
|
2006
|
+
if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
|
|
2007
|
+
return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
|
|
2008
|
+
}
|
|
2009
|
+
return defaults[provider];
|
|
2010
|
+
}
|
|
2011
|
+
async function loadRemoteFactory() {
|
|
2012
|
+
const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
|
|
2013
|
+
const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
|
|
2014
|
+
if (!moduleName) {
|
|
2015
|
+
throw new Error(
|
|
2016
|
+
"AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
|
|
2017
|
+
);
|
|
2018
|
+
}
|
|
2019
|
+
if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
|
|
2020
|
+
throw new Error(
|
|
2021
|
+
`AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
const loaded = await import(moduleName);
|
|
2025
|
+
const factory = loaded[exportName];
|
|
2026
|
+
if (typeof factory !== "function") {
|
|
2027
|
+
throw new Error(
|
|
2028
|
+
`Remote client factory "${exportName}" was not found in module "${moduleName}".`
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
return factory;
|
|
2032
|
+
}
|
|
2033
|
+
async function createModel(options = {}, routing = {}) {
|
|
2034
|
+
const provider = resolveProvider(options);
|
|
2035
|
+
if (provider === "local") {
|
|
2036
|
+
throw new Error(
|
|
2037
|
+
"Local provider does not create a language model. Use the deterministic pipeline path instead."
|
|
2038
|
+
);
|
|
2039
|
+
}
|
|
2040
|
+
const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
|
|
2041
|
+
if (!apiKey) {
|
|
2042
|
+
throw new Error(
|
|
2043
|
+
"AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
|
|
2044
|
+
);
|
|
2045
|
+
}
|
|
2046
|
+
const modelName = resolveModelName(provider, options, routing);
|
|
2047
|
+
const createRemoteClient = await loadRemoteFactory();
|
|
2048
|
+
const remoteClient = createRemoteClient({ apiKey });
|
|
2049
|
+
return remoteClient(modelName);
|
|
2050
|
+
}
|
|
2051
|
+
function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed", riskLevel = "medium", overrides) {
|
|
2052
|
+
const relaxed = {
|
|
2053
|
+
remote: { low: 0.65, medium: 0.7, high: 0.8 },
|
|
2054
|
+
local: { low: 0.65, medium: 0.7, high: 0.8 }
|
|
2055
|
+
};
|
|
2056
|
+
const strict = {
|
|
2057
|
+
remote: { low: 0.8, medium: 0.85, high: 0.9 },
|
|
2058
|
+
local: { low: 0.8, medium: 0.85, high: 0.9 }
|
|
2059
|
+
};
|
|
2060
|
+
const baseThreshold = safetyProfile === "strict" ? strict[provider][riskLevel] : relaxed[provider][riskLevel];
|
|
2061
|
+
const override = overrides?.[riskLevel];
|
|
2062
|
+
if (typeof override === "number" && Number.isFinite(override)) {
|
|
2063
|
+
return Math.max(0, Math.min(1, override));
|
|
2064
|
+
}
|
|
2065
|
+
return baseThreshold;
|
|
2066
|
+
}
|
|
2067
|
+
function estimateModelCostUsd(params) {
|
|
2068
|
+
const inputTokens = params.promptChars / 4;
|
|
2069
|
+
const outputTokens = params.completionChars / 4;
|
|
2070
|
+
const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
|
|
2071
|
+
return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
|
|
2072
|
+
}
|
|
2073
|
+
function getNvdConfig() {
|
|
2074
|
+
return {
|
|
2075
|
+
apiKey: process.env.AUTOREMEDIATOR_NVD_API_KEY
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
function getGitHubToken() {
|
|
2079
|
+
return process.env.GITHUB_TOKEN;
|
|
2080
|
+
}
|
|
2081
|
+
function getIntelligenceSourceConfig() {
|
|
2082
|
+
return {
|
|
2083
|
+
gitLabAdvisoryApi: process.env.AUTOREMEDIATOR_GITLAB_ADVISORY_API ?? "https://advisories.gitlab.com/api/v1/advisories",
|
|
2084
|
+
certCcSearchUrl: process.env.AUTOREMEDIATOR_CERTCC_SEARCH_URL ?? "https://www.kb.cert.org/vuls/search",
|
|
2085
|
+
epssApi: process.env.AUTOREMEDIATOR_EPSS_API ?? "https://api.first.org/data/v1/epss",
|
|
2086
|
+
cveServicesApi: process.env.AUTOREMEDIATOR_CVE_SERVICES_API ?? "https://cveawg.mitre.org/api/cve",
|
|
2087
|
+
depsDevApi: process.env.AUTOREMEDIATOR_DEPSDEV_API ?? "https://api.deps.dev/v3",
|
|
2088
|
+
scorecardApi: process.env.AUTOREMEDIATOR_SCORECARD_API ?? "https://api.securityscorecards.dev",
|
|
2089
|
+
vendorAdvisoryFeeds: (process.env.AUTOREMEDIATOR_VENDOR_ADVISORY_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
|
|
2090
|
+
commercialFeeds: (process.env.AUTOREMEDIATOR_COMMERCIAL_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
|
|
2091
|
+
commercialFeedToken: process.env.AUTOREMEDIATOR_COMMERCIAL_FEED_TOKEN
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// src/intelligence/sources/epss.ts
|
|
2096
|
+
async function fetchEpss(cveId) {
|
|
2097
|
+
const { epssApi } = getIntelligenceSourceConfig();
|
|
2098
|
+
if (!epssApi) return void 0;
|
|
2099
|
+
try {
|
|
2100
|
+
const url = new URL(epssApi);
|
|
2101
|
+
url.searchParams.set("cve", cveId);
|
|
2102
|
+
const res = await httpClient({ url: url.toString() });
|
|
2103
|
+
if (!res.ok) return void 0;
|
|
2104
|
+
const body = res.data;
|
|
2105
|
+
return body.data?.[0];
|
|
2106
|
+
} catch {
|
|
2107
|
+
return void 0;
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
async function enrichWithEpss(details) {
|
|
2111
|
+
const row = await fetchEpss(details.id);
|
|
2112
|
+
if (!row) return details;
|
|
2113
|
+
const score = Number.parseFloat(row.epss);
|
|
2114
|
+
const percentile = Number.parseFloat(row.percentile);
|
|
2115
|
+
if (!Number.isFinite(score) || !Number.isFinite(percentile)) {
|
|
2116
|
+
return details;
|
|
2117
|
+
}
|
|
2118
|
+
details.epss = {
|
|
2119
|
+
score,
|
|
2120
|
+
percentile,
|
|
2121
|
+
date: row.date
|
|
2122
|
+
};
|
|
2123
|
+
return details;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// src/intelligence/sources/cisa-kev.ts
|
|
2127
|
+
var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
|
|
2128
|
+
async function fetchCisaKevFeed() {
|
|
2129
|
+
try {
|
|
2130
|
+
const res = await httpClient({ url: CISA_KEV_URL });
|
|
2131
|
+
if (!res.ok) return void 0;
|
|
2132
|
+
return res.data;
|
|
2133
|
+
} catch {
|
|
2134
|
+
return void 0;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
function findKevEntry(feed, cveId) {
|
|
2138
|
+
if (!feed?.vulnerabilities?.length) return void 0;
|
|
2139
|
+
const normalized = cveId.toUpperCase();
|
|
2140
|
+
return feed.vulnerabilities.find((v) => v.cveID.toUpperCase() === normalized);
|
|
2141
|
+
}
|
|
2142
|
+
async function enrichWithCisaKev(details) {
|
|
2143
|
+
const feed = await fetchCisaKevFeed();
|
|
2144
|
+
const entry = findKevEntry(feed, details.id);
|
|
2145
|
+
if (!entry) return details;
|
|
2146
|
+
details.kev = {
|
|
2147
|
+
knownExploited: true,
|
|
2148
|
+
dateAdded: entry.dateAdded,
|
|
2149
|
+
dueDate: entry.dueDate,
|
|
2150
|
+
requiredAction: entry.requiredAction,
|
|
2151
|
+
knownRansomwareCampaignUse: entry.knownRansomwareCampaignUse
|
|
2152
|
+
};
|
|
2153
|
+
if (!details.references.includes(CISA_KEV_URL)) {
|
|
2154
|
+
details.references.push(CISA_KEV_URL);
|
|
2155
|
+
}
|
|
2156
|
+
return details;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
// src/intelligence/sources/registry.ts
|
|
2160
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
2161
|
+
import { join as join9 } from "path";
|
|
2162
|
+
import { execa as execa4 } from "execa";
|
|
2163
|
+
import semver from "semver";
|
|
2164
|
+
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
2165
|
+
async function fetchPackageVersions(packageName) {
|
|
2166
|
+
const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
|
|
2167
|
+
try {
|
|
2168
|
+
const res = await httpClient({ url });
|
|
2169
|
+
if (res.status === 404) return [];
|
|
2170
|
+
if (!res.ok) {
|
|
2171
|
+
throw new Error(`npm registry error ${res.status} for "${packageName}": ${res.text}`);
|
|
2172
|
+
}
|
|
2173
|
+
const data = res.data;
|
|
2174
|
+
return Object.keys(data.versions);
|
|
2175
|
+
} catch (err) {
|
|
2176
|
+
if (err instanceof Error && err.message.includes("registry error")) {
|
|
2177
|
+
throw err;
|
|
2178
|
+
}
|
|
2179
|
+
return [];
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
async function findSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
|
|
2183
|
+
const resolution = await resolveSafeUpgradeVersion(
|
|
2184
|
+
packageName,
|
|
2185
|
+
installedVersion,
|
|
2186
|
+
firstPatchedVersion,
|
|
2187
|
+
vulnerableRange
|
|
2188
|
+
);
|
|
2189
|
+
return resolution.safeVersion;
|
|
2190
|
+
}
|
|
2191
|
+
async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
|
|
2192
|
+
const versions = await fetchPackageVersions(packageName);
|
|
2193
|
+
if (!versions.length) {
|
|
2194
|
+
return {
|
|
2195
|
+
candidates: {},
|
|
2196
|
+
majorOnlyFixAvailable: false
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
2199
|
+
const installed = semver.parse(installedVersion);
|
|
2200
|
+
const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
|
|
2201
|
+
if (!vulnerableRange) return true;
|
|
2202
|
+
try {
|
|
2203
|
+
return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
|
|
2204
|
+
} catch {
|
|
2205
|
+
return true;
|
|
2206
|
+
}
|
|
2207
|
+
}).sort(semver.compare);
|
|
2208
|
+
if (!candidates.length) {
|
|
2209
|
+
return {
|
|
2210
|
+
candidates: {},
|
|
2211
|
+
majorOnlyFixAvailable: false
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
const categorizedCandidates = {};
|
|
2215
|
+
for (const candidate of candidates) {
|
|
2216
|
+
const level = classifyUpgradeLevel(installedVersion, candidate);
|
|
2217
|
+
if (!level) continue;
|
|
2218
|
+
if (!categorizedCandidates[level]) {
|
|
2219
|
+
categorizedCandidates[level] = candidate;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
const safeVersion = categorizedCandidates.patch ?? categorizedCandidates.minor ?? categorizedCandidates.major;
|
|
2223
|
+
if (!safeVersion) {
|
|
2224
|
+
return {
|
|
2225
|
+
candidates: categorizedCandidates,
|
|
2226
|
+
majorOnlyFixAvailable: false
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
const upgradeLevel = classifyUpgradeLevel(installedVersion, safeVersion);
|
|
2230
|
+
const majorOnlyFixAvailable = !categorizedCandidates.patch && !categorizedCandidates.minor && Boolean(categorizedCandidates.major);
|
|
2231
|
+
if (!installed || !upgradeLevel) {
|
|
2232
|
+
return {
|
|
2233
|
+
safeVersion,
|
|
2234
|
+
upgradeLevel,
|
|
2235
|
+
candidates: categorizedCandidates,
|
|
2236
|
+
majorOnlyFixAvailable
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
return {
|
|
2240
|
+
safeVersion,
|
|
2241
|
+
upgradeLevel,
|
|
2242
|
+
candidates: categorizedCandidates,
|
|
2243
|
+
majorOnlyFixAvailable
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
function classifyUpgradeLevel(installedVersion, candidateVersion) {
|
|
2247
|
+
const installed = semver.parse(installedVersion);
|
|
2248
|
+
const candidate = semver.parse(candidateVersion);
|
|
2249
|
+
if (!installed || !candidate) return void 0;
|
|
2250
|
+
if (candidate.major > installed.major) return "major";
|
|
2251
|
+
if (candidate.minor > installed.minor) return "minor";
|
|
2252
|
+
if (candidate.patch > installed.patch || candidate.version === installed.version) {
|
|
2253
|
+
return "patch";
|
|
2254
|
+
}
|
|
2255
|
+
return void 0;
|
|
2256
|
+
}
|
|
2257
|
+
async function queryOutdatedPackages(cwd, options = {}) {
|
|
2258
|
+
const pm = options.packageManager ?? detectPackageManager(cwd);
|
|
2259
|
+
let stdout;
|
|
2260
|
+
try {
|
|
2261
|
+
const args = pm === "pnpm" ? ["outdated", "--json"] : ["outdated", "--json"];
|
|
2262
|
+
const yarnArgs = pm === "yarn" ? ["outdated", "--json"] : void 0;
|
|
2263
|
+
const finalArgs = yarnArgs ?? args;
|
|
2264
|
+
const result2 = await execa4(pm, finalArgs, {
|
|
2265
|
+
cwd,
|
|
2266
|
+
reject: false,
|
|
2267
|
+
stdio: "pipe"
|
|
2268
|
+
});
|
|
2269
|
+
if (result2.exitCode !== 0 && result2.exitCode !== 1) {
|
|
2270
|
+
const stderr = typeof result2.stderr === "string" ? result2.stderr : "";
|
|
2271
|
+
if (!result2.stdout) {
|
|
2272
|
+
throw new Error(
|
|
2273
|
+
`${pm} outdated --json failed (exit ${result2.exitCode}): ${stderr.slice(0, 500)}`
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
stdout = typeof result2.stdout === "string" ? result2.stdout : "";
|
|
2278
|
+
} catch (err) {
|
|
2279
|
+
if (err instanceof Error && !err.message.includes("outdated --json failed")) {
|
|
2280
|
+
throw new Error(`Failed to query outdated packages: ${err.message}`);
|
|
2281
|
+
}
|
|
2282
|
+
throw err;
|
|
2283
|
+
}
|
|
2284
|
+
if (!stdout.trim()) {
|
|
2285
|
+
return /* @__PURE__ */ new Map();
|
|
2286
|
+
}
|
|
2287
|
+
const rawEntries = {};
|
|
2288
|
+
if (pm === "yarn") {
|
|
2289
|
+
for (const line of stdout.split("\n")) {
|
|
2290
|
+
const trimmed = line.trim();
|
|
2291
|
+
if (!trimmed) continue;
|
|
2292
|
+
try {
|
|
2293
|
+
const parsed = JSON.parse(trimmed);
|
|
2294
|
+
if (parsed.type === "table" && parsed.data) {
|
|
2295
|
+
const tableData = parsed.data;
|
|
2296
|
+
const head = tableData.head ?? [];
|
|
2297
|
+
const currentIdx = head.indexOf("Current");
|
|
2298
|
+
const wantedIdx = head.indexOf("Wanted");
|
|
2299
|
+
const latestIdx = head.indexOf("Latest");
|
|
2300
|
+
for (const row of tableData.body ?? []) {
|
|
2301
|
+
const name = row[0];
|
|
2302
|
+
if (!name) continue;
|
|
2303
|
+
rawEntries[name] = {
|
|
2304
|
+
current: row[currentIdx] ?? "",
|
|
2305
|
+
wanted: row[wantedIdx] ?? "",
|
|
2306
|
+
latest: row[latestIdx] ?? "",
|
|
2307
|
+
type: row[head.indexOf("Package Type")] ?? "dependencies"
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
} catch {
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
} else {
|
|
2315
|
+
try {
|
|
2316
|
+
const parsed = JSON.parse(stdout);
|
|
2317
|
+
if (Array.isArray(parsed)) {
|
|
2318
|
+
for (const entry of parsed) {
|
|
2319
|
+
const name = entry.name ?? entry.packageName;
|
|
2320
|
+
if (!name) continue;
|
|
2321
|
+
rawEntries[name] = {
|
|
2322
|
+
current: entry.current,
|
|
2323
|
+
wanted: entry.wanted,
|
|
2324
|
+
latest: entry.latest,
|
|
2325
|
+
type: entry.dependencyType
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2328
|
+
} else {
|
|
2329
|
+
Object.assign(rawEntries, parsed);
|
|
2330
|
+
}
|
|
2331
|
+
} catch {
|
|
2332
|
+
throw new Error(`Could not parse ${pm} outdated --json output.`);
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
let directDeps;
|
|
2336
|
+
try {
|
|
2337
|
+
const pkgRaw = JSON.parse(readFileSync7(join9(cwd, "package.json"), "utf8"));
|
|
2338
|
+
directDeps = /* @__PURE__ */ new Set([
|
|
2339
|
+
...Object.keys(pkgRaw.dependencies ?? {}),
|
|
2340
|
+
...Object.keys(pkgRaw.devDependencies ?? {})
|
|
2341
|
+
]);
|
|
2342
|
+
} catch {
|
|
2343
|
+
directDeps = /* @__PURE__ */ new Set();
|
|
2344
|
+
}
|
|
2345
|
+
const result = /* @__PURE__ */ new Map();
|
|
2346
|
+
for (const [name, entry] of Object.entries(rawEntries)) {
|
|
2347
|
+
const currentVersion = entry.current ?? "";
|
|
2348
|
+
const wantedVersion = entry.wanted ?? currentVersion;
|
|
2349
|
+
const latestVersion = entry.latest ?? currentVersion;
|
|
2350
|
+
if (!currentVersion || !latestVersion) continue;
|
|
2351
|
+
const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
|
|
2352
|
+
if (!options.includeTransitive && dependencyScope === "transitive") continue;
|
|
2353
|
+
const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
|
|
2354
|
+
result.set(name, {
|
|
2355
|
+
currentVersion,
|
|
2356
|
+
wantedVersion,
|
|
2357
|
+
latestVersion,
|
|
2358
|
+
isMajorBump,
|
|
2359
|
+
dependencyScope
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
return result;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
// src/remediation/evaluate-package.ts
|
|
2366
|
+
async function evaluatePackage(packageName, options = {}) {
|
|
2367
|
+
const version = options.version?.trim();
|
|
2368
|
+
const enrich = options.enrichIntelligence !== false;
|
|
2369
|
+
let versions = [];
|
|
2370
|
+
let latestVersion;
|
|
2371
|
+
try {
|
|
2372
|
+
versions = await fetchPackageVersions(packageName);
|
|
2373
|
+
if (versions.length > 0) {
|
|
2374
|
+
const valid = versions.filter((v) => semver2.valid(v)).sort((a, b) => semver2.compare(a, b));
|
|
2375
|
+
latestVersion = valid[valid.length - 1];
|
|
2376
|
+
}
|
|
2377
|
+
} catch {
|
|
2378
|
+
}
|
|
2379
|
+
let rawVulns = [];
|
|
2380
|
+
try {
|
|
2381
|
+
rawVulns = await queryOsvPackage(packageName, version);
|
|
2382
|
+
} catch {
|
|
2383
|
+
rawVulns = [];
|
|
2384
|
+
}
|
|
2385
|
+
let kevFeed = null;
|
|
2386
|
+
if (enrich) {
|
|
2387
|
+
try {
|
|
2388
|
+
kevFeed = await fetchCisaKevFeed();
|
|
2389
|
+
} catch {
|
|
2390
|
+
kevFeed = null;
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
const entries = [];
|
|
2394
|
+
for (const raw of rawVulns) {
|
|
2395
|
+
const cveDetails = parseOsvVuln(raw);
|
|
2396
|
+
const affected = cveDetails.affectedPackages.find(
|
|
2397
|
+
(p) => p.name.toLowerCase() === packageName.toLowerCase()
|
|
2398
|
+
);
|
|
2399
|
+
if (version && affected?.vulnerableRange) {
|
|
2400
|
+
const cleanVer = semver2.clean(version) || version;
|
|
2401
|
+
if (semver2.valid(cleanVer) && !semver2.satisfies(cleanVer, affected.vulnerableRange)) {
|
|
2402
|
+
continue;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
let inCisaKev = false;
|
|
2406
|
+
let epssScore;
|
|
2407
|
+
let epssPercentile;
|
|
2408
|
+
if (enrich && cveDetails.id.startsWith("CVE-")) {
|
|
2409
|
+
if (kevFeed) {
|
|
2410
|
+
inCisaKev = Boolean(findKevEntry(kevFeed, cveDetails.id));
|
|
2411
|
+
}
|
|
2412
|
+
try {
|
|
2413
|
+
const epss = await fetchEpss(cveDetails.id);
|
|
2414
|
+
if (epss) {
|
|
2415
|
+
epssScore = Number.parseFloat(epss.epss);
|
|
2416
|
+
epssPercentile = Number.parseFloat(epss.percentile);
|
|
2417
|
+
}
|
|
2418
|
+
} catch {
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
let safeUpgradeVersion;
|
|
2422
|
+
if (affected?.firstPatchedVersion) {
|
|
2423
|
+
try {
|
|
2424
|
+
const safe = await findSafeUpgradeVersion(
|
|
2425
|
+
packageName,
|
|
2426
|
+
version || "0.0.0",
|
|
2427
|
+
affected.firstPatchedVersion
|
|
2428
|
+
);
|
|
2429
|
+
safeUpgradeVersion = safe ?? affected.firstPatchedVersion;
|
|
2430
|
+
} catch {
|
|
2431
|
+
safeUpgradeVersion = affected.firstPatchedVersion;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
entries.push({
|
|
2435
|
+
cveId: cveDetails.id,
|
|
2436
|
+
severity: cveDetails.severity,
|
|
2437
|
+
summary: cveDetails.summary,
|
|
2438
|
+
vulnerableRange: affected?.vulnerableRange,
|
|
2439
|
+
firstPatchedVersion: affected?.firstPatchedVersion,
|
|
2440
|
+
safeUpgradeVersion,
|
|
2441
|
+
epssScore,
|
|
2442
|
+
epssPercentile,
|
|
2443
|
+
inCisaKev,
|
|
2444
|
+
references: cveDetails.references
|
|
2445
|
+
});
|
|
2446
|
+
}
|
|
2447
|
+
const isVulnerable = entries.length > 0;
|
|
2448
|
+
let verdict = "safe";
|
|
2449
|
+
if (isVulnerable) {
|
|
2450
|
+
const hasKevOrHighEpss = entries.some((e) => e.inCisaKev || e.epssScore && e.epssScore > 0.5);
|
|
2451
|
+
const hasHighOrCritical = entries.some(
|
|
2452
|
+
(e) => e.severity === "CRITICAL" || e.severity === "HIGH"
|
|
2453
|
+
);
|
|
2454
|
+
if (hasKevOrHighEpss) {
|
|
2455
|
+
verdict = "actively-exploited";
|
|
2456
|
+
} else if (hasHighOrCritical) {
|
|
2457
|
+
verdict = "vulnerable";
|
|
2458
|
+
} else {
|
|
2459
|
+
verdict = "caution";
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
let recommendedVersion;
|
|
2463
|
+
if (isVulnerable) {
|
|
2464
|
+
const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver2.valid(v))).sort((a, b) => semver2.compare(a, b));
|
|
2465
|
+
recommendedVersion = upgradeCandidates[upgradeCandidates.length - 1] || latestVersion;
|
|
2466
|
+
} else {
|
|
2467
|
+
recommendedVersion = version || latestVersion;
|
|
2468
|
+
}
|
|
2469
|
+
const summary = isVulnerable ? `Package "${packageName}${version ? `@${version}` : ""}" has ${entries.length} known vulnerability(ies) (verdict: ${verdict}). Recommended safe version: ${recommendedVersion || "none"}.` : `Package "${packageName}${version ? `@${version}` : ""}" has no known vulnerabilities. Safe to install.`;
|
|
2470
|
+
return {
|
|
2471
|
+
schemaVersion: "1.0",
|
|
2472
|
+
packageName,
|
|
2473
|
+
evaluatedVersion: version,
|
|
2474
|
+
isVulnerable,
|
|
2475
|
+
verdict,
|
|
2476
|
+
summary,
|
|
2477
|
+
vulnerabilities: entries,
|
|
2478
|
+
recommendedVersion,
|
|
2479
|
+
latestVersion,
|
|
2480
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
// src/api/patches/reconcile.ts
|
|
2485
|
+
async function reconcilePatchArtifacts(options = {}) {
|
|
2486
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2487
|
+
const pm = options.packageManager ?? detectPackageManager(cwd);
|
|
2488
|
+
const summaries = await listPatchArtifacts(options);
|
|
2489
|
+
const results = [];
|
|
2490
|
+
for (const summary of summaries) {
|
|
2491
|
+
const inspection = await inspectPatchArtifact(summary.patchFilePath, options);
|
|
2492
|
+
let packageName = inspection.packageName;
|
|
2493
|
+
let vulnerableVersion = inspection.vulnerableVersion;
|
|
2494
|
+
const cveId = inspection.cveId;
|
|
2495
|
+
if (!packageName && summary.patchFileName) {
|
|
2496
|
+
const basename = summary.patchFileName.replace(/\.patch$/, "");
|
|
2497
|
+
const plusIdx = basename.lastIndexOf("+");
|
|
2498
|
+
if (plusIdx > 0) {
|
|
2499
|
+
let rawPkg = basename.slice(0, plusIdx);
|
|
2500
|
+
vulnerableVersion = basename.slice(plusIdx + 1);
|
|
2501
|
+
if (rawPkg.includes("+")) {
|
|
2502
|
+
rawPkg = "@" + rawPkg.replace("+", "/");
|
|
2503
|
+
}
|
|
2504
|
+
packageName = rawPkg;
|
|
2505
|
+
} else {
|
|
2506
|
+
packageName = basename;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
if (!packageName) {
|
|
2510
|
+
results.push({
|
|
2511
|
+
patchFilePath: summary.patchFilePath,
|
|
2512
|
+
packageName: "unknown",
|
|
2513
|
+
cveId,
|
|
2514
|
+
status: "patch-retained",
|
|
2515
|
+
reason: "Cannot determine package name from patch artifact."
|
|
2516
|
+
});
|
|
2517
|
+
continue;
|
|
2518
|
+
}
|
|
2519
|
+
let safeUpgradeVersion;
|
|
2520
|
+
try {
|
|
2521
|
+
const evaluation = await evaluatePackage(packageName, {
|
|
2522
|
+
version: vulnerableVersion,
|
|
2523
|
+
enrichIntelligence: false
|
|
2524
|
+
});
|
|
2525
|
+
if (cveId) {
|
|
2526
|
+
const vuln = evaluation.vulnerabilities.find(
|
|
2527
|
+
(v) => v.cveId.toLowerCase() === cveId.toLowerCase()
|
|
2528
|
+
);
|
|
2529
|
+
if (vuln?.safeUpgradeVersion) {
|
|
2530
|
+
safeUpgradeVersion = vuln.safeUpgradeVersion;
|
|
2531
|
+
}
|
|
2532
|
+
} else {
|
|
2533
|
+
safeUpgradeVersion = evaluation.vulnerabilities.find(
|
|
2534
|
+
(v) => v.safeUpgradeVersion
|
|
2535
|
+
)?.safeUpgradeVersion;
|
|
2536
|
+
}
|
|
2537
|
+
} catch {
|
|
2538
|
+
}
|
|
2539
|
+
if (!safeUpgradeVersion) {
|
|
2540
|
+
results.push({
|
|
2541
|
+
patchFilePath: summary.patchFilePath,
|
|
2542
|
+
packageName,
|
|
2543
|
+
cveId,
|
|
2544
|
+
installedVersion: vulnerableVersion,
|
|
2545
|
+
status: "patch-retained",
|
|
2546
|
+
reason: "No safe upstream release exists yet; patch remains necessary."
|
|
2547
|
+
});
|
|
2548
|
+
continue;
|
|
2549
|
+
}
|
|
2550
|
+
if (!options.autoApply) {
|
|
2551
|
+
results.push({
|
|
2552
|
+
patchFilePath: summary.patchFilePath,
|
|
2553
|
+
packageName,
|
|
2554
|
+
cveId,
|
|
2555
|
+
installedVersion: vulnerableVersion,
|
|
2556
|
+
safeUpstreamVersion: safeUpgradeVersion,
|
|
2557
|
+
status: "safe-version-available",
|
|
2558
|
+
reason: `Safe upstream version ${safeUpgradeVersion} is available. Re-run with autoApply: true or --auto-apply to upgrade.`
|
|
2559
|
+
});
|
|
2560
|
+
continue;
|
|
2561
|
+
}
|
|
2562
|
+
try {
|
|
2563
|
+
const pkgJsonPath = join10(cwd, "package.json");
|
|
2564
|
+
if (existsSync8(pkgJsonPath)) {
|
|
2565
|
+
const pkgJson = JSON.parse(await readFile3(pkgJsonPath, "utf8"));
|
|
2566
|
+
let updated = false;
|
|
2567
|
+
const depSections = [
|
|
2568
|
+
"dependencies",
|
|
2569
|
+
"devDependencies",
|
|
2570
|
+
"peerDependencies",
|
|
2571
|
+
"optionalDependencies"
|
|
2572
|
+
];
|
|
2573
|
+
for (const depSection of depSections) {
|
|
2574
|
+
if (pkgJson[depSection] && pkgJson[depSection][packageName]) {
|
|
2575
|
+
pkgJson[depSection][packageName] = `^${safeUpgradeVersion}`;
|
|
2576
|
+
updated = true;
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
if (pkgJson.pnpm?.patchedDependencies) {
|
|
2580
|
+
for (const key of Object.keys(pkgJson.pnpm.patchedDependencies)) {
|
|
2581
|
+
if (key.startsWith(`${packageName}@`)) {
|
|
2582
|
+
delete pkgJson.pnpm.patchedDependencies[key];
|
|
2583
|
+
updated = true;
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
if (updated) {
|
|
2588
|
+
await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
await rm(summary.patchFilePath, { force: true });
|
|
2592
|
+
if (inspection.manifestFilePath) {
|
|
2593
|
+
await rm(inspection.manifestFilePath, { force: true });
|
|
2594
|
+
}
|
|
2595
|
+
const installCmd = resolveInstallCommand(pm);
|
|
2596
|
+
await execa5(installCmd[0], installCmd.slice(1), { cwd, reject: false });
|
|
2597
|
+
if (options.runTests) {
|
|
2598
|
+
const testCmd = resolveTestCommand(pm);
|
|
2599
|
+
await execa5(testCmd[0], testCmd.slice(1), { cwd, reject: false });
|
|
2600
|
+
}
|
|
2601
|
+
results.push({
|
|
2602
|
+
patchFilePath: summary.patchFilePath,
|
|
2603
|
+
packageName,
|
|
2604
|
+
cveId,
|
|
2605
|
+
installedVersion: vulnerableVersion,
|
|
2606
|
+
safeUpstreamVersion: safeUpgradeVersion,
|
|
2607
|
+
status: "upgraded",
|
|
2608
|
+
reason: `Successfully upgraded to ${safeUpgradeVersion} and retired patch artifact.`
|
|
2609
|
+
});
|
|
2610
|
+
} catch (err) {
|
|
2611
|
+
results.push({
|
|
2612
|
+
patchFilePath: summary.patchFilePath,
|
|
2613
|
+
packageName,
|
|
2614
|
+
cveId,
|
|
2615
|
+
installedVersion: vulnerableVersion,
|
|
2616
|
+
safeUpstreamVersion: safeUpgradeVersion,
|
|
2617
|
+
status: "error",
|
|
2618
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
const reconciledCount = results.filter((r) => r.status === "upgraded").length;
|
|
2623
|
+
const retainedCount = results.filter(
|
|
2624
|
+
(r) => r.status === "patch-retained" || r.status === "safe-version-available"
|
|
2625
|
+
).length;
|
|
2626
|
+
return {
|
|
2627
|
+
reconciled: results,
|
|
2628
|
+
reconciledCount,
|
|
2629
|
+
retainedCount
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
// src/platform/idempotency.ts
|
|
2634
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
|
|
2635
|
+
import { join as join11 } from "path";
|
|
2636
|
+
var DEFAULT_INDEX = {
|
|
2637
|
+
schemaVersion: "1.0",
|
|
2638
|
+
entries: {}
|
|
2639
|
+
};
|
|
2640
|
+
function indexFilePath(cwd) {
|
|
2641
|
+
return join11(cwd, ".autoremediator", "state", "idempotency.json");
|
|
2642
|
+
}
|
|
2643
|
+
function entryKey(idempotencyKey, cveId) {
|
|
2644
|
+
return `${idempotencyKey}::${cveId.toUpperCase()}`;
|
|
2645
|
+
}
|
|
2646
|
+
function loadIndex(cwd) {
|
|
2647
|
+
const filePath = indexFilePath(cwd);
|
|
2648
|
+
if (!existsSync9(filePath)) return DEFAULT_INDEX;
|
|
2649
|
+
try {
|
|
2650
|
+
const parsed = JSON.parse(readFileSync8(filePath, "utf8"));
|
|
2651
|
+
if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
|
|
2652
|
+
return parsed;
|
|
2653
|
+
}
|
|
2654
|
+
return DEFAULT_INDEX;
|
|
2655
|
+
} catch {
|
|
2656
|
+
return DEFAULT_INDEX;
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
function saveIndex(cwd, index) {
|
|
2660
|
+
const filePath = indexFilePath(cwd);
|
|
2661
|
+
mkdirSync2(join11(cwd, ".autoremediator", "state"), { recursive: true });
|
|
2662
|
+
writeFileSync2(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
|
|
2663
|
+
}
|
|
2664
|
+
function readIdempotentReport(cwd, idempotencyKey, cveId) {
|
|
2665
|
+
const index = loadIndex(cwd);
|
|
2666
|
+
const key = entryKey(idempotencyKey, cveId);
|
|
2667
|
+
return index.entries[key]?.report;
|
|
2668
|
+
}
|
|
2669
|
+
function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
|
|
2670
|
+
const index = loadIndex(cwd);
|
|
2671
|
+
const key = entryKey(idempotencyKey, cveId);
|
|
2672
|
+
index.entries[key] = {
|
|
2673
|
+
key: idempotencyKey,
|
|
2674
|
+
cveId: cveId.toUpperCase(),
|
|
2675
|
+
report,
|
|
2676
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2677
|
+
};
|
|
2678
|
+
saveIndex(cwd, index);
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
// src/remediation/pipeline.ts
|
|
2682
|
+
import { generateText as generateText2 } from "ai";
|
|
2683
|
+
|
|
2684
|
+
// src/remediation/local/run.ts
|
|
2685
|
+
import semver6 from "semver";
|
|
2686
|
+
|
|
2687
|
+
// src/intelligence/sources/github-advisory.ts
|
|
2688
|
+
var GITHUB_API_BASE = "https://api.github.com/advisories";
|
|
2689
|
+
async function fetchGhAdvisories(cveId) {
|
|
2690
|
+
const token = getGitHubToken();
|
|
2691
|
+
const headers = {
|
|
2692
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
2693
|
+
"User-Agent": "autoremediator"
|
|
2694
|
+
};
|
|
2695
|
+
if (token) {
|
|
2696
|
+
headers["Authorization"] = `token ${token}`;
|
|
2697
|
+
}
|
|
2698
|
+
const normalized = cveId.trim();
|
|
2699
|
+
if (normalized.toUpperCase().startsWith("GHSA-")) {
|
|
2700
|
+
const url2 = `${GITHUB_API_BASE}/${encodeURIComponent(normalized)}`;
|
|
2701
|
+
try {
|
|
2702
|
+
const res = await httpClient({ url: url2, headers });
|
|
2703
|
+
if (!res.ok) {
|
|
2704
|
+
if (res.status === 404) return [];
|
|
2705
|
+
throw new Error(`HTTP ${res.status}: ${res.text}`);
|
|
2706
|
+
}
|
|
2707
|
+
return res.data ? [res.data] : [];
|
|
2708
|
+
} catch (err) {
|
|
2709
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2710
|
+
console.warn(
|
|
2711
|
+
`[autoremediator] GitHub Advisory API error for ${cveId}: ${errorMsg} \u2014 skipping.`
|
|
2712
|
+
);
|
|
2713
|
+
return [];
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
const url = new URL(GITHUB_API_BASE);
|
|
2717
|
+
url.searchParams.set("cve_id", cveId);
|
|
2718
|
+
url.searchParams.set("ecosystem", "npm");
|
|
2719
|
+
url.searchParams.set("type", "reviewed");
|
|
2720
|
+
url.searchParams.set("per_page", "10");
|
|
2721
|
+
try {
|
|
2722
|
+
const res = await httpClient({
|
|
2723
|
+
url: url.toString(),
|
|
2724
|
+
headers
|
|
1999
2725
|
});
|
|
2000
2726
|
if (!res.ok) {
|
|
2001
2727
|
if (res.status === 404) return [];
|
|
@@ -2095,9 +2821,9 @@ async function enrichWithNvd(details) {
|
|
|
2095
2821
|
}
|
|
2096
2822
|
|
|
2097
2823
|
// src/intelligence/sources/snapshot.ts
|
|
2098
|
-
import { readFileSync as
|
|
2824
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
2099
2825
|
function loadIntelligenceSnapshot(filePath) {
|
|
2100
|
-
const raw =
|
|
2826
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
2101
2827
|
const parsed = JSON.parse(raw);
|
|
2102
2828
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2103
2829
|
throw new Error(`Intelligence snapshot at ${filePath} must be a JSON object keyed by CVE ID.`);
|
|
@@ -2109,8 +2835,8 @@ function lookupSnapshotCve(snapshot, cveId) {
|
|
|
2109
2835
|
}
|
|
2110
2836
|
|
|
2111
2837
|
// src/remediation/tools/check-reachability.ts
|
|
2112
|
-
import { existsSync as
|
|
2113
|
-
import { isAbsolute as isAbsolute2, join as
|
|
2838
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync as statSync2 } from "fs";
|
|
2839
|
+
import { isAbsolute as isAbsolute2, join as join12, resolve as resolve3 } from "path";
|
|
2114
2840
|
import { parseSync } from "oxc-parser";
|
|
2115
2841
|
import { z as z2 } from "zod";
|
|
2116
2842
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
|
|
@@ -2126,16 +2852,16 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
2126
2852
|
]);
|
|
2127
2853
|
function loadTsconfigPaths(cwd) {
|
|
2128
2854
|
for (const configName of ["tsconfig.json", "jsconfig.json"]) {
|
|
2129
|
-
const fullPath =
|
|
2130
|
-
if (!
|
|
2855
|
+
const fullPath = join12(cwd, configName);
|
|
2856
|
+
if (!existsSync10(fullPath)) continue;
|
|
2131
2857
|
try {
|
|
2132
|
-
const raw =
|
|
2858
|
+
const raw = readFileSync10(fullPath, "utf8");
|
|
2133
2859
|
const cleaned = raw.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
|
|
2134
2860
|
const parsed = JSON.parse(cleaned);
|
|
2135
2861
|
const compilerOptions = parsed.compilerOptions;
|
|
2136
2862
|
if (compilerOptions?.paths) {
|
|
2137
2863
|
return {
|
|
2138
|
-
baseUrl: compilerOptions.baseUrl ?
|
|
2864
|
+
baseUrl: compilerOptions.baseUrl ? resolve3(cwd, compilerOptions.baseUrl) : cwd,
|
|
2139
2865
|
paths: compilerOptions.paths
|
|
2140
2866
|
};
|
|
2141
2867
|
}
|
|
@@ -2150,14 +2876,14 @@ function resolveAliasedPath(importSource, config) {
|
|
|
2150
2876
|
}
|
|
2151
2877
|
for (const [pattern, replacements] of Object.entries(config.paths)) {
|
|
2152
2878
|
if (pattern === importSource && replacements[0]) {
|
|
2153
|
-
return
|
|
2879
|
+
return resolve3(config.baseUrl, replacements[0]);
|
|
2154
2880
|
}
|
|
2155
2881
|
if (pattern.endsWith("/*")) {
|
|
2156
2882
|
const prefix = pattern.slice(0, -2);
|
|
2157
2883
|
if (importSource.startsWith(prefix)) {
|
|
2158
2884
|
const suffix = importSource.slice(prefix.length);
|
|
2159
2885
|
const replacement = replacements[0]?.slice(0, -2) ?? "";
|
|
2160
|
-
return
|
|
2886
|
+
return resolve3(config.baseUrl, replacement + suffix);
|
|
2161
2887
|
}
|
|
2162
2888
|
}
|
|
2163
2889
|
}
|
|
@@ -2166,16 +2892,16 @@ function resolveAliasedPath(importSource, config) {
|
|
|
2166
2892
|
function* walkSourceFiles(dir) {
|
|
2167
2893
|
let entries;
|
|
2168
2894
|
try {
|
|
2169
|
-
entries =
|
|
2895
|
+
entries = readdirSync2(dir);
|
|
2170
2896
|
} catch {
|
|
2171
2897
|
return;
|
|
2172
2898
|
}
|
|
2173
2899
|
for (const entry of entries) {
|
|
2174
2900
|
if (IGNORED_DIRS.has(entry)) continue;
|
|
2175
|
-
const full =
|
|
2901
|
+
const full = join12(dir, entry);
|
|
2176
2902
|
let stat2;
|
|
2177
2903
|
try {
|
|
2178
|
-
stat2 =
|
|
2904
|
+
stat2 = statSync2(full);
|
|
2179
2905
|
} catch {
|
|
2180
2906
|
continue;
|
|
2181
2907
|
}
|
|
@@ -2213,7 +2939,31 @@ function walkAst(node, visitor) {
|
|
|
2213
2939
|
}
|
|
2214
2940
|
}
|
|
2215
2941
|
}
|
|
2216
|
-
function
|
|
2942
|
+
function toRootPackageName(importSource) {
|
|
2943
|
+
if (importSource.startsWith("@")) {
|
|
2944
|
+
const parts = importSource.split("/");
|
|
2945
|
+
return parts.slice(0, 2).join("/");
|
|
2946
|
+
}
|
|
2947
|
+
return importSource.split("/")[0] ?? importSource;
|
|
2948
|
+
}
|
|
2949
|
+
function findTransitiveDependencyPath(cwd, targetPackage, importedDirectPackages) {
|
|
2950
|
+
for (const directPkg of importedDirectPackages) {
|
|
2951
|
+
const manifestPath = join12(cwd, "node_modules", directPkg, "package.json");
|
|
2952
|
+
if (!existsSync10(manifestPath)) continue;
|
|
2953
|
+
try {
|
|
2954
|
+
const manifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
|
|
2955
|
+
if (manifest.dependencies && manifest.dependencies[targetPackage]) {
|
|
2956
|
+
return {
|
|
2957
|
+
directDependency: directPkg,
|
|
2958
|
+
version: manifest.dependencies[targetPackage]
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
} catch {
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
return null;
|
|
2965
|
+
}
|
|
2966
|
+
function extractPackageReferences(filename, content, packageName, tsconfig, outImportedDirectPackages) {
|
|
2217
2967
|
const results = [];
|
|
2218
2968
|
let parseResult;
|
|
2219
2969
|
try {
|
|
@@ -2225,6 +2975,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
|
|
|
2225
2975
|
walkAst(parseResult.program, (node) => {
|
|
2226
2976
|
if (node.type === "ImportDeclaration") {
|
|
2227
2977
|
const source = node.source;
|
|
2978
|
+
if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
|
|
2979
|
+
outImportedDirectPackages?.add(toRootPackageName(source.value));
|
|
2980
|
+
}
|
|
2228
2981
|
if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
|
|
2229
2982
|
const symbols = [];
|
|
2230
2983
|
const importedIdentifiers = /* @__PURE__ */ new Map();
|
|
@@ -2259,6 +3012,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
|
|
|
2259
3012
|
}
|
|
2260
3013
|
if (node.type === "ImportExpression") {
|
|
2261
3014
|
const source = node.source;
|
|
3015
|
+
if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
|
|
3016
|
+
outImportedDirectPackages?.add(toRootPackageName(source.value));
|
|
3017
|
+
}
|
|
2262
3018
|
if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
|
|
2263
3019
|
results.push({
|
|
2264
3020
|
matchType: "dynamic-import",
|
|
@@ -2270,6 +3026,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
|
|
|
2270
3026
|
}
|
|
2271
3027
|
if (node.type === "CallExpression" && node.callee?.name === "require" && Array.isArray(node.arguments) && node.arguments.length > 0) {
|
|
2272
3028
|
const firstArg = node.arguments[0];
|
|
3029
|
+
if (firstArg?.value && typeof firstArg.value === "string" && !firstArg.value.startsWith(".") && !isAbsolute2(firstArg.value)) {
|
|
3030
|
+
outImportedDirectPackages?.add(toRootPackageName(firstArg.value));
|
|
3031
|
+
}
|
|
2273
3032
|
if (firstArg?.value && typeof firstArg.value === "string" && matchesPackage(firstArg.value, packageName, tsconfig)) {
|
|
2274
3033
|
results.push({
|
|
2275
3034
|
matchType: "require",
|
|
@@ -2281,6 +3040,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
|
|
|
2281
3040
|
}
|
|
2282
3041
|
if (node.type === "ExportNamedDeclaration" || node.type === "ExportAllDeclaration") {
|
|
2283
3042
|
const source = node.source;
|
|
3043
|
+
if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
|
|
3044
|
+
outImportedDirectPackages?.add(toRootPackageName(source.value));
|
|
3045
|
+
}
|
|
2284
3046
|
if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
|
|
2285
3047
|
const symbols = [];
|
|
2286
3048
|
if (node.type === "ExportAllDeclaration") {
|
|
@@ -2357,15 +3119,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
|
|
|
2357
3119
|
let hasReExport = false;
|
|
2358
3120
|
let totalInvocations = 0;
|
|
2359
3121
|
let hasPackageImport = false;
|
|
3122
|
+
const importedDirectPackages = /* @__PURE__ */ new Set();
|
|
2360
3123
|
for (const filePath of walkSourceFiles(cwd)) {
|
|
2361
3124
|
scanned++;
|
|
2362
3125
|
let content;
|
|
2363
3126
|
try {
|
|
2364
|
-
content =
|
|
3127
|
+
content = readFileSync10(filePath, "utf8");
|
|
2365
3128
|
} catch {
|
|
2366
3129
|
continue;
|
|
2367
3130
|
}
|
|
2368
|
-
const refs = extractPackageReferences(filePath, content, packageName, tsconfig);
|
|
3131
|
+
const refs = extractPackageReferences(filePath, content, packageName, tsconfig, importedDirectPackages);
|
|
2369
3132
|
if (refs.length > 0) {
|
|
2370
3133
|
hasPackageImport = true;
|
|
2371
3134
|
}
|
|
@@ -2408,6 +3171,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
|
|
|
2408
3171
|
};
|
|
2409
3172
|
}
|
|
2410
3173
|
if (!hasPackageImport) {
|
|
3174
|
+
const transitivePath = findTransitiveDependencyPath(cwd, packageName, importedDirectPackages);
|
|
3175
|
+
if (transitivePath) {
|
|
3176
|
+
return {
|
|
3177
|
+
packageName,
|
|
3178
|
+
status: "reachable",
|
|
3179
|
+
reason: `Package '${packageName}' is a transitive dependency of imported package '${transitivePath.directDependency}'`,
|
|
3180
|
+
reachabilityBasis: "transitive-path-found",
|
|
3181
|
+
evidence: []
|
|
3182
|
+
};
|
|
3183
|
+
}
|
|
2411
3184
|
return {
|
|
2412
3185
|
packageName,
|
|
2413
3186
|
status: "not-reachable",
|
|
@@ -2536,270 +3309,64 @@ async function runSecOpsPreflight(normalizedId, cveDetails, opts) {
|
|
|
2536
3309
|
id: cveDetails.id,
|
|
2537
3310
|
kev: cveDetails.kev,
|
|
2538
3311
|
epss: cveDetails.epss
|
|
2539
|
-
},
|
|
2540
|
-
policy: { exploitSignalOverride: opts.exploitSignalOverride }
|
|
2541
|
-
});
|
|
2542
|
-
if (result.exploitSignalTriggered) {
|
|
2543
|
-
exploitSignalTriggered = true;
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
let slaBreaches;
|
|
2547
|
-
if (opts.slaCheck && opts.slaPolicy && cveDetails.publishedAt) {
|
|
2548
|
-
const breach = checkSlaBreach(
|
|
2549
|
-
normalizedId,
|
|
2550
|
-
cveDetails.severity,
|
|
2551
|
-
cveDetails.publishedAt,
|
|
2552
|
-
opts.slaPolicy
|
|
2553
|
-
);
|
|
2554
|
-
if (breach) {
|
|
2555
|
-
slaBreaches = [breach];
|
|
2556
|
-
}
|
|
2557
|
-
}
|
|
2558
|
-
return { suppressed: false, exploitSignalTriggered, slaBreaches };
|
|
2559
|
-
}
|
|
2560
|
-
|
|
2561
|
-
// src/remediation/local/sbom.ts
|
|
2562
|
-
function buildSbom(packages, vulnerableNames, results) {
|
|
2563
|
-
const statusByPackage = /* @__PURE__ */ new Map();
|
|
2564
|
-
for (const result of results) {
|
|
2565
|
-
if (!vulnerableNames.has(result.packageName)) continue;
|
|
2566
|
-
if (result.suppressedBy) {
|
|
2567
|
-
statusByPackage.set(result.packageName, "suppressed");
|
|
2568
|
-
} else if (!result.applied && result.strategy === "none") {
|
|
2569
|
-
statusByPackage.set(result.packageName, "skipped");
|
|
2570
|
-
} else if (result.applied) {
|
|
2571
|
-
statusByPackage.set(result.packageName, "patched");
|
|
2572
|
-
} else {
|
|
2573
|
-
statusByPackage.set(result.packageName, "unpatched");
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
return packages.map((pkg) => {
|
|
2577
|
-
const isVulnerable = vulnerableNames.has(pkg.name);
|
|
2578
|
-
const entry = {
|
|
2579
|
-
name: pkg.name,
|
|
2580
|
-
version: pkg.version,
|
|
2581
|
-
scope: pkg.type === "direct" ? "direct" : "transitive"
|
|
2582
|
-
};
|
|
2583
|
-
if (isVulnerable) {
|
|
2584
|
-
entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
|
|
2585
|
-
}
|
|
2586
|
-
return entry;
|
|
2587
|
-
});
|
|
2588
|
-
}
|
|
2589
|
-
|
|
2590
|
-
// src/intelligence/sources/registry.ts
|
|
2591
|
-
import { readFileSync as readFileSync8 } from "fs";
|
|
2592
|
-
import { join as join10 } from "path";
|
|
2593
|
-
import { execa as execa4 } from "execa";
|
|
2594
|
-
import semver from "semver";
|
|
2595
|
-
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
2596
|
-
async function fetchPackageVersions(packageName) {
|
|
2597
|
-
const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
|
|
2598
|
-
try {
|
|
2599
|
-
const res = await httpClient({ url });
|
|
2600
|
-
if (res.status === 404) return [];
|
|
2601
|
-
if (!res.ok) {
|
|
2602
|
-
throw new Error(`npm registry error ${res.status} for "${packageName}": ${res.text}`);
|
|
2603
|
-
}
|
|
2604
|
-
const data = res.data;
|
|
2605
|
-
return Object.keys(data.versions);
|
|
2606
|
-
} catch (err) {
|
|
2607
|
-
if (err instanceof Error && err.message.includes("registry error")) {
|
|
2608
|
-
throw err;
|
|
2609
|
-
}
|
|
2610
|
-
return [];
|
|
2611
|
-
}
|
|
2612
|
-
}
|
|
2613
|
-
async function findSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
|
|
2614
|
-
const resolution = await resolveSafeUpgradeVersion(
|
|
2615
|
-
packageName,
|
|
2616
|
-
installedVersion,
|
|
2617
|
-
firstPatchedVersion,
|
|
2618
|
-
vulnerableRange
|
|
2619
|
-
);
|
|
2620
|
-
return resolution.safeVersion;
|
|
2621
|
-
}
|
|
2622
|
-
async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
|
|
2623
|
-
const versions = await fetchPackageVersions(packageName);
|
|
2624
|
-
if (!versions.length) {
|
|
2625
|
-
return {
|
|
2626
|
-
candidates: {},
|
|
2627
|
-
majorOnlyFixAvailable: false
|
|
2628
|
-
};
|
|
2629
|
-
}
|
|
2630
|
-
const installed = semver.parse(installedVersion);
|
|
2631
|
-
const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
|
|
2632
|
-
if (!vulnerableRange) return true;
|
|
2633
|
-
try {
|
|
2634
|
-
return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
|
|
2635
|
-
} catch {
|
|
2636
|
-
return true;
|
|
2637
|
-
}
|
|
2638
|
-
}).sort(semver.compare);
|
|
2639
|
-
if (!candidates.length) {
|
|
2640
|
-
return {
|
|
2641
|
-
candidates: {},
|
|
2642
|
-
majorOnlyFixAvailable: false
|
|
2643
|
-
};
|
|
2644
|
-
}
|
|
2645
|
-
const categorizedCandidates = {};
|
|
2646
|
-
for (const candidate of candidates) {
|
|
2647
|
-
const level = classifyUpgradeLevel(installedVersion, candidate);
|
|
2648
|
-
if (!level) continue;
|
|
2649
|
-
if (!categorizedCandidates[level]) {
|
|
2650
|
-
categorizedCandidates[level] = candidate;
|
|
2651
|
-
}
|
|
2652
|
-
}
|
|
2653
|
-
const safeVersion = categorizedCandidates.patch ?? categorizedCandidates.minor ?? categorizedCandidates.major;
|
|
2654
|
-
if (!safeVersion) {
|
|
2655
|
-
return {
|
|
2656
|
-
candidates: categorizedCandidates,
|
|
2657
|
-
majorOnlyFixAvailable: false
|
|
2658
|
-
};
|
|
2659
|
-
}
|
|
2660
|
-
const upgradeLevel = classifyUpgradeLevel(installedVersion, safeVersion);
|
|
2661
|
-
const majorOnlyFixAvailable = !categorizedCandidates.patch && !categorizedCandidates.minor && Boolean(categorizedCandidates.major);
|
|
2662
|
-
if (!installed || !upgradeLevel) {
|
|
2663
|
-
return {
|
|
2664
|
-
safeVersion,
|
|
2665
|
-
upgradeLevel,
|
|
2666
|
-
candidates: categorizedCandidates,
|
|
2667
|
-
majorOnlyFixAvailable
|
|
2668
|
-
};
|
|
2669
|
-
}
|
|
2670
|
-
return {
|
|
2671
|
-
safeVersion,
|
|
2672
|
-
upgradeLevel,
|
|
2673
|
-
candidates: categorizedCandidates,
|
|
2674
|
-
majorOnlyFixAvailable
|
|
2675
|
-
};
|
|
2676
|
-
}
|
|
2677
|
-
function classifyUpgradeLevel(installedVersion, candidateVersion) {
|
|
2678
|
-
const installed = semver.parse(installedVersion);
|
|
2679
|
-
const candidate = semver.parse(candidateVersion);
|
|
2680
|
-
if (!installed || !candidate) return void 0;
|
|
2681
|
-
if (candidate.major > installed.major) return "major";
|
|
2682
|
-
if (candidate.minor > installed.minor) return "minor";
|
|
2683
|
-
if (candidate.patch > installed.patch || candidate.version === installed.version) {
|
|
2684
|
-
return "patch";
|
|
2685
|
-
}
|
|
2686
|
-
return void 0;
|
|
2687
|
-
}
|
|
2688
|
-
async function queryOutdatedPackages(cwd, options = {}) {
|
|
2689
|
-
const pm = options.packageManager ?? detectPackageManager(cwd);
|
|
2690
|
-
let stdout;
|
|
2691
|
-
try {
|
|
2692
|
-
const args = pm === "pnpm" ? ["outdated", "--json"] : ["outdated", "--json"];
|
|
2693
|
-
const yarnArgs = pm === "yarn" ? ["outdated", "--json"] : void 0;
|
|
2694
|
-
const finalArgs = yarnArgs ?? args;
|
|
2695
|
-
const result2 = await execa4(pm, finalArgs, {
|
|
2696
|
-
cwd,
|
|
2697
|
-
reject: false,
|
|
2698
|
-
stdio: "pipe"
|
|
2699
|
-
});
|
|
2700
|
-
if (result2.exitCode !== 0 && result2.exitCode !== 1) {
|
|
2701
|
-
const stderr = typeof result2.stderr === "string" ? result2.stderr : "";
|
|
2702
|
-
if (!result2.stdout) {
|
|
2703
|
-
throw new Error(
|
|
2704
|
-
`${pm} outdated --json failed (exit ${result2.exitCode}): ${stderr.slice(0, 500)}`
|
|
2705
|
-
);
|
|
2706
|
-
}
|
|
2707
|
-
}
|
|
2708
|
-
stdout = typeof result2.stdout === "string" ? result2.stdout : "";
|
|
2709
|
-
} catch (err) {
|
|
2710
|
-
if (err instanceof Error && !err.message.includes("outdated --json failed")) {
|
|
2711
|
-
throw new Error(`Failed to query outdated packages: ${err.message}`);
|
|
2712
|
-
}
|
|
2713
|
-
throw err;
|
|
2714
|
-
}
|
|
2715
|
-
if (!stdout.trim()) {
|
|
2716
|
-
return /* @__PURE__ */ new Map();
|
|
2717
|
-
}
|
|
2718
|
-
const rawEntries = {};
|
|
2719
|
-
if (pm === "yarn") {
|
|
2720
|
-
for (const line of stdout.split("\n")) {
|
|
2721
|
-
const trimmed = line.trim();
|
|
2722
|
-
if (!trimmed) continue;
|
|
2723
|
-
try {
|
|
2724
|
-
const parsed = JSON.parse(trimmed);
|
|
2725
|
-
if (parsed.type === "table" && parsed.data) {
|
|
2726
|
-
const tableData = parsed.data;
|
|
2727
|
-
const head = tableData.head ?? [];
|
|
2728
|
-
const currentIdx = head.indexOf("Current");
|
|
2729
|
-
const wantedIdx = head.indexOf("Wanted");
|
|
2730
|
-
const latestIdx = head.indexOf("Latest");
|
|
2731
|
-
for (const row of tableData.body ?? []) {
|
|
2732
|
-
const name = row[0];
|
|
2733
|
-
if (!name) continue;
|
|
2734
|
-
rawEntries[name] = {
|
|
2735
|
-
current: row[currentIdx] ?? "",
|
|
2736
|
-
wanted: row[wantedIdx] ?? "",
|
|
2737
|
-
latest: row[latestIdx] ?? "",
|
|
2738
|
-
type: row[head.indexOf("Package Type")] ?? "dependencies"
|
|
2739
|
-
};
|
|
2740
|
-
}
|
|
2741
|
-
}
|
|
2742
|
-
} catch {
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
} else {
|
|
2746
|
-
try {
|
|
2747
|
-
const parsed = JSON.parse(stdout);
|
|
2748
|
-
if (Array.isArray(parsed)) {
|
|
2749
|
-
for (const entry of parsed) {
|
|
2750
|
-
const name = entry.name ?? entry.packageName;
|
|
2751
|
-
if (!name) continue;
|
|
2752
|
-
rawEntries[name] = {
|
|
2753
|
-
current: entry.current,
|
|
2754
|
-
wanted: entry.wanted,
|
|
2755
|
-
latest: entry.latest,
|
|
2756
|
-
type: entry.dependencyType
|
|
2757
|
-
};
|
|
2758
|
-
}
|
|
2759
|
-
} else {
|
|
2760
|
-
Object.assign(rawEntries, parsed);
|
|
2761
|
-
}
|
|
2762
|
-
} catch {
|
|
2763
|
-
throw new Error(`Could not parse ${pm} outdated --json output.`);
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
2766
|
-
let directDeps;
|
|
2767
|
-
try {
|
|
2768
|
-
const pkgRaw = JSON.parse(readFileSync8(join10(cwd, "package.json"), "utf8"));
|
|
2769
|
-
directDeps = /* @__PURE__ */ new Set([
|
|
2770
|
-
...Object.keys(pkgRaw.dependencies ?? {}),
|
|
2771
|
-
...Object.keys(pkgRaw.devDependencies ?? {})
|
|
2772
|
-
]);
|
|
2773
|
-
} catch {
|
|
2774
|
-
directDeps = /* @__PURE__ */ new Set();
|
|
2775
|
-
}
|
|
2776
|
-
const result = /* @__PURE__ */ new Map();
|
|
2777
|
-
for (const [name, entry] of Object.entries(rawEntries)) {
|
|
2778
|
-
const currentVersion = entry.current ?? "";
|
|
2779
|
-
const wantedVersion = entry.wanted ?? currentVersion;
|
|
2780
|
-
const latestVersion = entry.latest ?? currentVersion;
|
|
2781
|
-
if (!currentVersion || !latestVersion) continue;
|
|
2782
|
-
const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
|
|
2783
|
-
if (!options.includeTransitive && dependencyScope === "transitive") continue;
|
|
2784
|
-
const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
|
|
2785
|
-
result.set(name, {
|
|
2786
|
-
currentVersion,
|
|
2787
|
-
wantedVersion,
|
|
2788
|
-
latestVersion,
|
|
2789
|
-
isMajorBump,
|
|
2790
|
-
dependencyScope
|
|
3312
|
+
},
|
|
3313
|
+
policy: { exploitSignalOverride: opts.exploitSignalOverride }
|
|
2791
3314
|
});
|
|
3315
|
+
if (result.exploitSignalTriggered) {
|
|
3316
|
+
exploitSignalTriggered = true;
|
|
3317
|
+
}
|
|
2792
3318
|
}
|
|
2793
|
-
|
|
3319
|
+
let slaBreaches;
|
|
3320
|
+
if (opts.slaCheck && opts.slaPolicy && cveDetails.publishedAt) {
|
|
3321
|
+
const breach = checkSlaBreach(
|
|
3322
|
+
normalizedId,
|
|
3323
|
+
cveDetails.severity,
|
|
3324
|
+
cveDetails.publishedAt,
|
|
3325
|
+
opts.slaPolicy
|
|
3326
|
+
);
|
|
3327
|
+
if (breach) {
|
|
3328
|
+
slaBreaches = [breach];
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
return { suppressed: false, exploitSignalTriggered, slaBreaches };
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
// src/remediation/local/sbom.ts
|
|
3335
|
+
function buildSbom(packages, vulnerableNames, results) {
|
|
3336
|
+
const statusByPackage = /* @__PURE__ */ new Map();
|
|
3337
|
+
for (const result of results) {
|
|
3338
|
+
if (!vulnerableNames.has(result.packageName)) continue;
|
|
3339
|
+
if (result.suppressedBy) {
|
|
3340
|
+
statusByPackage.set(result.packageName, "suppressed");
|
|
3341
|
+
} else if (!result.applied && result.strategy === "none") {
|
|
3342
|
+
statusByPackage.set(result.packageName, "skipped");
|
|
3343
|
+
} else if (result.applied) {
|
|
3344
|
+
statusByPackage.set(result.packageName, "patched");
|
|
3345
|
+
} else {
|
|
3346
|
+
statusByPackage.set(result.packageName, "unpatched");
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
return packages.map((pkg) => {
|
|
3350
|
+
const isVulnerable = vulnerableNames.has(pkg.name);
|
|
3351
|
+
const entry = {
|
|
3352
|
+
name: pkg.name,
|
|
3353
|
+
version: pkg.version,
|
|
3354
|
+
scope: pkg.type === "direct" ? "direct" : "transitive"
|
|
3355
|
+
};
|
|
3356
|
+
if (isVulnerable) {
|
|
3357
|
+
entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
|
|
3358
|
+
}
|
|
3359
|
+
return entry;
|
|
3360
|
+
});
|
|
2794
3361
|
}
|
|
2795
3362
|
|
|
2796
3363
|
// src/remediation/tools/apply-version-bump.ts
|
|
2797
3364
|
import { z as z4 } from "zod";
|
|
2798
|
-
import { join as
|
|
2799
|
-
import { readFileSync as
|
|
3365
|
+
import { join as join14 } from "path";
|
|
3366
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
|
|
2800
3367
|
|
|
2801
3368
|
// src/platform/safe-exec.ts
|
|
2802
|
-
import { execa as
|
|
3369
|
+
import { execa as execa6 } from "execa";
|
|
2803
3370
|
var DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([
|
|
2804
3371
|
"NODE_OPTIONS",
|
|
2805
3372
|
"NODE_PATH",
|
|
@@ -2848,7 +3415,7 @@ function safeExeca(file, args = [], options = {}) {
|
|
|
2848
3415
|
options.env ?? process.env,
|
|
2849
3416
|
options.allowNodeOptions
|
|
2850
3417
|
);
|
|
2851
|
-
return
|
|
3418
|
+
return execa6(file, args, {
|
|
2852
3419
|
...options,
|
|
2853
3420
|
timeout,
|
|
2854
3421
|
env: sanitizedEnv
|
|
@@ -2856,19 +3423,19 @@ function safeExeca(file, args = [], options = {}) {
|
|
|
2856
3423
|
}
|
|
2857
3424
|
|
|
2858
3425
|
// src/remediation/tools/apply-version-bump.ts
|
|
2859
|
-
import
|
|
3426
|
+
import semver3 from "semver";
|
|
2860
3427
|
|
|
2861
3428
|
// src/platform/repo-lock.ts
|
|
2862
|
-
import { mkdir, rm } from "fs/promises";
|
|
2863
|
-
import { join as
|
|
3429
|
+
import { mkdir, rm as rm2 } from "fs/promises";
|
|
3430
|
+
import { join as join13 } from "path";
|
|
2864
3431
|
async function sleep(ms) {
|
|
2865
|
-
await new Promise((
|
|
3432
|
+
await new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
2866
3433
|
}
|
|
2867
3434
|
async function acquireRepoLock(cwd, options = {}) {
|
|
2868
3435
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
2869
3436
|
const retryDelayMs = options.retryDelayMs ?? 125;
|
|
2870
|
-
const lockRoot =
|
|
2871
|
-
const lockPath =
|
|
3437
|
+
const lockRoot = join13(cwd, ".autoremediator", "locks");
|
|
3438
|
+
const lockPath = join13(cwd, ".autoremediator", "locks", "remediation.lock");
|
|
2872
3439
|
const startedAt = Date.now();
|
|
2873
3440
|
await mkdir(lockRoot, { recursive: true });
|
|
2874
3441
|
while (true) {
|
|
@@ -2877,7 +3444,7 @@ async function acquireRepoLock(cwd, options = {}) {
|
|
|
2877
3444
|
return {
|
|
2878
3445
|
lockPath,
|
|
2879
3446
|
release: async () => {
|
|
2880
|
-
await
|
|
3447
|
+
await rm2(lockPath, { recursive: true, force: true });
|
|
2881
3448
|
}
|
|
2882
3449
|
};
|
|
2883
3450
|
} catch {
|
|
@@ -2909,6 +3476,7 @@ var applyVersionBumpTool = defineTool({
|
|
|
2909
3476
|
dryRun: z4.boolean().default(false).describe("If true, report changes but do not write"),
|
|
2910
3477
|
policy: z4.string().optional().describe("Optional path to .autoremediator policy file"),
|
|
2911
3478
|
runTests: z4.boolean().default(false).describe("If true, run test validation after applying the fix"),
|
|
3479
|
+
typecheck: z4.boolean().default(false).describe("If true, run typecheck validation after applying the fix"),
|
|
2912
3480
|
installMode: z4.enum(["standard", "prefer-offline", "deterministic"]).optional(),
|
|
2913
3481
|
installPreferOffline: z4.boolean().optional(),
|
|
2914
3482
|
enforceFrozenLockfile: z4.boolean().optional(),
|
|
@@ -2923,13 +3491,14 @@ var applyVersionBumpTool = defineTool({
|
|
|
2923
3491
|
dryRun,
|
|
2924
3492
|
policy,
|
|
2925
3493
|
runTests,
|
|
3494
|
+
typecheck,
|
|
2926
3495
|
installMode,
|
|
2927
3496
|
installPreferOffline,
|
|
2928
3497
|
enforceFrozenLockfile,
|
|
2929
3498
|
workspace
|
|
2930
3499
|
}) => {
|
|
2931
3500
|
const pm = packageManager ?? detectPackageManager(cwd);
|
|
2932
|
-
|
|
3501
|
+
let pkgPath = join14(cwd, "package.json");
|
|
2933
3502
|
const loadedPolicy = loadPolicy(cwd, policy);
|
|
2934
3503
|
const commandConstraints = {
|
|
2935
3504
|
...loadedPolicy.constraints,
|
|
@@ -2939,9 +3508,6 @@ var applyVersionBumpTool = defineTool({
|
|
|
2939
3508
|
workspace: workspace ?? loadedPolicy.constraints?.workspace
|
|
2940
3509
|
};
|
|
2941
3510
|
const yarnMajor = pm === "yarn" ? await getYarnMajorVersion(cwd) : void 0;
|
|
2942
|
-
const installCommand = resolveInstallCommand(pm, commandConstraints, yarnMajor);
|
|
2943
|
-
const testCommand = resolveTestCommand(pm, commandConstraints);
|
|
2944
|
-
const dedupeCommand = resolveDedupeCommand(pm, commandConstraints);
|
|
2945
3511
|
if (!isPackageAllowed(loadedPolicy, packageName)) {
|
|
2946
3512
|
return {
|
|
2947
3513
|
packageName,
|
|
@@ -2954,7 +3520,7 @@ var applyVersionBumpTool = defineTool({
|
|
|
2954
3520
|
message: `Policy blocked changes for package "${packageName}".`
|
|
2955
3521
|
};
|
|
2956
3522
|
}
|
|
2957
|
-
const isMajorBump =
|
|
3523
|
+
const isMajorBump = semver3.valid(fromVersion) && semver3.valid(toVersion) && semver3.major(toVersion) > semver3.major(fromVersion);
|
|
2958
3524
|
if (isMajorBump && !loadedPolicy.allowMajorBumps) {
|
|
2959
3525
|
return {
|
|
2960
3526
|
packageName,
|
|
@@ -2967,9 +3533,10 @@ var applyVersionBumpTool = defineTool({
|
|
|
2967
3533
|
message: `Policy blocked major bump for "${packageName}" (${fromVersion} -> ${toVersion}).`
|
|
2968
3534
|
};
|
|
2969
3535
|
}
|
|
3536
|
+
pkgPath = join14(cwd, "package.json");
|
|
2970
3537
|
let pkgJson;
|
|
2971
3538
|
try {
|
|
2972
|
-
pkgJson = JSON.parse(
|
|
3539
|
+
pkgJson = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
2973
3540
|
} catch {
|
|
2974
3541
|
return {
|
|
2975
3542
|
packageName,
|
|
@@ -2981,9 +3548,20 @@ var applyVersionBumpTool = defineTool({
|
|
|
2981
3548
|
message: `Could not read package.json at "${pkgPath}".`
|
|
2982
3549
|
};
|
|
2983
3550
|
}
|
|
2984
|
-
|
|
3551
|
+
let depField = ["dependencies", "devDependencies", "peerDependencies"].find(
|
|
2985
3552
|
(f) => pkgJson[f]?.[packageName] !== void 0
|
|
2986
3553
|
);
|
|
3554
|
+
if (!depField) {
|
|
3555
|
+
const declaringWorkspace = findDeclaringWorkspacePackage(cwd, packageName, workspace);
|
|
3556
|
+
if (declaringWorkspace) {
|
|
3557
|
+
pkgPath = declaringWorkspace.manifestPath;
|
|
3558
|
+
pkgJson = declaringWorkspace.manifest;
|
|
3559
|
+
depField = ["dependencies", "devDependencies", "peerDependencies"].find(
|
|
3560
|
+
(f) => pkgJson[f]?.[packageName] !== void 0
|
|
3561
|
+
);
|
|
3562
|
+
commandConstraints.workspace = workspace ?? declaringWorkspace.name;
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
2987
3565
|
if (!depField) {
|
|
2988
3566
|
return {
|
|
2989
3567
|
packageName,
|
|
@@ -2999,9 +3577,14 @@ var applyVersionBumpTool = defineTool({
|
|
|
2999
3577
|
const prefixMatch = currentRange.match(/^([~^]?)/);
|
|
3000
3578
|
const prefix = prefixMatch?.[1] ?? "";
|
|
3001
3579
|
const newRange = `${prefix}${toVersion}`;
|
|
3580
|
+
const effectiveInstallCommand = resolveInstallCommand(pm, commandConstraints, yarnMajor);
|
|
3581
|
+
const effectiveTestCommand = resolveTestCommand(pm, commandConstraints);
|
|
3582
|
+
const effectiveTypecheckCommand = resolveTypecheckCommand(pm, commandConstraints, cwd);
|
|
3583
|
+
const effectiveDedupeCommand = resolveDedupeCommand(pm, commandConstraints);
|
|
3584
|
+
const shouldTypecheck = typecheck || Boolean(commandConstraints.typecheck);
|
|
3002
3585
|
if (dryRun) {
|
|
3003
|
-
const installCmd =
|
|
3004
|
-
const testCmd =
|
|
3586
|
+
const installCmd = effectiveInstallCommand.join(" ");
|
|
3587
|
+
const testCmd = effectiveTestCommand.join(" ");
|
|
3005
3588
|
return {
|
|
3006
3589
|
packageName,
|
|
3007
3590
|
strategy: "version-bump",
|
|
@@ -3009,14 +3592,14 @@ var applyVersionBumpTool = defineTool({
|
|
|
3009
3592
|
toVersion,
|
|
3010
3593
|
applied: false,
|
|
3011
3594
|
dryRun: true,
|
|
3012
|
-
message: `[DRY RUN] Would update ${depField}.${packageName}: "${currentRange}" -> "${newRange}", then run ${installCmd}${runTests ? ` and ${testCmd}` : ""}${
|
|
3595
|
+
message: `[DRY RUN] Would update ${depField}.${packageName}: "${currentRange}" -> "${newRange}", then run ${installCmd}${runTests ? ` and ${testCmd}` : ""}${effectiveDedupeCommand.length > 0 ? ` and ${effectiveDedupeCommand.join(" ")} (best-effort)` : ""}.`
|
|
3013
3596
|
};
|
|
3014
3597
|
}
|
|
3015
3598
|
return withRepoLock(cwd, async () => {
|
|
3016
3599
|
pkgJson[depField][packageName] = newRange;
|
|
3017
3600
|
writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
3018
3601
|
try {
|
|
3019
|
-
const [installCmd, ...installArgs] =
|
|
3602
|
+
const [installCmd, ...installArgs] = effectiveInstallCommand;
|
|
3020
3603
|
await safeExeca(installCmd, installArgs, {
|
|
3021
3604
|
cwd,
|
|
3022
3605
|
stdio: "pipe"
|
|
@@ -3033,12 +3616,12 @@ var applyVersionBumpTool = defineTool({
|
|
|
3033
3616
|
applied: false,
|
|
3034
3617
|
dryRun: false,
|
|
3035
3618
|
unresolvedReason: "install-failed",
|
|
3036
|
-
message: `${
|
|
3619
|
+
message: `${effectiveInstallCommand.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
|
|
3037
3620
|
};
|
|
3038
3621
|
}
|
|
3039
3622
|
if (runTests) {
|
|
3040
3623
|
try {
|
|
3041
|
-
const [testCmd, ...testArgs] =
|
|
3624
|
+
const [testCmd, ...testArgs] = effectiveTestCommand;
|
|
3042
3625
|
await safeExeca(testCmd, testArgs, {
|
|
3043
3626
|
cwd,
|
|
3044
3627
|
stdio: "pipe"
|
|
@@ -3047,7 +3630,38 @@ var applyVersionBumpTool = defineTool({
|
|
|
3047
3630
|
pkgJson[depField][packageName] = currentRange;
|
|
3048
3631
|
writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
3049
3632
|
try {
|
|
3050
|
-
const [rollbackCmd, ...rollbackArgs] =
|
|
3633
|
+
const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
|
|
3634
|
+
await safeExeca(rollbackCmd, rollbackArgs, {
|
|
3635
|
+
cwd,
|
|
3636
|
+
stdio: "pipe"
|
|
3637
|
+
});
|
|
3638
|
+
} catch {
|
|
3639
|
+
}
|
|
3640
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3641
|
+
return {
|
|
3642
|
+
packageName,
|
|
3643
|
+
strategy: "version-bump",
|
|
3644
|
+
fromVersion,
|
|
3645
|
+
toVersion,
|
|
3646
|
+
applied: false,
|
|
3647
|
+
dryRun: false,
|
|
3648
|
+
unresolvedReason: "validation-failed",
|
|
3649
|
+
message: `${effectiveTestCommand.join(" ")} failed after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
|
|
3650
|
+
};
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
if (shouldTypecheck && effectiveTypecheckCommand.length > 0) {
|
|
3654
|
+
try {
|
|
3655
|
+
const [tcCmd, ...tcArgs] = effectiveTypecheckCommand;
|
|
3656
|
+
await safeExeca(tcCmd, tcArgs, {
|
|
3657
|
+
cwd,
|
|
3658
|
+
stdio: "pipe"
|
|
3659
|
+
});
|
|
3660
|
+
} catch (err) {
|
|
3661
|
+
pkgJson[depField][packageName] = currentRange;
|
|
3662
|
+
writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
3663
|
+
try {
|
|
3664
|
+
const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
|
|
3051
3665
|
await safeExeca(rollbackCmd, rollbackArgs, {
|
|
3052
3666
|
cwd,
|
|
3053
3667
|
stdio: "pipe"
|
|
@@ -3063,20 +3677,22 @@ var applyVersionBumpTool = defineTool({
|
|
|
3063
3677
|
applied: false,
|
|
3064
3678
|
dryRun: false,
|
|
3065
3679
|
unresolvedReason: "validation-failed",
|
|
3066
|
-
message:
|
|
3680
|
+
message: `TypeCheck validation failed (${effectiveTypecheckCommand.join(" ")}) after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
|
|
3067
3681
|
};
|
|
3068
3682
|
}
|
|
3069
3683
|
}
|
|
3070
3684
|
let dedupeNote = "";
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3685
|
+
if (effectiveDedupeCommand.length > 0) {
|
|
3686
|
+
try {
|
|
3687
|
+
const [dedupeCmd, ...dedupeArgs] = effectiveDedupeCommand;
|
|
3688
|
+
await safeExeca(dedupeCmd, dedupeArgs, {
|
|
3689
|
+
cwd,
|
|
3690
|
+
stdio: "pipe"
|
|
3691
|
+
});
|
|
3692
|
+
} catch (err) {
|
|
3693
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3694
|
+
dedupeNote = ` Dedupe warning: ${effectiveDedupeCommand.join(" ")} failed (${message}).`;
|
|
3695
|
+
}
|
|
3080
3696
|
}
|
|
3081
3697
|
return {
|
|
3082
3698
|
packageName,
|
|
@@ -3085,7 +3701,7 @@ var applyVersionBumpTool = defineTool({
|
|
|
3085
3701
|
toVersion,
|
|
3086
3702
|
applied: true,
|
|
3087
3703
|
dryRun: false,
|
|
3088
|
-
message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion}, ran ${
|
|
3704
|
+
message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion} in ${depField}, then ran ${effectiveInstallCommand.join(" ")}${runTests ? ` and passed ${effectiveTestCommand.join(" ")}` : ""}.${dedupeNote}`
|
|
3089
3705
|
};
|
|
3090
3706
|
});
|
|
3091
3707
|
}
|
|
@@ -3093,9 +3709,9 @@ var applyVersionBumpTool = defineTool({
|
|
|
3093
3709
|
|
|
3094
3710
|
// src/remediation/tools/apply-package-override/index.ts
|
|
3095
3711
|
import { z as z5 } from "zod";
|
|
3096
|
-
import { join as
|
|
3097
|
-
import { existsSync as
|
|
3098
|
-
import
|
|
3712
|
+
import { join as join15 } from "path";
|
|
3713
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "fs";
|
|
3714
|
+
import semver4 from "semver";
|
|
3099
3715
|
|
|
3100
3716
|
// src/remediation/tools/apply-package-override/helpers.ts
|
|
3101
3717
|
async function collectDependencyTrace(cwd, pm, packageName, constraints) {
|
|
@@ -3237,9 +3853,9 @@ var applyPackageOverrideTool = defineTool({
|
|
|
3237
3853
|
workspace
|
|
3238
3854
|
}) => {
|
|
3239
3855
|
const pm = packageManager ?? detectPackageManager(cwd);
|
|
3240
|
-
const pkgPath =
|
|
3241
|
-
const denoJsonPath =
|
|
3242
|
-
const isDenoNative = pm === "deno" && !
|
|
3856
|
+
const pkgPath = join15(cwd, "package.json");
|
|
3857
|
+
const denoJsonPath = join15(cwd, "deno.json");
|
|
3858
|
+
const isDenoNative = pm === "deno" && !existsSync11(pkgPath);
|
|
3243
3859
|
const loadedPolicy = loadPolicy(cwd, policy);
|
|
3244
3860
|
const commandConstraints = {
|
|
3245
3861
|
...loadedPolicy.constraints,
|
|
@@ -3265,7 +3881,7 @@ var applyPackageOverrideTool = defineTool({
|
|
|
3265
3881
|
message: `Policy blocked changes for package "${packageName}".`
|
|
3266
3882
|
};
|
|
3267
3883
|
}
|
|
3268
|
-
const isMajorBump =
|
|
3884
|
+
const isMajorBump = semver4.valid(fromVersion) && semver4.valid(toVersion) && semver4.major(toVersion) > semver4.major(fromVersion);
|
|
3269
3885
|
if (isMajorBump && !loadedPolicy.allowMajorBumps) {
|
|
3270
3886
|
return {
|
|
3271
3887
|
packageName,
|
|
@@ -3281,7 +3897,7 @@ var applyPackageOverrideTool = defineTool({
|
|
|
3281
3897
|
if (isDenoNative) {
|
|
3282
3898
|
let denoJson;
|
|
3283
3899
|
try {
|
|
3284
|
-
denoJson = JSON.parse(
|
|
3900
|
+
denoJson = JSON.parse(readFileSync12(denoJsonPath, "utf8"));
|
|
3285
3901
|
} catch {
|
|
3286
3902
|
return {
|
|
3287
3903
|
packageName,
|
|
@@ -3384,7 +4000,7 @@ var applyPackageOverrideTool = defineTool({
|
|
|
3384
4000
|
}
|
|
3385
4001
|
let pkgJson;
|
|
3386
4002
|
try {
|
|
3387
|
-
pkgJson = JSON.parse(
|
|
4003
|
+
pkgJson = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
3388
4004
|
} catch {
|
|
3389
4005
|
return {
|
|
3390
4006
|
packageName,
|
|
@@ -3481,6 +4097,76 @@ var applyPackageOverrideTool = defineTool({
|
|
|
3481
4097
|
}
|
|
3482
4098
|
});
|
|
3483
4099
|
|
|
4100
|
+
// src/platform/supply-chain.ts
|
|
4101
|
+
async function verifyPackageTrust(packageName, targetVersion, policy) {
|
|
4102
|
+
if (!policy || !policy.minPublishAgeHours && !policy.blockInstallScripts && !policy.requireProvenance) {
|
|
4103
|
+
return { trusted: true };
|
|
4104
|
+
}
|
|
4105
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
|
|
4106
|
+
let packument = null;
|
|
4107
|
+
try {
|
|
4108
|
+
const res = await httpClient({
|
|
4109
|
+
url,
|
|
4110
|
+
headers: { Accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8" }
|
|
4111
|
+
});
|
|
4112
|
+
if (res.ok && res.data) {
|
|
4113
|
+
packument = res.data;
|
|
4114
|
+
}
|
|
4115
|
+
} catch (err) {
|
|
4116
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4117
|
+
return {
|
|
4118
|
+
trusted: false,
|
|
4119
|
+
reason: `Could not verify supply-chain trust for "${packageName}@${targetVersion}": ${msg}`
|
|
4120
|
+
};
|
|
4121
|
+
}
|
|
4122
|
+
if (!packument) {
|
|
4123
|
+
return {
|
|
4124
|
+
trusted: false,
|
|
4125
|
+
reason: `Package packument for "${packageName}" not found on registry.`
|
|
4126
|
+
};
|
|
4127
|
+
}
|
|
4128
|
+
let publishAgeHours;
|
|
4129
|
+
if (packument.time?.[targetVersion]) {
|
|
4130
|
+
const publishedAt = new Date(packument.time[targetVersion]).getTime();
|
|
4131
|
+
publishAgeHours = Math.max(0, (Date.now() - publishedAt) / (1e3 * 60 * 60));
|
|
4132
|
+
if (policy.minPublishAgeHours && publishAgeHours < policy.minPublishAgeHours) {
|
|
4133
|
+
return {
|
|
4134
|
+
trusted: false,
|
|
4135
|
+
publishAgeHours,
|
|
4136
|
+
reason: `Target release "${packageName}@${targetVersion}" was published only ${publishAgeHours.toFixed(1)}h ago (minimum required age is ${policy.minPublishAgeHours}h).`
|
|
4137
|
+
};
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
const versionData = packument.versions?.[targetVersion];
|
|
4141
|
+
const scripts = versionData?.scripts ?? {};
|
|
4142
|
+
const installScriptKeys = ["preinstall", "install", "postinstall"].filter((k) => Boolean(scripts[k]));
|
|
4143
|
+
const hasInstallScripts = installScriptKeys.length > 0;
|
|
4144
|
+
if (policy.blockInstallScripts && hasInstallScripts) {
|
|
4145
|
+
return {
|
|
4146
|
+
trusted: false,
|
|
4147
|
+
publishAgeHours,
|
|
4148
|
+
hasInstallScripts: true,
|
|
4149
|
+
reason: `Target release "${packageName}@${targetVersion}" defines risky lifecycle scripts (${installScriptKeys.join(", ")}).`
|
|
4150
|
+
};
|
|
4151
|
+
}
|
|
4152
|
+
const hasProvenance = Boolean(versionData?.dist?.attestations?.provenance || versionData?.dist?.attestations?.url);
|
|
4153
|
+
if (policy.requireProvenance && !hasProvenance) {
|
|
4154
|
+
return {
|
|
4155
|
+
trusted: false,
|
|
4156
|
+
publishAgeHours,
|
|
4157
|
+
hasInstallScripts,
|
|
4158
|
+
hasProvenance: false,
|
|
4159
|
+
reason: `Target release "${packageName}@${targetVersion}" does not have cryptographic build provenance attestations.`
|
|
4160
|
+
};
|
|
4161
|
+
}
|
|
4162
|
+
return {
|
|
4163
|
+
trusted: true,
|
|
4164
|
+
publishAgeHours,
|
|
4165
|
+
hasInstallScripts,
|
|
4166
|
+
hasProvenance
|
|
4167
|
+
};
|
|
4168
|
+
}
|
|
4169
|
+
|
|
3484
4170
|
// src/remediation/local/primary-strategy.ts
|
|
3485
4171
|
async function resolvePrimaryResult(params) {
|
|
3486
4172
|
const { vulnerable, cwd, packageManager, dryRun, policy, runTests, constraints } = params;
|
|
@@ -3551,6 +4237,22 @@ async function resolvePrimaryResult(params) {
|
|
|
3551
4237
|
}
|
|
3552
4238
|
};
|
|
3553
4239
|
}
|
|
4240
|
+
const trust2 = await verifyPackageTrust(pkg.name, safeUpgrade2.safeVersion, constraints.supplyChainPolicy);
|
|
4241
|
+
if (!trust2.trusted) {
|
|
4242
|
+
return {
|
|
4243
|
+
steps: 1,
|
|
4244
|
+
result: {
|
|
4245
|
+
packageName: pkg.name,
|
|
4246
|
+
strategy: "none",
|
|
4247
|
+
fromVersion: pkg.version,
|
|
4248
|
+
applied: false,
|
|
4249
|
+
dryRun,
|
|
4250
|
+
unresolvedReason: "policy-blocked",
|
|
4251
|
+
vulnerableRange: vulnerable.affected.vulnerableRange,
|
|
4252
|
+
message: `Supply chain trust gate blocked "${pkg.name}@${safeUpgrade2.safeVersion}": ${trust2.reason}`
|
|
4253
|
+
}
|
|
4254
|
+
};
|
|
4255
|
+
}
|
|
3554
4256
|
const overrideResult = await applyPackageOverrideTool.execute({
|
|
3555
4257
|
cwd,
|
|
3556
4258
|
packageManager,
|
|
@@ -3606,6 +4308,22 @@ async function resolvePrimaryResult(params) {
|
|
|
3606
4308
|
}
|
|
3607
4309
|
};
|
|
3608
4310
|
}
|
|
4311
|
+
const trust = await verifyPackageTrust(pkg.name, safeUpgrade.safeVersion, constraints.supplyChainPolicy);
|
|
4312
|
+
if (!trust.trusted) {
|
|
4313
|
+
return {
|
|
4314
|
+
steps: 1,
|
|
4315
|
+
result: {
|
|
4316
|
+
packageName: pkg.name,
|
|
4317
|
+
strategy: "none",
|
|
4318
|
+
fromVersion: pkg.version,
|
|
4319
|
+
applied: false,
|
|
4320
|
+
dryRun,
|
|
4321
|
+
unresolvedReason: "policy-blocked",
|
|
4322
|
+
vulnerableRange: vulnerable.affected.vulnerableRange,
|
|
4323
|
+
message: `Supply chain trust gate blocked "${pkg.name}@${safeUpgrade.safeVersion}": ${trust.reason}`
|
|
4324
|
+
}
|
|
4325
|
+
};
|
|
4326
|
+
}
|
|
3609
4327
|
const applyResult = await applyVersionBumpTool.execute({
|
|
3610
4328
|
cwd,
|
|
3611
4329
|
packageManager,
|
|
@@ -3615,6 +4333,7 @@ async function resolvePrimaryResult(params) {
|
|
|
3615
4333
|
dryRun,
|
|
3616
4334
|
policy,
|
|
3617
4335
|
runTests,
|
|
4336
|
+
typecheck: constraints.typecheck,
|
|
3618
4337
|
installMode: constraints.installMode,
|
|
3619
4338
|
installPreferOffline: constraints.installPreferOffline,
|
|
3620
4339
|
enforceFrozenLockfile: constraints.enforceFrozenLockfile,
|
|
@@ -3628,10 +4347,10 @@ async function resolvePrimaryResult(params) {
|
|
|
3628
4347
|
|
|
3629
4348
|
// src/remediation/tools/fetch-package-source.ts
|
|
3630
4349
|
import { z as z6 } from "zod";
|
|
3631
|
-
import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as
|
|
3632
|
-
import { join as
|
|
4350
|
+
import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile4, rm as rm3 } from "fs/promises";
|
|
4351
|
+
import { join as join16 } from "path";
|
|
3633
4352
|
import { tmpdir } from "os";
|
|
3634
|
-
import { execa as
|
|
4353
|
+
import { execa as execa7 } from "execa";
|
|
3635
4354
|
var fetchPackageSourceTool = defineTool({
|
|
3636
4355
|
description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
|
|
3637
4356
|
parameters: z6.object({
|
|
@@ -3649,25 +4368,25 @@ var fetchPackageSourceTool = defineTool({
|
|
|
3649
4368
|
if (safePatterns.length === 0) {
|
|
3650
4369
|
return { success: false, error: "No valid file patterns provided." };
|
|
3651
4370
|
}
|
|
3652
|
-
const tempBaseDir = await mkdtemp(
|
|
3653
|
-
const extractDir =
|
|
4371
|
+
const tempBaseDir = await mkdtemp(join16(tmpdir(), "autoremediator-pkg-"));
|
|
4372
|
+
const extractDir = join16(tempBaseDir, "out");
|
|
3654
4373
|
try {
|
|
3655
4374
|
const scopedName = packageName.split("/").pop();
|
|
3656
4375
|
const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
|
|
3657
4376
|
await mkdir2(tempBaseDir, { recursive: true });
|
|
3658
|
-
const tarballPath =
|
|
3659
|
-
await
|
|
4377
|
+
const tarballPath = join16(tempBaseDir, "package.tgz");
|
|
4378
|
+
await execa7("curl", ["-L", "-o", tarballPath, npmUrl]);
|
|
3660
4379
|
await mkdir2(extractDir, { recursive: true });
|
|
3661
|
-
await
|
|
4380
|
+
await execa7("tar", ["-xzf", tarballPath, "-C", extractDir]);
|
|
3662
4381
|
const extractedContents = await readdir2(extractDir);
|
|
3663
|
-
const packageRootDir = extractedContents.includes("package") ?
|
|
4382
|
+
const packageRootDir = extractedContents.includes("package") ? join16(extractDir, "package") : extractDir;
|
|
3664
4383
|
const sourceCode = {};
|
|
3665
4384
|
async function walkDir(dir, relativeBase) {
|
|
3666
4385
|
try {
|
|
3667
4386
|
const files = await readdir2(dir, { withFileTypes: true });
|
|
3668
4387
|
for (const file of files) {
|
|
3669
|
-
const fullPath =
|
|
3670
|
-
const relPath =
|
|
4388
|
+
const fullPath = join16(dir, file.name);
|
|
4389
|
+
const relPath = join16(relativeBase, file.name);
|
|
3671
4390
|
if (file.isDirectory()) {
|
|
3672
4391
|
if (!["node_modules", ".git", "dist", "build", "coverage", ".next", "out"].includes(
|
|
3673
4392
|
file.name
|
|
@@ -3681,7 +4400,7 @@ var fetchPackageSourceTool = defineTool({
|
|
|
3681
4400
|
});
|
|
3682
4401
|
if (matches) {
|
|
3683
4402
|
try {
|
|
3684
|
-
const content = await
|
|
4403
|
+
const content = await readFile4(fullPath, "utf8");
|
|
3685
4404
|
sourceCode[relPath] = content;
|
|
3686
4405
|
} catch {
|
|
3687
4406
|
}
|
|
@@ -3716,7 +4435,7 @@ var fetchPackageSourceTool = defineTool({
|
|
|
3716
4435
|
error: `Failed to fetch and extract package ${packageName}@${version}: ${message}`
|
|
3717
4436
|
};
|
|
3718
4437
|
} finally {
|
|
3719
|
-
await
|
|
4438
|
+
await rm3(tempBaseDir, { recursive: true, force: true });
|
|
3720
4439
|
}
|
|
3721
4440
|
}
|
|
3722
4441
|
});
|
|
@@ -3774,6 +4493,7 @@ Analyze the source code to:
|
|
|
3774
4493
|
2. Explain the root cause of the security issue
|
|
3775
4494
|
3. Propose a secure fix that addresses the vulnerability
|
|
3776
4495
|
4. Provide the complete fixed version of affected files
|
|
4496
|
+
5. Optionally provide a standalone Node.js reproduction/regression test script
|
|
3777
4497
|
|
|
3778
4498
|
## Response Format
|
|
3779
4499
|
Respond ONLY with valid JSON (no markdown, no extra text):
|
|
@@ -3784,7 +4504,8 @@ Respond ONLY with valid JSON (no markdown, no extra text):
|
|
|
3784
4504
|
"path/to/other.ts": "Complete fixed source code for this file"
|
|
3785
4505
|
},
|
|
3786
4506
|
"confidence": 0.95,
|
|
3787
|
-
"riskLevel": "medium"
|
|
4507
|
+
"riskLevel": "medium",
|
|
4508
|
+
"reproductionTest": "// Node.js script asserting the patch resolves the exploit/regression"
|
|
3788
4509
|
}
|
|
3789
4510
|
|
|
3790
4511
|
Important:
|
|
@@ -4064,6 +4785,7 @@ var generatePatchTool = defineTool({
|
|
|
4064
4785
|
success: true,
|
|
4065
4786
|
patches,
|
|
4066
4787
|
patchContent: patches[0]?.unifiedDiff,
|
|
4788
|
+
reproductionTest: analysis.reproductionTest,
|
|
4067
4789
|
llmProvider: provider,
|
|
4068
4790
|
llmModel: modelName,
|
|
4069
4791
|
latencyMs,
|
|
@@ -4088,17 +4810,17 @@ var generatePatchTool = defineTool({
|
|
|
4088
4810
|
|
|
4089
4811
|
// src/remediation/tools/apply-patch-file/index.ts
|
|
4090
4812
|
import { z as z8 } from "zod";
|
|
4091
|
-
import { mkdir as mkdir3, writeFile as
|
|
4092
|
-
import { join as
|
|
4093
|
-
import { execa as
|
|
4813
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
4814
|
+
import { join as join18 } from "path";
|
|
4815
|
+
import { execa as execa9 } from "execa";
|
|
4094
4816
|
|
|
4095
4817
|
// src/remediation/tools/apply-patch-file/helpers.ts
|
|
4096
|
-
import { existsSync as
|
|
4097
|
-
import { mkdtemp as mkdtemp2, readFile as
|
|
4818
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4819
|
+
import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm4, writeFile as writeFile2 } from "fs/promises";
|
|
4098
4820
|
import { createHash } from "crypto";
|
|
4099
4821
|
import { tmpdir as tmpdir2 } from "os";
|
|
4100
|
-
import { join as
|
|
4101
|
-
import { execa as
|
|
4822
|
+
import { join as join17 } from "path";
|
|
4823
|
+
import { execa as execa8 } from "execa";
|
|
4102
4824
|
async function resolvePatchMode(packageManager, cwd) {
|
|
4103
4825
|
if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno")
|
|
4104
4826
|
return "patch-package";
|
|
@@ -4129,13 +4851,13 @@ function computePatchIntegrity(patchContent) {
|
|
|
4129
4851
|
return `sha256:${hex}`;
|
|
4130
4852
|
}
|
|
4131
4853
|
async function writePatchManifest(manifestFilePath, artifact) {
|
|
4132
|
-
await
|
|
4854
|
+
await writeFile2(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
|
|
4133
4855
|
}
|
|
4134
4856
|
async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
4135
|
-
const pkgJsonPath =
|
|
4857
|
+
const pkgJsonPath = join17(cwd, "package.json");
|
|
4136
4858
|
let pkgJson;
|
|
4137
4859
|
try {
|
|
4138
|
-
pkgJson = JSON.parse(await
|
|
4860
|
+
pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf8"));
|
|
4139
4861
|
} catch {
|
|
4140
4862
|
return {
|
|
4141
4863
|
success: false,
|
|
@@ -4147,7 +4869,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
|
4147
4869
|
try {
|
|
4148
4870
|
const commands = getPackageManagerCommands(packageManager);
|
|
4149
4871
|
const [cmd, ...args] = commands.installDev("patch-package");
|
|
4150
|
-
await
|
|
4872
|
+
await execa8(cmd, args, {
|
|
4151
4873
|
cwd,
|
|
4152
4874
|
stdio: "pipe"
|
|
4153
4875
|
});
|
|
@@ -4168,13 +4890,13 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
|
4168
4890
|
} else if (!currentPostinstall) {
|
|
4169
4891
|
pkgJson.scripts.postinstall = patchApplyCmd;
|
|
4170
4892
|
}
|
|
4171
|
-
await
|
|
4893
|
+
await writeFile2(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
4172
4894
|
return { success: true };
|
|
4173
4895
|
}
|
|
4174
4896
|
async function capturePackageJsonSnapshot(cwd) {
|
|
4175
|
-
const path =
|
|
4897
|
+
const path = join17(cwd, "package.json");
|
|
4176
4898
|
try {
|
|
4177
|
-
const content = await
|
|
4899
|
+
const content = await readFile5(path, "utf8");
|
|
4178
4900
|
return { path, content };
|
|
4179
4901
|
} catch {
|
|
4180
4902
|
return void 0;
|
|
@@ -4190,19 +4912,19 @@ async function cleanupPatchArtifacts(params) {
|
|
|
4190
4912
|
installCommand,
|
|
4191
4913
|
rerunInstall
|
|
4192
4914
|
} = params;
|
|
4193
|
-
await
|
|
4915
|
+
await rm4(patchFilePath, { force: true }).catch(() => void 0);
|
|
4194
4916
|
if (manifestFilePath) {
|
|
4195
|
-
await
|
|
4917
|
+
await rm4(manifestFilePath, { force: true }).catch(() => void 0);
|
|
4196
4918
|
}
|
|
4197
4919
|
if (patchMode === "patch-package" && packageJsonSnapshot) {
|
|
4198
|
-
await
|
|
4920
|
+
await writeFile2(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
|
|
4199
4921
|
() => void 0
|
|
4200
4922
|
);
|
|
4201
4923
|
}
|
|
4202
4924
|
if (!rerunInstall) return;
|
|
4203
4925
|
try {
|
|
4204
4926
|
const [installCmd, ...installArgs] = installCommand;
|
|
4205
|
-
await
|
|
4927
|
+
await execa8(installCmd, installArgs, {
|
|
4206
4928
|
cwd,
|
|
4207
4929
|
stdio: "pipe"
|
|
4208
4930
|
});
|
|
@@ -4216,7 +4938,7 @@ async function applyNativePatch(params) {
|
|
|
4216
4938
|
const createArgs = ["patch", packageSpec];
|
|
4217
4939
|
let patchDir;
|
|
4218
4940
|
try {
|
|
4219
|
-
const createResult = await
|
|
4941
|
+
const createResult = await execa8(createCommand, createArgs, {
|
|
4220
4942
|
cwd,
|
|
4221
4943
|
stdio: "pipe"
|
|
4222
4944
|
});
|
|
@@ -4244,17 +4966,17 @@ ${createResult.stderr}`);
|
|
|
4244
4966
|
error: `Could not determine native patch directory for ${packageSpec}.`
|
|
4245
4967
|
};
|
|
4246
4968
|
}
|
|
4247
|
-
const tempPatchDir = await mkdtemp2(
|
|
4248
|
-
const tempPatchFile =
|
|
4969
|
+
const tempPatchDir = await mkdtemp2(join17(tmpdir2(), "autoremediator-native-patch-"));
|
|
4970
|
+
const tempPatchFile = join17(tempPatchDir, "change.patch");
|
|
4249
4971
|
try {
|
|
4250
|
-
await
|
|
4251
|
-
await
|
|
4972
|
+
await writeFile2(tempPatchFile, patchContent, "utf8");
|
|
4973
|
+
await execa8("patch", ["-p1", "-i", tempPatchFile], {
|
|
4252
4974
|
cwd: patchDir,
|
|
4253
4975
|
stdio: "pipe"
|
|
4254
4976
|
});
|
|
4255
4977
|
const commitCommand = patchMode === "native-pnpm" ? "pnpm" : "yarn";
|
|
4256
4978
|
const commitArgs = patchMode === "native-pnpm" ? ["patch-commit", patchDir] : ["patch-commit", "-s", patchDir];
|
|
4257
|
-
await
|
|
4979
|
+
await execa8(commitCommand, commitArgs, {
|
|
4258
4980
|
cwd,
|
|
4259
4981
|
stdio: "pipe"
|
|
4260
4982
|
});
|
|
@@ -4269,19 +4991,19 @@ ${createResult.stderr}`);
|
|
|
4269
4991
|
error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
|
|
4270
4992
|
};
|
|
4271
4993
|
} finally {
|
|
4272
|
-
await
|
|
4994
|
+
await rm4(tempPatchDir, { recursive: true, force: true });
|
|
4273
4995
|
}
|
|
4274
4996
|
return { success: true };
|
|
4275
4997
|
}
|
|
4276
4998
|
function extractPatchDirectory(output) {
|
|
4277
4999
|
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
4278
5000
|
for (const line of lines) {
|
|
4279
|
-
if (
|
|
5001
|
+
if (existsSync12(line)) {
|
|
4280
5002
|
return line;
|
|
4281
5003
|
}
|
|
4282
5004
|
const tokens = line.split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, ""));
|
|
4283
5005
|
for (const token of tokens) {
|
|
4284
|
-
if (token.startsWith("/") &&
|
|
5006
|
+
if (token.startsWith("/") && existsSync12(token)) {
|
|
4285
5007
|
return token;
|
|
4286
5008
|
}
|
|
4287
5009
|
}
|
|
@@ -4291,7 +5013,7 @@ function extractPatchDirectory(output) {
|
|
|
4291
5013
|
async function validatePatchWithTests(cwd, testCommand) {
|
|
4292
5014
|
try {
|
|
4293
5015
|
const [cmd, ...args] = testCommand;
|
|
4294
|
-
const result = await
|
|
5016
|
+
const result = await execa8(cmd, args, {
|
|
4295
5017
|
cwd,
|
|
4296
5018
|
timeout: 6e4,
|
|
4297
5019
|
stdio: "pipe"
|
|
@@ -4351,6 +5073,7 @@ var applyPatchFileTool = defineTool({
|
|
|
4351
5073
|
enforceFrozenLockfile: z8.boolean().optional(),
|
|
4352
5074
|
workspace: z8.string().optional(),
|
|
4353
5075
|
validateWithTests: z8.boolean().optional().default(true).describe("Run package manager test command to validate patch doesn't break anything"),
|
|
5076
|
+
reproductionTest: z8.string().optional().describe("Optional synthesized reproduction test script verifying fix"),
|
|
4354
5077
|
dryRun: z8.boolean().optional().default(false).describe("If true, report but do not mutate files")
|
|
4355
5078
|
}).refine((value) => Boolean(value.patchContent || value.patches && value.patches.length > 0), {
|
|
4356
5079
|
message: "Either patchContent or patches must be provided"
|
|
@@ -4372,6 +5095,7 @@ var applyPatchFileTool = defineTool({
|
|
|
4372
5095
|
workspace,
|
|
4373
5096
|
riskLevel,
|
|
4374
5097
|
validateWithTests,
|
|
5098
|
+
reproductionTest,
|
|
4375
5099
|
dryRun
|
|
4376
5100
|
}) => {
|
|
4377
5101
|
try {
|
|
@@ -4422,7 +5146,7 @@ var applyPatchFileTool = defineTool({
|
|
|
4422
5146
|
};
|
|
4423
5147
|
}
|
|
4424
5148
|
const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
|
|
4425
|
-
const patchFilePath =
|
|
5149
|
+
const patchFilePath = join18(cwd, patchesDir, patchFileName);
|
|
4426
5150
|
const manifestFilePath = `${patchFilePath}.json`;
|
|
4427
5151
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4428
5152
|
const baseArtifact = {
|
|
@@ -4461,9 +5185,9 @@ var applyPatchFileTool = defineTool({
|
|
|
4461
5185
|
}
|
|
4462
5186
|
return withRepoLock(cwd, async () => {
|
|
4463
5187
|
const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
|
|
4464
|
-
const patchesDirPath =
|
|
5188
|
+
const patchesDirPath = join18(cwd, patchesDir);
|
|
4465
5189
|
await mkdir3(patchesDirPath, { recursive: true });
|
|
4466
|
-
await
|
|
5190
|
+
await writeFile3(patchFilePath, selectedPatch, "utf8");
|
|
4467
5191
|
validationPhases.push({
|
|
4468
5192
|
phase: "patch-write",
|
|
4469
5193
|
passed: true,
|
|
@@ -4531,7 +5255,7 @@ var applyPatchFileTool = defineTool({
|
|
|
4531
5255
|
if (patchMode === "patch-package") {
|
|
4532
5256
|
try {
|
|
4533
5257
|
const [installCmd, ...installArgs] = installCommand;
|
|
4534
|
-
await
|
|
5258
|
+
await execa9(installCmd, installArgs, {
|
|
4535
5259
|
cwd,
|
|
4536
5260
|
stdio: "pipe"
|
|
4537
5261
|
});
|
|
@@ -4624,10 +5348,33 @@ var applyPatchFileTool = defineTool({
|
|
|
4624
5348
|
message: "Patch validation tests passed."
|
|
4625
5349
|
});
|
|
4626
5350
|
}
|
|
5351
|
+
let reproductionTestPath;
|
|
5352
|
+
if (reproductionTest) {
|
|
5353
|
+
const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
|
|
5354
|
+
reproductionTestPath = join18(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
|
|
5355
|
+
await writeFile3(reproductionTestPath, reproductionTest, "utf8");
|
|
5356
|
+
try {
|
|
5357
|
+
const reproRun = await execa9("node", [reproductionTestPath], { cwd, stdio: "pipe" });
|
|
5358
|
+
validationPhases.push({
|
|
5359
|
+
phase: "reproduction-test",
|
|
5360
|
+
passed: reproRun.exitCode === 0,
|
|
5361
|
+
message: "Reproduction test verified fix successfully."
|
|
5362
|
+
});
|
|
5363
|
+
} catch (err) {
|
|
5364
|
+
const reproErr = err instanceof Error ? err.message : String(err);
|
|
5365
|
+
validationPhases.push({
|
|
5366
|
+
phase: "reproduction-test",
|
|
5367
|
+
passed: false,
|
|
5368
|
+
error: `Reproduction test failed: ${reproErr}`
|
|
5369
|
+
});
|
|
5370
|
+
}
|
|
5371
|
+
}
|
|
4627
5372
|
const finalArtifact = {
|
|
4628
5373
|
...artifact,
|
|
4629
5374
|
applied: true,
|
|
4630
5375
|
dryRun: false,
|
|
5376
|
+
reproductionTest,
|
|
5377
|
+
reproductionTestPath,
|
|
4631
5378
|
validationPhases
|
|
4632
5379
|
};
|
|
4633
5380
|
await writePatchManifest(manifestFilePath, finalArtifact);
|
|
@@ -4937,7 +5684,11 @@ function resolveLocalRunOptions(options) {
|
|
|
4937
5684
|
const runTests = options.runTests ?? false;
|
|
4938
5685
|
const policy = options.policy ?? "";
|
|
4939
5686
|
const patchesDir = options.patchesDir || "./patches";
|
|
4940
|
-
const constraints =
|
|
5687
|
+
const constraints = {
|
|
5688
|
+
...options.constraints,
|
|
5689
|
+
typecheck: options.typecheck ?? options.constraints?.typecheck,
|
|
5690
|
+
supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy
|
|
5691
|
+
};
|
|
4941
5692
|
const loadedPolicy = loadPolicy(cwd, options.policy);
|
|
4942
5693
|
const llmProvider = resolveProvider(options);
|
|
4943
5694
|
return {
|
|
@@ -4974,7 +5725,7 @@ function resolveLocalRunOptions(options) {
|
|
|
4974
5725
|
}
|
|
4975
5726
|
|
|
4976
5727
|
// src/remediation/local/vulnerability-match.ts
|
|
4977
|
-
import
|
|
5728
|
+
import semver5 from "semver";
|
|
4978
5729
|
function findVulnerablePackages(cveDetails, installedPackages) {
|
|
4979
5730
|
const vulnerablePackages = [];
|
|
4980
5731
|
for (const affected of cveDetails.affectedPackages) {
|
|
@@ -4983,10 +5734,10 @@ function findVulnerablePackages(cveDetails, installedPackages) {
|
|
|
4983
5734
|
if (affected.ecosystem !== "npm") continue;
|
|
4984
5735
|
const matches = installedPackages.filter((pkg) => pkg.name === affected.name);
|
|
4985
5736
|
for (const installed of matches) {
|
|
4986
|
-
if (!
|
|
5737
|
+
if (!semver5.valid(installed.version)) continue;
|
|
4987
5738
|
let isVulnerable = false;
|
|
4988
5739
|
try {
|
|
4989
|
-
isVulnerable =
|
|
5740
|
+
isVulnerable = semver5.satisfies(installed.version, affected.vulnerableRange, {
|
|
4990
5741
|
includePrerelease: false
|
|
4991
5742
|
});
|
|
4992
5743
|
} catch {
|
|
@@ -5329,7 +6080,7 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
5329
6080
|
};
|
|
5330
6081
|
if (regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
|
|
5331
6082
|
try {
|
|
5332
|
-
if (
|
|
6083
|
+
if (semver6.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
|
|
5333
6084
|
includePrerelease: false
|
|
5334
6085
|
})) {
|
|
5335
6086
|
primaryResult.regressionDetected = true;
|
|
@@ -5420,8 +6171,8 @@ function accumulateStepResults(params) {
|
|
|
5420
6171
|
}
|
|
5421
6172
|
|
|
5422
6173
|
// src/remediation/orchestration-prompt.ts
|
|
5423
|
-
import { existsSync as
|
|
5424
|
-
import { join as
|
|
6174
|
+
import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
|
|
6175
|
+
import { join as join19 } from "path";
|
|
5425
6176
|
function buildProviderAddendum(provider, personality = "balanced") {
|
|
5426
6177
|
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.";
|
|
5427
6178
|
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.";
|
|
@@ -5432,13 +6183,13 @@ Provider profile:
|
|
|
5432
6183
|
- ${personalityDirective}`;
|
|
5433
6184
|
}
|
|
5434
6185
|
function loadOrchestrationPrompt(ctx) {
|
|
5435
|
-
const promptPath =
|
|
6186
|
+
const promptPath = join19(
|
|
5436
6187
|
process.cwd(),
|
|
5437
6188
|
".github",
|
|
5438
6189
|
"instructions",
|
|
5439
6190
|
"orchestration.instructions.md"
|
|
5440
6191
|
);
|
|
5441
|
-
if (!
|
|
6192
|
+
if (!existsSync13(promptPath)) {
|
|
5442
6193
|
return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
|
|
5443
6194
|
Working directory: ${ctx.cwd}
|
|
5444
6195
|
Package manager: ${ctx.packageManager}
|
|
@@ -5464,7 +6215,7 @@ Fallback sequence (when neither version bump nor override can be applied):
|
|
|
5464
6215
|
|
|
5465
6216
|
Always respect dryRun and policy constraints.`;
|
|
5466
6217
|
}
|
|
5467
|
-
const template =
|
|
6218
|
+
const template = readFileSync13(promptPath, "utf8");
|
|
5468
6219
|
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(
|
|
5469
6220
|
"{{directDependenciesOnly}}",
|
|
5470
6221
|
String(ctx.constraints.directDependenciesOnly ?? false)
|
|
@@ -5481,77 +6232,13 @@ function createProgressEmitter(options) {
|
|
|
5481
6232
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5482
6233
|
provider: extra?.provider,
|
|
5483
6234
|
model: extra?.model
|
|
5484
|
-
});
|
|
5485
|
-
};
|
|
5486
|
-
}
|
|
5487
|
-
|
|
5488
|
-
// src/remediation/tools/lookup-cve.ts
|
|
5489
|
-
import { z as z9 } from "zod";
|
|
5490
|
-
|
|
5491
|
-
// src/intelligence/sources/cisa-kev.ts
|
|
5492
|
-
var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
|
|
5493
|
-
async function fetchCisaKevFeed() {
|
|
5494
|
-
try {
|
|
5495
|
-
const res = await httpClient({ url: CISA_KEV_URL });
|
|
5496
|
-
if (!res.ok) return void 0;
|
|
5497
|
-
return res.data;
|
|
5498
|
-
} catch {
|
|
5499
|
-
return void 0;
|
|
5500
|
-
}
|
|
5501
|
-
}
|
|
5502
|
-
function findKevEntry(feed, cveId) {
|
|
5503
|
-
if (!feed?.vulnerabilities?.length) return void 0;
|
|
5504
|
-
const normalized = cveId.toUpperCase();
|
|
5505
|
-
return feed.vulnerabilities.find((v) => v.cveID.toUpperCase() === normalized);
|
|
5506
|
-
}
|
|
5507
|
-
async function enrichWithCisaKev(details) {
|
|
5508
|
-
const feed = await fetchCisaKevFeed();
|
|
5509
|
-
const entry = findKevEntry(feed, details.id);
|
|
5510
|
-
if (!entry) return details;
|
|
5511
|
-
details.kev = {
|
|
5512
|
-
knownExploited: true,
|
|
5513
|
-
dateAdded: entry.dateAdded,
|
|
5514
|
-
dueDate: entry.dueDate,
|
|
5515
|
-
requiredAction: entry.requiredAction,
|
|
5516
|
-
knownRansomwareCampaignUse: entry.knownRansomwareCampaignUse
|
|
5517
|
-
};
|
|
5518
|
-
if (!details.references.includes(CISA_KEV_URL)) {
|
|
5519
|
-
details.references.push(CISA_KEV_URL);
|
|
5520
|
-
}
|
|
5521
|
-
return details;
|
|
5522
|
-
}
|
|
5523
|
-
|
|
5524
|
-
// src/intelligence/sources/epss.ts
|
|
5525
|
-
async function fetchEpss(cveId) {
|
|
5526
|
-
const { epssApi } = getIntelligenceSourceConfig();
|
|
5527
|
-
if (!epssApi) return void 0;
|
|
5528
|
-
try {
|
|
5529
|
-
const url = new URL(epssApi);
|
|
5530
|
-
url.searchParams.set("cve", cveId);
|
|
5531
|
-
const res = await httpClient({ url: url.toString() });
|
|
5532
|
-
if (!res.ok) return void 0;
|
|
5533
|
-
const body = res.data;
|
|
5534
|
-
return body.data?.[0];
|
|
5535
|
-
} catch {
|
|
5536
|
-
return void 0;
|
|
5537
|
-
}
|
|
5538
|
-
}
|
|
5539
|
-
async function enrichWithEpss(details) {
|
|
5540
|
-
const row = await fetchEpss(details.id);
|
|
5541
|
-
if (!row) return details;
|
|
5542
|
-
const score = Number.parseFloat(row.epss);
|
|
5543
|
-
const percentile = Number.parseFloat(row.percentile);
|
|
5544
|
-
if (!Number.isFinite(score) || !Number.isFinite(percentile)) {
|
|
5545
|
-
return details;
|
|
5546
|
-
}
|
|
5547
|
-
details.epss = {
|
|
5548
|
-
score,
|
|
5549
|
-
percentile,
|
|
5550
|
-
date: row.date
|
|
6235
|
+
});
|
|
5551
6236
|
};
|
|
5552
|
-
return details;
|
|
5553
6237
|
}
|
|
5554
6238
|
|
|
6239
|
+
// src/remediation/tools/lookup-cve.ts
|
|
6240
|
+
import { z as z9 } from "zod";
|
|
6241
|
+
|
|
5555
6242
|
// src/intelligence/sources/cve-services.ts
|
|
5556
6243
|
function pickEnglishDescription(container) {
|
|
5557
6244
|
if (!container?.descriptions?.length) return void 0;
|
|
@@ -5835,7 +6522,7 @@ var lookupCveTool = defineTool({
|
|
|
5835
6522
|
|
|
5836
6523
|
// src/remediation/tools/check-version-match.ts
|
|
5837
6524
|
import { z as z10 } from "zod";
|
|
5838
|
-
import
|
|
6525
|
+
import semver7 from "semver";
|
|
5839
6526
|
var affectedPackageSchema = z10.object({
|
|
5840
6527
|
name: z10.string(),
|
|
5841
6528
|
ecosystem: z10.literal("npm"),
|
|
@@ -5864,10 +6551,10 @@ var checkVersionMatchTool = defineTool({
|
|
|
5864
6551
|
(p) => p.name === affected.name
|
|
5865
6552
|
);
|
|
5866
6553
|
for (const installed of matches) {
|
|
5867
|
-
if (!
|
|
6554
|
+
if (!semver7.valid(installed.version)) continue;
|
|
5868
6555
|
let isVulnerable = false;
|
|
5869
6556
|
try {
|
|
5870
|
-
isVulnerable =
|
|
6557
|
+
isVulnerable = semver7.satisfies(installed.version, affected.vulnerableRange, {
|
|
5871
6558
|
includePrerelease: false
|
|
5872
6559
|
});
|
|
5873
6560
|
} catch {
|
|
@@ -6185,7 +6872,7 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
6185
6872
|
}
|
|
6186
6873
|
|
|
6187
6874
|
// src/api/change-request/index.ts
|
|
6188
|
-
import { execa as
|
|
6875
|
+
import { execa as execa10 } from "execa";
|
|
6189
6876
|
import { randomBytes } from "crypto";
|
|
6190
6877
|
function sanitizeBranchToken(value) {
|
|
6191
6878
|
return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
@@ -6214,22 +6901,68 @@ function resolveProvider2(options) {
|
|
|
6214
6901
|
return options.provider ?? "github";
|
|
6215
6902
|
}
|
|
6216
6903
|
function resolveGrouping(options) {
|
|
6217
|
-
|
|
6904
|
+
const allowed = ["all", "per-cve", "per-package", "by-severity"];
|
|
6905
|
+
if (options.grouping && !allowed.includes(options.grouping)) {
|
|
6218
6906
|
throw new Error(
|
|
6219
|
-
|
|
6907
|
+
`Unsupported changeRequest.grouping: "${options.grouping}". Supported values: ${allowed.join(", ")}`
|
|
6220
6908
|
);
|
|
6221
6909
|
}
|
|
6222
|
-
return "all";
|
|
6910
|
+
return options.grouping ?? "all";
|
|
6911
|
+
}
|
|
6912
|
+
function groupReports(reports, grouping) {
|
|
6913
|
+
if (reports.length === 0) return [];
|
|
6914
|
+
if (grouping === "all") {
|
|
6915
|
+
return [reports];
|
|
6916
|
+
}
|
|
6917
|
+
if (grouping === "per-cve") {
|
|
6918
|
+
const map = /* @__PURE__ */ new Map();
|
|
6919
|
+
for (const report of reports) {
|
|
6920
|
+
const key = report.cveId.toUpperCase();
|
|
6921
|
+
const list = map.get(key) ?? [];
|
|
6922
|
+
list.push(report);
|
|
6923
|
+
map.set(key, list);
|
|
6924
|
+
}
|
|
6925
|
+
return Array.from(map.values());
|
|
6926
|
+
}
|
|
6927
|
+
if (grouping === "per-package") {
|
|
6928
|
+
const map = /* @__PURE__ */ new Map();
|
|
6929
|
+
for (const report of reports) {
|
|
6930
|
+
const pkgs = new Set(report.results.map((r) => r.packageName));
|
|
6931
|
+
if (pkgs.size === 0) {
|
|
6932
|
+
const list = map.get("misc") ?? [];
|
|
6933
|
+
list.push(report);
|
|
6934
|
+
map.set("misc", list);
|
|
6935
|
+
} else {
|
|
6936
|
+
for (const pkg of pkgs) {
|
|
6937
|
+
const list = map.get(pkg) ?? [];
|
|
6938
|
+
list.push(report);
|
|
6939
|
+
map.set(pkg, list);
|
|
6940
|
+
}
|
|
6941
|
+
}
|
|
6942
|
+
}
|
|
6943
|
+
return Array.from(map.values());
|
|
6944
|
+
}
|
|
6945
|
+
if (grouping === "by-severity") {
|
|
6946
|
+
const map = /* @__PURE__ */ new Map();
|
|
6947
|
+
for (const report of reports) {
|
|
6948
|
+
const sev = report.cveDetails?.severity ?? "UNKNOWN";
|
|
6949
|
+
const list = map.get(sev) ?? [];
|
|
6950
|
+
list.push(report);
|
|
6951
|
+
map.set(sev, list);
|
|
6952
|
+
}
|
|
6953
|
+
return Array.from(map.values());
|
|
6954
|
+
}
|
|
6955
|
+
return [reports];
|
|
6223
6956
|
}
|
|
6224
6957
|
async function detectCurrentBranch(cwd) {
|
|
6225
|
-
const result = await
|
|
6958
|
+
const result = await execa10("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
6226
6959
|
cwd,
|
|
6227
6960
|
stdio: "pipe"
|
|
6228
6961
|
});
|
|
6229
6962
|
return result.stdout.trim();
|
|
6230
6963
|
}
|
|
6231
6964
|
async function ensureRepoHasChanges(cwd) {
|
|
6232
|
-
const diff = await
|
|
6965
|
+
const diff = await execa10("git", ["status", "--porcelain"], {
|
|
6233
6966
|
cwd,
|
|
6234
6967
|
stdio: "pipe"
|
|
6235
6968
|
});
|
|
@@ -6258,7 +6991,7 @@ async function createGitHubRequest(params) {
|
|
|
6258
6991
|
if (params.draft) {
|
|
6259
6992
|
args.push("--draft");
|
|
6260
6993
|
}
|
|
6261
|
-
const result = await
|
|
6994
|
+
const result = await execa10("gh", args, {
|
|
6262
6995
|
cwd: params.cwd,
|
|
6263
6996
|
stdio: "pipe"
|
|
6264
6997
|
});
|
|
@@ -6283,7 +7016,7 @@ async function createGitLabRequest(params) {
|
|
|
6283
7016
|
if (params.draft) {
|
|
6284
7017
|
args.push("--draft");
|
|
6285
7018
|
}
|
|
6286
|
-
const result = await
|
|
7019
|
+
const result = await execa10("glab", args, {
|
|
6287
7020
|
cwd: params.cwd,
|
|
6288
7021
|
stdio: "pipe"
|
|
6289
7022
|
});
|
|
@@ -6293,64 +7026,68 @@ async function createChangeRequestsForReports(params) {
|
|
|
6293
7026
|
const { cwd, options, reports } = params;
|
|
6294
7027
|
const provider = resolveProvider2(options);
|
|
6295
7028
|
const grouping = resolveGrouping(options);
|
|
6296
|
-
const
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
const
|
|
7029
|
+
const groups = groupReports(reports, grouping);
|
|
7030
|
+
if (groups.length === 0) {
|
|
7031
|
+
return [];
|
|
7032
|
+
}
|
|
7033
|
+
const results = [];
|
|
7034
|
+
const hasChanges = await ensureRepoHasChanges(cwd).catch(() => false);
|
|
7035
|
+
const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd).catch(() => "main");
|
|
7036
|
+
const branchPrefix = options.branchPrefix ?? "autoremediator";
|
|
7037
|
+
const pushRemote = options.pushRemote ?? "origin";
|
|
7038
|
+
if (options.pushRemote && !/^[a-zA-Z0-9._-]+$/.test(options.pushRemote)) {
|
|
7039
|
+
throw new Error(`Invalid pushRemote value: ${options.pushRemote}`);
|
|
7040
|
+
}
|
|
7041
|
+
if (options.repository && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(options.repository)) {
|
|
7042
|
+
throw new Error(`Invalid repository format: ${options.repository}`);
|
|
7043
|
+
}
|
|
7044
|
+
for (const group of groups) {
|
|
7045
|
+
const plan = buildPlan(group);
|
|
7046
|
+
const titlePrefix = options.titlePrefix?.trim().slice(0, 200);
|
|
7047
|
+
const bodyFooter = options.bodyFooter ? options.bodyFooter.slice(0, 2e3) : void 0;
|
|
7048
|
+
const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
|
|
7049
|
+
const body = bodyFooter ? `${plan.body}
|
|
6301
7050
|
|
|
6302
7051
|
${bodyFooter}` : plan.body;
|
|
6303
|
-
try {
|
|
6304
|
-
const hasChanges = await ensureRepoHasChanges(cwd);
|
|
6305
7052
|
if (!hasChanges) {
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
];
|
|
7053
|
+
results.push({
|
|
7054
|
+
provider,
|
|
7055
|
+
grouping,
|
|
7056
|
+
repository: options.repository,
|
|
7057
|
+
branchName: "",
|
|
7058
|
+
title,
|
|
7059
|
+
body,
|
|
7060
|
+
created: false,
|
|
7061
|
+
cveIds: plan.cveIds,
|
|
7062
|
+
packageNames: plan.packageNames,
|
|
7063
|
+
error: "No repository changes to include in change request."
|
|
7064
|
+
});
|
|
7065
|
+
continue;
|
|
6320
7066
|
}
|
|
6321
|
-
const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd);
|
|
6322
|
-
const branchPrefix = options.branchPrefix ?? "autoremediator";
|
|
6323
7067
|
const branchName = toBranchName(branchPrefix, plan.cveIds);
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
target: baseBranch,
|
|
6348
|
-
title,
|
|
6349
|
-
body,
|
|
6350
|
-
draft: options.draft
|
|
6351
|
-
});
|
|
6352
|
-
return [
|
|
6353
|
-
{
|
|
7068
|
+
try {
|
|
7069
|
+
await execa10("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
|
|
7070
|
+
await execa10("git", ["add", "-A"], { cwd, stdio: "pipe" });
|
|
7071
|
+
await execa10("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
|
|
7072
|
+
await execa10("git", ["push", "-u", pushRemote, branchName], { cwd, stdio: "pipe" });
|
|
7073
|
+
const url = provider === "github" ? await createGitHubRequest({
|
|
7074
|
+
cwd,
|
|
7075
|
+
repository: options.repository,
|
|
7076
|
+
head: branchName,
|
|
7077
|
+
base: baseBranch,
|
|
7078
|
+
title,
|
|
7079
|
+
body,
|
|
7080
|
+
draft: options.draft
|
|
7081
|
+
}) : await createGitLabRequest({
|
|
7082
|
+
cwd,
|
|
7083
|
+
repository: options.repository,
|
|
7084
|
+
source: branchName,
|
|
7085
|
+
target: baseBranch,
|
|
7086
|
+
title,
|
|
7087
|
+
body,
|
|
7088
|
+
draft: options.draft
|
|
7089
|
+
});
|
|
7090
|
+
results.push({
|
|
6354
7091
|
provider,
|
|
6355
7092
|
grouping,
|
|
6356
7093
|
repository: options.repository,
|
|
@@ -6362,11 +7099,13 @@ ${bodyFooter}` : plan.body;
|
|
|
6362
7099
|
url,
|
|
6363
7100
|
cveIds: plan.cveIds,
|
|
6364
7101
|
packageNames: plan.packageNames
|
|
7102
|
+
});
|
|
7103
|
+
if (groups.length > 1) {
|
|
7104
|
+
await execa10("git", ["checkout", baseBranch], { cwd, stdio: "pipe" }).catch(() => {
|
|
7105
|
+
});
|
|
6365
7106
|
}
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
return [
|
|
6369
|
-
{
|
|
7107
|
+
} catch (error) {
|
|
7108
|
+
results.push({
|
|
6370
7109
|
provider,
|
|
6371
7110
|
grouping,
|
|
6372
7111
|
repository: options.repository,
|
|
@@ -6378,9 +7117,10 @@ ${bodyFooter}` : plan.body;
|
|
|
6378
7117
|
cveIds: plan.cveIds,
|
|
6379
7118
|
packageNames: plan.packageNames,
|
|
6380
7119
|
error: error instanceof Error ? error.message : String(error)
|
|
6381
|
-
}
|
|
6382
|
-
|
|
7120
|
+
});
|
|
7121
|
+
}
|
|
6383
7122
|
}
|
|
7123
|
+
return results;
|
|
6384
7124
|
}
|
|
6385
7125
|
|
|
6386
7126
|
// src/api/context.ts
|
|
@@ -6414,7 +7154,7 @@ function resolveConstraints(options, cwd) {
|
|
|
6414
7154
|
|
|
6415
7155
|
// src/platform/evidence.ts
|
|
6416
7156
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
6417
|
-
import { join as
|
|
7157
|
+
import { join as join20 } from "path";
|
|
6418
7158
|
function createEvidenceLog(cwd, cveIds, context = {}) {
|
|
6419
7159
|
return {
|
|
6420
7160
|
runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -6446,9 +7186,9 @@ function finalizeEvidence(log) {
|
|
|
6446
7186
|
return log;
|
|
6447
7187
|
}
|
|
6448
7188
|
function writeEvidenceLog(cwd, log) {
|
|
6449
|
-
const dir =
|
|
7189
|
+
const dir = join20(cwd, ".autoremediator", "evidence");
|
|
6450
7190
|
mkdirSync3(dir, { recursive: true });
|
|
6451
|
-
const filePath =
|
|
7191
|
+
const filePath = join20(dir, `${log.runId}.json`);
|
|
6452
7192
|
writeFileSync5(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
|
|
6453
7193
|
return filePath;
|
|
6454
7194
|
}
|
|
@@ -6587,10 +7327,15 @@ function writeRemediateEvidence(cwd, evidence) {
|
|
|
6587
7327
|
}
|
|
6588
7328
|
|
|
6589
7329
|
// src/api/remediate/validators.ts
|
|
7330
|
+
var CVE_ID_REGEX = /^CVE-\d{4}-\d{1,7}$/i;
|
|
7331
|
+
var GHSA_ID_REGEX = /^GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$/i;
|
|
7332
|
+
function isVulnerabilityId(id) {
|
|
7333
|
+
return CVE_ID_REGEX.test(id) || GHSA_ID_REGEX.test(id);
|
|
7334
|
+
}
|
|
6590
7335
|
function assertValidCveId(cveId) {
|
|
6591
|
-
if (
|
|
7336
|
+
if (!isVulnerabilityId(cveId)) {
|
|
6592
7337
|
throw new Error(
|
|
6593
|
-
`Invalid
|
|
7338
|
+
`Invalid vulnerability ID: "${cveId}". Expected format: CVE-YYYY-NNNNN (e.g. CVE-2021-23337) or GHSA-xxxx-xxxx-xxxx.`
|
|
6594
7339
|
);
|
|
6595
7340
|
}
|
|
6596
7341
|
}
|
|
@@ -6686,13 +7431,14 @@ async function planRemediation(cveId, options = {}) {
|
|
|
6686
7431
|
}
|
|
6687
7432
|
|
|
6688
7433
|
// src/scanner/parse-input.ts
|
|
6689
|
-
import { extname, resolve as
|
|
6690
|
-
import { readFileSync as
|
|
6691
|
-
import { execa as
|
|
7434
|
+
import { extname, resolve as resolve4 } from "path";
|
|
7435
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
7436
|
+
import { execa as execa11 } from "execa";
|
|
6692
7437
|
|
|
6693
7438
|
// src/scanner/adapters/npm-audit.ts
|
|
6694
|
-
import { readFileSync as
|
|
7439
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
6695
7440
|
var CVE_REGEX = /CVE-\d{4}-\d{1,7}/gi;
|
|
7441
|
+
var GHSA_REGEX = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
|
|
6696
7442
|
function normalizeSeverity(raw) {
|
|
6697
7443
|
if (!raw) return "UNKNOWN";
|
|
6698
7444
|
const up = raw.toUpperCase();
|
|
@@ -6707,7 +7453,8 @@ function parseNpmAuditJsonFromString(content) {
|
|
|
6707
7453
|
for (const vuln of Object.values(report.vulnerabilities ?? {})) {
|
|
6708
7454
|
for (const viaEntry of vuln.via ?? []) {
|
|
6709
7455
|
const text = typeof viaEntry === "string" ? viaEntry : `${viaEntry.url ?? ""} ${viaEntry.name ?? ""}`;
|
|
6710
|
-
const
|
|
7456
|
+
const cveMatches = text.match(CVE_REGEX) ?? [];
|
|
7457
|
+
const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX) ?? [];
|
|
6711
7458
|
for (const match of matches) {
|
|
6712
7459
|
const cveId = match.toUpperCase();
|
|
6713
7460
|
const key = `${cveId}:${vuln.name}`;
|
|
@@ -6725,13 +7472,14 @@ function parseNpmAuditJsonFromString(content) {
|
|
|
6725
7472
|
return findings;
|
|
6726
7473
|
}
|
|
6727
7474
|
function parseNpmAuditJsonFile(filePath) {
|
|
6728
|
-
const content =
|
|
7475
|
+
const content = readFileSync14(filePath, "utf8");
|
|
6729
7476
|
return parseNpmAuditJsonFromString(content);
|
|
6730
7477
|
}
|
|
6731
7478
|
|
|
6732
7479
|
// src/scanner/adapters/yarn-audit.ts
|
|
6733
|
-
import { readFileSync as
|
|
7480
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
6734
7481
|
var CVE_REGEX2 = /CVE-\d{4}-\d{1,7}/gi;
|
|
7482
|
+
var GHSA_REGEX2 = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
|
|
6735
7483
|
function normalizeSeverity2(raw) {
|
|
6736
7484
|
if (!raw) return "UNKNOWN";
|
|
6737
7485
|
const up = raw.toUpperCase();
|
|
@@ -6756,7 +7504,8 @@ function parseYarnAuditJsonFromString(content) {
|
|
|
6756
7504
|
const packageName = advisory?.module_name;
|
|
6757
7505
|
const severity = normalizeSeverity2(advisory?.severity);
|
|
6758
7506
|
const text = `${advisory?.url ?? ""} ${(advisory?.cves ?? []).join(" ")}`;
|
|
6759
|
-
const
|
|
7507
|
+
const cveMatches = text.match(CVE_REGEX2) ?? [];
|
|
7508
|
+
const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX2) ?? [];
|
|
6760
7509
|
for (const match of matches) {
|
|
6761
7510
|
const cveId = match.toUpperCase();
|
|
6762
7511
|
const key = `${cveId}:${packageName ?? ""}`;
|
|
@@ -6773,12 +7522,12 @@ function parseYarnAuditJsonFromString(content) {
|
|
|
6773
7522
|
return findings;
|
|
6774
7523
|
}
|
|
6775
7524
|
function parseYarnAuditJsonFile(filePath) {
|
|
6776
|
-
const content =
|
|
7525
|
+
const content = readFileSync15(filePath, "utf8");
|
|
6777
7526
|
return parseYarnAuditJsonFromString(content);
|
|
6778
7527
|
}
|
|
6779
7528
|
|
|
6780
7529
|
// src/scanner/adapters/sarif.ts
|
|
6781
|
-
import { readFileSync as
|
|
7530
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
6782
7531
|
var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
|
|
6783
7532
|
function extractPackageName(result) {
|
|
6784
7533
|
const pkg = result.properties?.["packageName"];
|
|
@@ -6816,7 +7565,7 @@ function parseSarifFromString(content) {
|
|
|
6816
7565
|
return findings;
|
|
6817
7566
|
}
|
|
6818
7567
|
function parseSarifFile(filePath) {
|
|
6819
|
-
const content =
|
|
7568
|
+
const content = readFileSync16(filePath, "utf8");
|
|
6820
7569
|
return parseSarifFromString(content);
|
|
6821
7570
|
}
|
|
6822
7571
|
|
|
@@ -6825,7 +7574,7 @@ function parseScanInput(filePath, format) {
|
|
|
6825
7574
|
if (filePath.includes("\0")) {
|
|
6826
7575
|
throw new Error("Invalid scan input path: path contains null bytes");
|
|
6827
7576
|
}
|
|
6828
|
-
const resolvedPath =
|
|
7577
|
+
const resolvedPath = resolve4(filePath);
|
|
6829
7578
|
const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
|
|
6830
7579
|
if (resolved === "npm-audit") {
|
|
6831
7580
|
return parseNpmAuditJsonFile(resolvedPath);
|
|
@@ -6847,7 +7596,7 @@ async function parseScanInputFromAudit(params) {
|
|
|
6847
7596
|
}
|
|
6848
7597
|
const command = resolveAuditCommand(pm, { workspace: params.workspace });
|
|
6849
7598
|
const [cmd, ...args] = command;
|
|
6850
|
-
const result = await
|
|
7599
|
+
const result = await execa11(cmd, args, {
|
|
6851
7600
|
cwd: params.cwd,
|
|
6852
7601
|
stdio: "pipe",
|
|
6853
7602
|
reject: false
|
|
@@ -6901,7 +7650,7 @@ function inferFormat(filePath) {
|
|
|
6901
7650
|
const ext = extname(filePath).toLowerCase();
|
|
6902
7651
|
if (ext === ".sarif") return "sarif";
|
|
6903
7652
|
try {
|
|
6904
|
-
const content =
|
|
7653
|
+
const content = readFileSync17(filePath, "utf8");
|
|
6905
7654
|
const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
|
|
6906
7655
|
if (firstLine) {
|
|
6907
7656
|
const parsed = JSON.parse(firstLine);
|
|
@@ -7312,14 +8061,14 @@ async function remediatePortfolio(targets, options = {}) {
|
|
|
7312
8061
|
}
|
|
7313
8062
|
|
|
7314
8063
|
// src/api/update-outdated/index.ts
|
|
7315
|
-
import { join as
|
|
7316
|
-
import { readFileSync as
|
|
7317
|
-
import { execa as
|
|
8064
|
+
import { join as join21 } from "path";
|
|
8065
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync6 } from "fs";
|
|
8066
|
+
import { execa as execa12 } from "execa";
|
|
7318
8067
|
async function applyBump(params) {
|
|
7319
|
-
const pkgPath =
|
|
8068
|
+
const pkgPath = join21(params.cwd, "package.json");
|
|
7320
8069
|
let pkgJson;
|
|
7321
8070
|
try {
|
|
7322
|
-
pkgJson = JSON.parse(
|
|
8071
|
+
pkgJson = JSON.parse(readFileSync18(pkgPath, "utf8"));
|
|
7323
8072
|
} catch {
|
|
7324
8073
|
return {
|
|
7325
8074
|
applied: false,
|
|
@@ -7344,7 +8093,7 @@ async function applyBump(params) {
|
|
|
7344
8093
|
writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
7345
8094
|
try {
|
|
7346
8095
|
const [installCmd, ...installArgs] = params.installCommand;
|
|
7347
|
-
await
|
|
8096
|
+
await execa12(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
|
|
7348
8097
|
} catch (err) {
|
|
7349
8098
|
pkgJson[depField][params.packageName] = currentRange;
|
|
7350
8099
|
writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
@@ -7357,13 +8106,13 @@ async function applyBump(params) {
|
|
|
7357
8106
|
if (params.runTests) {
|
|
7358
8107
|
try {
|
|
7359
8108
|
const [testCmd, ...testArgs] = params.testCommand;
|
|
7360
|
-
await
|
|
8109
|
+
await execa12(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
|
|
7361
8110
|
} catch (err) {
|
|
7362
8111
|
pkgJson[depField][params.packageName] = currentRange;
|
|
7363
8112
|
writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
7364
8113
|
try {
|
|
7365
8114
|
const [rollbackCmd, ...rollbackArgs] = params.installCommand;
|
|
7366
|
-
await
|
|
8115
|
+
await execa12(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
|
|
7367
8116
|
} catch {
|
|
7368
8117
|
}
|
|
7369
8118
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -7564,131 +8313,169 @@ async function checkReachability(options) {
|
|
|
7564
8313
|
return assessPackageReachability(cwd, options.packageName, options.symbol);
|
|
7565
8314
|
}
|
|
7566
8315
|
|
|
7567
|
-
// src/
|
|
7568
|
-
import
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
}
|
|
7588
|
-
|
|
7589
|
-
if (enrich) {
|
|
8316
|
+
// src/scanner/delta-scan.ts
|
|
8317
|
+
import { existsSync as existsSync14, readFileSync as readFileSync19 } from "fs";
|
|
8318
|
+
import { join as join22 } from "path";
|
|
8319
|
+
import { execa as execa13 } from "execa";
|
|
8320
|
+
import semver9 from "semver";
|
|
8321
|
+
|
|
8322
|
+
// src/scanner/lockfile-diff.ts
|
|
8323
|
+
import YAML2 from "yaml";
|
|
8324
|
+
import semver8 from "semver";
|
|
8325
|
+
function parseLockfileDependencies(filename, content) {
|
|
8326
|
+
const result = /* @__PURE__ */ new Map();
|
|
8327
|
+
const add = (name, ver) => {
|
|
8328
|
+
if (!name || !ver) return;
|
|
8329
|
+
const cleanVer = semver8.clean(ver) || semver8.coerce(ver)?.version || ver;
|
|
8330
|
+
let set = result.get(name);
|
|
8331
|
+
if (!set) {
|
|
8332
|
+
set = /* @__PURE__ */ new Set();
|
|
8333
|
+
result.set(name, set);
|
|
8334
|
+
}
|
|
8335
|
+
set.add(cleanVer);
|
|
8336
|
+
};
|
|
8337
|
+
if (filename.endsWith("package-lock.json")) {
|
|
7590
8338
|
try {
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
8339
|
+
const data = JSON.parse(content);
|
|
8340
|
+
if (data.packages && typeof data.packages === "object") {
|
|
8341
|
+
for (const [key, pkg] of Object.entries(data.packages)) {
|
|
8342
|
+
if (!key || key === "" || typeof pkg !== "object" || !pkg) continue;
|
|
8343
|
+
const idx = key.lastIndexOf("node_modules/");
|
|
8344
|
+
if (idx !== -1) {
|
|
8345
|
+
const rawName = key.slice(idx + "node_modules/".length);
|
|
8346
|
+
const ver = pkg.version;
|
|
8347
|
+
add(rawName, ver);
|
|
8348
|
+
}
|
|
8349
|
+
}
|
|
8350
|
+
}
|
|
8351
|
+
if (data.dependencies && typeof data.dependencies === "object") {
|
|
8352
|
+
const walkDeps = (deps) => {
|
|
8353
|
+
for (const [name, info] of Object.entries(deps)) {
|
|
8354
|
+
if (!info || typeof info !== "object") continue;
|
|
8355
|
+
const depObj = info;
|
|
8356
|
+
add(name, depObj.version);
|
|
8357
|
+
if (depObj.dependencies && typeof depObj.dependencies === "object") {
|
|
8358
|
+
walkDeps(depObj.dependencies);
|
|
8359
|
+
}
|
|
8360
|
+
}
|
|
8361
|
+
};
|
|
8362
|
+
walkDeps(data.dependencies);
|
|
7606
8363
|
}
|
|
8364
|
+
} catch {
|
|
7607
8365
|
}
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
8366
|
+
} else if (filename.endsWith("pnpm-lock.yaml")) {
|
|
8367
|
+
try {
|
|
8368
|
+
const data = YAML2.parse(content);
|
|
8369
|
+
if (data?.packages && typeof data.packages === "object") {
|
|
8370
|
+
for (const [key, val] of Object.entries(data.packages)) {
|
|
8371
|
+
let rawKey = key.replace(/^\//, "");
|
|
8372
|
+
const parenIdx = rawKey.indexOf("(");
|
|
8373
|
+
if (parenIdx !== -1) {
|
|
8374
|
+
rawKey = rawKey.slice(0, parenIdx);
|
|
8375
|
+
}
|
|
8376
|
+
const lastAt = rawKey.lastIndexOf("@");
|
|
8377
|
+
if (lastAt > 0) {
|
|
8378
|
+
const name = val?.name ?? rawKey.slice(0, lastAt);
|
|
8379
|
+
const version = val?.version ?? rawKey.slice(lastAt + 1);
|
|
8380
|
+
add(name, version);
|
|
8381
|
+
}
|
|
8382
|
+
}
|
|
7614
8383
|
}
|
|
7615
|
-
|
|
7616
|
-
const
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
8384
|
+
if (data?.snapshots && typeof data.snapshots === "object") {
|
|
8385
|
+
for (const key of Object.keys(data.snapshots)) {
|
|
8386
|
+
let rawKey = key.replace(/^\//, "");
|
|
8387
|
+
const parenIdx = rawKey.indexOf("(");
|
|
8388
|
+
if (parenIdx !== -1) {
|
|
8389
|
+
rawKey = rawKey.slice(0, parenIdx);
|
|
8390
|
+
}
|
|
8391
|
+
const lastAt = rawKey.lastIndexOf("@");
|
|
8392
|
+
if (lastAt > 0) {
|
|
8393
|
+
const name = rawKey.slice(0, lastAt);
|
|
8394
|
+
const version = rawKey.slice(lastAt + 1);
|
|
8395
|
+
add(name, version);
|
|
8396
|
+
}
|
|
7620
8397
|
}
|
|
7621
|
-
} catch {
|
|
7622
8398
|
}
|
|
8399
|
+
} catch {
|
|
7623
8400
|
}
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
);
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
8401
|
+
} else if (filename.endsWith("yarn.lock")) {
|
|
8402
|
+
const lines = content.split("\n");
|
|
8403
|
+
let currentPackages = [];
|
|
8404
|
+
for (const line of lines) {
|
|
8405
|
+
if (line.startsWith("#") || line.trim() === "") continue;
|
|
8406
|
+
if (!line.startsWith(" ")) {
|
|
8407
|
+
currentPackages = [];
|
|
8408
|
+
const parts = line.split(",").map((p) => p.trim().replace(/^"/, "").replace(/":?$/, ""));
|
|
8409
|
+
for (const part of parts) {
|
|
8410
|
+
const lastAt = part.lastIndexOf("@");
|
|
8411
|
+
if (lastAt > 0) {
|
|
8412
|
+
currentPackages.push(part.slice(0, lastAt));
|
|
8413
|
+
}
|
|
8414
|
+
}
|
|
8415
|
+
} else {
|
|
8416
|
+
const match = line.match(/^\s+version\s+"?([^"\s]+)"?/);
|
|
8417
|
+
if (match && currentPackages.length > 0) {
|
|
8418
|
+
const ver = match[1];
|
|
8419
|
+
for (const pkg of currentPackages) {
|
|
8420
|
+
add(pkg, ver);
|
|
8421
|
+
}
|
|
8422
|
+
currentPackages = [];
|
|
8423
|
+
}
|
|
7635
8424
|
}
|
|
7636
8425
|
}
|
|
7637
|
-
entries.push({
|
|
7638
|
-
cveId: cveDetails.id,
|
|
7639
|
-
severity: cveDetails.severity,
|
|
7640
|
-
summary: cveDetails.summary,
|
|
7641
|
-
vulnerableRange: affected?.vulnerableRange,
|
|
7642
|
-
firstPatchedVersion: affected?.firstPatchedVersion,
|
|
7643
|
-
safeUpgradeVersion,
|
|
7644
|
-
epssScore,
|
|
7645
|
-
epssPercentile,
|
|
7646
|
-
inCisaKev,
|
|
7647
|
-
references: cveDetails.references
|
|
7648
|
-
});
|
|
7649
8426
|
}
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
8427
|
+
return result;
|
|
8428
|
+
}
|
|
8429
|
+
function computeLockfileDiff(filename, oldContent, newContent) {
|
|
8430
|
+
const changes = [];
|
|
8431
|
+
const oldDeps = oldContent ? parseLockfileDependencies(filename, oldContent) : /* @__PURE__ */ new Map();
|
|
8432
|
+
const newDeps = newContent ? parseLockfileDependencies(filename, newContent) : /* @__PURE__ */ new Map();
|
|
8433
|
+
const allPkgs = /* @__PURE__ */ new Set([...oldDeps.keys(), ...newDeps.keys()]);
|
|
8434
|
+
for (const pkg of allPkgs) {
|
|
8435
|
+
const oldVers = oldDeps.get(pkg) ?? /* @__PURE__ */ new Set();
|
|
8436
|
+
const newVers = newDeps.get(pkg) ?? /* @__PURE__ */ new Set();
|
|
8437
|
+
for (const nVer of newVers) {
|
|
8438
|
+
if (!oldVers.has(nVer)) {
|
|
8439
|
+
if (oldVers.size === 0) {
|
|
8440
|
+
changes.push({
|
|
8441
|
+
packageName: pkg,
|
|
8442
|
+
changeType: "added",
|
|
8443
|
+
newVersion: nVer,
|
|
8444
|
+
dependencyType: "transitive",
|
|
8445
|
+
manifestPath: filename
|
|
8446
|
+
});
|
|
8447
|
+
} else {
|
|
8448
|
+
const [firstOld] = oldVers;
|
|
8449
|
+
const cleanOld = semver8.clean(firstOld) || semver8.coerce(firstOld)?.version;
|
|
8450
|
+
const cleanNew = semver8.clean(nVer) || semver8.coerce(nVer)?.version;
|
|
8451
|
+
const isUpgrade = cleanOld && cleanNew && semver8.valid(cleanOld) && semver8.valid(cleanNew) ? semver8.gt(cleanNew, cleanOld) : true;
|
|
8452
|
+
changes.push({
|
|
8453
|
+
packageName: pkg,
|
|
8454
|
+
changeType: isUpgrade ? "upgraded" : "downgraded",
|
|
8455
|
+
oldVersion: firstOld,
|
|
8456
|
+
newVersion: nVer,
|
|
8457
|
+
dependencyType: "transitive",
|
|
8458
|
+
manifestPath: filename
|
|
8459
|
+
});
|
|
8460
|
+
}
|
|
8461
|
+
}
|
|
8462
|
+
}
|
|
8463
|
+
if (newVers.size === 0 && oldVers.size > 0) {
|
|
8464
|
+
for (const oVer of oldVers) {
|
|
8465
|
+
changes.push({
|
|
8466
|
+
packageName: pkg,
|
|
8467
|
+
changeType: "removed",
|
|
8468
|
+
oldVersion: oVer,
|
|
8469
|
+
dependencyType: "transitive",
|
|
8470
|
+
manifestPath: filename
|
|
8471
|
+
});
|
|
8472
|
+
}
|
|
7663
8473
|
}
|
|
7664
8474
|
}
|
|
7665
|
-
|
|
7666
|
-
if (isVulnerable) {
|
|
7667
|
-
const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver7.valid(v))).sort((a, b) => semver7.compare(a, b));
|
|
7668
|
-
recommendedVersion = upgradeCandidates[upgradeCandidates.length - 1] || latestVersion;
|
|
7669
|
-
} else {
|
|
7670
|
-
recommendedVersion = version || latestVersion;
|
|
7671
|
-
}
|
|
7672
|
-
const summary = isVulnerable ? `Package "${packageName}${version ? `@${version}` : ""}" has ${entries.length} known vulnerability(ies) (verdict: ${verdict}). Recommended safe version: ${recommendedVersion || "none"}.` : `Package "${packageName}${version ? `@${version}` : ""}" has no known vulnerabilities. Safe to install.`;
|
|
7673
|
-
return {
|
|
7674
|
-
schemaVersion: "1.0",
|
|
7675
|
-
packageName,
|
|
7676
|
-
evaluatedVersion: version,
|
|
7677
|
-
isVulnerable,
|
|
7678
|
-
verdict,
|
|
7679
|
-
summary,
|
|
7680
|
-
vulnerabilities: entries,
|
|
7681
|
-
recommendedVersion,
|
|
7682
|
-
latestVersion,
|
|
7683
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7684
|
-
};
|
|
8475
|
+
return changes;
|
|
7685
8476
|
}
|
|
7686
8477
|
|
|
7687
8478
|
// src/scanner/delta-scan.ts
|
|
7688
|
-
import { existsSync as existsSync12, readFileSync as readFileSync17 } from "fs";
|
|
7689
|
-
import { join as join20 } from "path";
|
|
7690
|
-
import { execa as execa12 } from "execa";
|
|
7691
|
-
import semver8 from "semver";
|
|
7692
8479
|
var DEP_SECTIONS = [
|
|
7693
8480
|
"dependencies",
|
|
7694
8481
|
"devDependencies",
|
|
@@ -7698,7 +8485,7 @@ var DEP_SECTIONS = [
|
|
|
7698
8485
|
async function getChangedManifestPaths(cwd, baseRef) {
|
|
7699
8486
|
const manifests = /* @__PURE__ */ new Set();
|
|
7700
8487
|
try {
|
|
7701
|
-
const { stdout: diffFiles } = await
|
|
8488
|
+
const { stdout: diffFiles } = await execa13(
|
|
7702
8489
|
"git",
|
|
7703
8490
|
["diff", "--name-only", baseRef, "--", "package.json", "**/package.json"],
|
|
7704
8491
|
{ cwd, reject: false }
|
|
@@ -7709,7 +8496,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
|
|
|
7709
8496
|
manifests.add(trimmed);
|
|
7710
8497
|
}
|
|
7711
8498
|
}
|
|
7712
|
-
const { stdout: untrackedFiles } = await
|
|
8499
|
+
const { stdout: untrackedFiles } = await execa13(
|
|
7713
8500
|
"git",
|
|
7714
8501
|
["ls-files", "--others", "--exclude-standard", "--", "package.json", "**/package.json"],
|
|
7715
8502
|
{ cwd, reject: false }
|
|
@@ -7721,7 +8508,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
|
|
|
7721
8508
|
}
|
|
7722
8509
|
}
|
|
7723
8510
|
} catch {
|
|
7724
|
-
if (
|
|
8511
|
+
if (existsSync14(join22(cwd, "package.json"))) {
|
|
7725
8512
|
manifests.add("package.json");
|
|
7726
8513
|
}
|
|
7727
8514
|
}
|
|
@@ -7729,7 +8516,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
|
|
|
7729
8516
|
}
|
|
7730
8517
|
async function getOldManifestContent(cwd, baseRef, relPath) {
|
|
7731
8518
|
try {
|
|
7732
|
-
const { stdout } = await
|
|
8519
|
+
const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
|
|
7733
8520
|
cwd,
|
|
7734
8521
|
reject: false
|
|
7735
8522
|
});
|
|
@@ -7740,10 +8527,10 @@ async function getOldManifestContent(cwd, baseRef, relPath) {
|
|
|
7740
8527
|
}
|
|
7741
8528
|
}
|
|
7742
8529
|
function getNewManifestContent(cwd, relPath) {
|
|
7743
|
-
const fullPath =
|
|
7744
|
-
if (!
|
|
8530
|
+
const fullPath = join22(cwd, relPath);
|
|
8531
|
+
if (!existsSync14(fullPath)) return null;
|
|
7745
8532
|
try {
|
|
7746
|
-
return JSON.parse(
|
|
8533
|
+
return JSON.parse(readFileSync19(fullPath, "utf8"));
|
|
7747
8534
|
} catch {
|
|
7748
8535
|
return null;
|
|
7749
8536
|
}
|
|
@@ -7764,9 +8551,9 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
|
|
|
7764
8551
|
manifestPath: relPath
|
|
7765
8552
|
});
|
|
7766
8553
|
} else if (oldVer !== newVer) {
|
|
7767
|
-
const cleanOld =
|
|
7768
|
-
const cleanNew =
|
|
7769
|
-
const isUpgrade = cleanOld && cleanNew &&
|
|
8554
|
+
const cleanOld = semver9.clean(oldVer) || semver9.coerce(oldVer)?.version;
|
|
8555
|
+
const cleanNew = semver9.clean(newVer) || semver9.coerce(newVer)?.version;
|
|
8556
|
+
const isUpgrade = cleanOld && cleanNew && semver9.valid(cleanOld) && semver9.valid(cleanNew) ? semver9.gt(cleanNew, cleanOld) : true;
|
|
7770
8557
|
changes.push({
|
|
7771
8558
|
packageName: pkgName,
|
|
7772
8559
|
changeType: isUpgrade ? "upgraded" : "downgraded",
|
|
@@ -7791,22 +8578,89 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
|
|
|
7791
8578
|
}
|
|
7792
8579
|
return changes;
|
|
7793
8580
|
}
|
|
8581
|
+
var LOCKFILE_PATTERNS = [
|
|
8582
|
+
"package-lock.json",
|
|
8583
|
+
"**/package-lock.json",
|
|
8584
|
+
"pnpm-lock.yaml",
|
|
8585
|
+
"**/pnpm-lock.yaml",
|
|
8586
|
+
"yarn.lock",
|
|
8587
|
+
"**/yarn.lock"
|
|
8588
|
+
];
|
|
8589
|
+
async function getChangedLockfilePaths(cwd, baseRef) {
|
|
8590
|
+
const lockfiles = /* @__PURE__ */ new Set();
|
|
8591
|
+
try {
|
|
8592
|
+
const { stdout: diffFiles } = await execa13(
|
|
8593
|
+
"git",
|
|
8594
|
+
["diff", "--name-only", baseRef, "--", ...LOCKFILE_PATTERNS],
|
|
8595
|
+
{ cwd, reject: false }
|
|
8596
|
+
);
|
|
8597
|
+
for (const f of diffFiles.split("\n")) {
|
|
8598
|
+
const trimmed = f.trim();
|
|
8599
|
+
if (trimmed && !trimmed.includes("node_modules") && (trimmed.endsWith("package-lock.json") || trimmed.endsWith("pnpm-lock.yaml") || trimmed.endsWith("yarn.lock"))) {
|
|
8600
|
+
lockfiles.add(trimmed);
|
|
8601
|
+
}
|
|
8602
|
+
}
|
|
8603
|
+
const { stdout: untrackedFiles } = await execa13(
|
|
8604
|
+
"git",
|
|
8605
|
+
["ls-files", "--others", "--exclude-standard", "--", ...LOCKFILE_PATTERNS],
|
|
8606
|
+
{ cwd, reject: false }
|
|
8607
|
+
);
|
|
8608
|
+
for (const f of untrackedFiles.split("\n")) {
|
|
8609
|
+
const trimmed = f.trim();
|
|
8610
|
+
if (trimmed && !trimmed.includes("node_modules") && (trimmed.endsWith("package-lock.json") || trimmed.endsWith("pnpm-lock.yaml") || trimmed.endsWith("yarn.lock"))) {
|
|
8611
|
+
lockfiles.add(trimmed);
|
|
8612
|
+
}
|
|
8613
|
+
}
|
|
8614
|
+
} catch {
|
|
8615
|
+
}
|
|
8616
|
+
return Array.from(lockfiles);
|
|
8617
|
+
}
|
|
8618
|
+
async function getFileContentAtRef(cwd, baseRef, relPath) {
|
|
8619
|
+
try {
|
|
8620
|
+
const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
|
|
8621
|
+
cwd,
|
|
8622
|
+
reject: false
|
|
8623
|
+
});
|
|
8624
|
+
return stdout || null;
|
|
8625
|
+
} catch {
|
|
8626
|
+
return null;
|
|
8627
|
+
}
|
|
8628
|
+
}
|
|
7794
8629
|
async function scanDelta(options = {}) {
|
|
7795
8630
|
const cwd = options.cwd ?? process.cwd();
|
|
7796
8631
|
const baseRef = options.baseRef ?? "HEAD";
|
|
7797
8632
|
const changedManifests = await getChangedManifestPaths(cwd, baseRef);
|
|
8633
|
+
const changedLockfiles = await getChangedLockfilePaths(cwd, baseRef);
|
|
7798
8634
|
const dependencyChanges = [];
|
|
8635
|
+
const directPkgs = /* @__PURE__ */ new Set();
|
|
7799
8636
|
for (const manifestRel of changedManifests) {
|
|
7800
8637
|
const oldPkg = await getOldManifestContent(cwd, baseRef, manifestRel);
|
|
7801
8638
|
const newPkg = getNewManifestContent(cwd, manifestRel);
|
|
7802
8639
|
const diffs = computeDependencyDiff(manifestRel, oldPkg, newPkg);
|
|
8640
|
+
for (const d of diffs) {
|
|
8641
|
+
directPkgs.add(d.packageName);
|
|
8642
|
+
}
|
|
7803
8643
|
dependencyChanges.push(...diffs);
|
|
7804
8644
|
}
|
|
8645
|
+
for (const lockfileRel of changedLockfiles) {
|
|
8646
|
+
const oldContent = await getFileContentAtRef(cwd, baseRef, lockfileRel);
|
|
8647
|
+
const fullPath = join22(cwd, lockfileRel);
|
|
8648
|
+
const newContent = existsSync14(fullPath) ? readFileSync19(fullPath, "utf8") : null;
|
|
8649
|
+
const lockDiffs = computeLockfileDiff(lockfileRel, oldContent, newContent);
|
|
8650
|
+
for (const change of lockDiffs) {
|
|
8651
|
+
if (!directPkgs.has(change.packageName)) {
|
|
8652
|
+
dependencyChanges.push(change);
|
|
8653
|
+
}
|
|
8654
|
+
}
|
|
8655
|
+
if (!changedManifests.includes(lockfileRel)) {
|
|
8656
|
+
changedManifests.push(lockfileRel);
|
|
8657
|
+
}
|
|
8658
|
+
}
|
|
7805
8659
|
const introducedFindings = [];
|
|
7806
8660
|
const resolvedFindings = [];
|
|
7807
8661
|
for (const change of dependencyChanges) {
|
|
7808
8662
|
if (change.changeType === "added" || change.changeType === "upgraded" || change.changeType === "downgraded") {
|
|
7809
|
-
const cleanVer =
|
|
8663
|
+
const cleanVer = semver9.coerce(change.newVersion)?.version ?? change.newVersion;
|
|
7810
8664
|
const evaluation = await evaluatePackage(change.packageName, {
|
|
7811
8665
|
version: cleanVer,
|
|
7812
8666
|
enrichIntelligence: true
|
|
@@ -7826,12 +8680,12 @@ async function scanDelta(options = {}) {
|
|
|
7826
8680
|
}
|
|
7827
8681
|
}
|
|
7828
8682
|
if (change.oldVersion && (change.changeType === "upgraded" || change.changeType === "removed")) {
|
|
7829
|
-
const cleanOldVer =
|
|
8683
|
+
const cleanOldVer = semver9.coerce(change.oldVersion)?.version ?? change.oldVersion;
|
|
7830
8684
|
const oldEval = await evaluatePackage(change.packageName, {
|
|
7831
8685
|
version: cleanOldVer,
|
|
7832
8686
|
enrichIntelligence: true
|
|
7833
8687
|
});
|
|
7834
|
-
const cleanNewVer = change.newVersion ?
|
|
8688
|
+
const cleanNewVer = change.newVersion ? semver9.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
|
|
7835
8689
|
const newEval = cleanNewVer ? await evaluatePackage(change.packageName, {
|
|
7836
8690
|
version: cleanNewVer,
|
|
7837
8691
|
enrichIntelligence: false
|
|
@@ -7949,6 +8803,7 @@ export {
|
|
|
7949
8803
|
listPatchArtifacts,
|
|
7950
8804
|
planRemediation,
|
|
7951
8805
|
pollJob,
|
|
8806
|
+
reconcilePatchArtifacts,
|
|
7952
8807
|
remediate,
|
|
7953
8808
|
remediateFromScan,
|
|
7954
8809
|
remediatePortfolio,
|