autoremediator 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/change-request/index.d.ts.map +1 -1
- package/dist/api/options-schema.d.ts.map +1 -1
- package/dist/api/patches/reconcile.d.ts.map +1 -1
- package/dist/api/scan-execution.d.ts.map +1 -1
- package/dist/api/vex/index.d.ts +6 -2
- package/dist/api/vex/index.d.ts.map +1 -1
- package/dist/{chunk-7BORIBPY.js → chunk-ZGLGT4XQ.js} +2589 -885
- package/dist/cli/program.d.ts.map +1 -1
- package/dist/cli/runners.d.ts.map +1 -1
- package/dist/cli/triage.d.ts +9 -1
- package/dist/cli/triage.d.ts.map +1 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli.js +74 -33
- package/dist/index.js +2464 -882
- package/dist/index.js.map +1 -1
- package/dist/intelligence/snapshot.d.ts +2 -0
- package/dist/intelligence/snapshot.d.ts.map +1 -0
- package/dist/intelligence/sources/registry.d.ts +1 -0
- package/dist/intelligence/sources/registry.d.ts.map +1 -1
- package/dist/intelligence/sources/snapshot.d.ts +1 -1
- package/dist/intelligence/sources/snapshot.d.ts.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/openapi/handlers/routes.d.ts.map +1 -1
- package/dist/openapi/handlers/validators.d.ts.map +1 -1
- package/dist/openapi/http-utils.d.ts.map +1 -1
- package/dist/openapi/server.js +20 -7
- package/dist/platform/jobs.d.ts +39 -1
- package/dist/platform/jobs.d.ts.map +1 -1
- package/dist/platform/policy.d.ts +10 -0
- package/dist/platform/policy.d.ts.map +1 -1
- package/dist/platform/rollback.d.ts +11 -0
- package/dist/platform/rollback.d.ts.map +1 -1
- package/dist/platform/types.d.ts +5 -0
- package/dist/platform/types.d.ts.map +1 -1
- package/dist/remediation/local/disposition.d.ts +5 -2
- package/dist/remediation/local/disposition.d.ts.map +1 -1
- package/dist/remediation/local/fallback.d.ts.map +1 -1
- package/dist/remediation/local/options.d.ts.map +1 -1
- package/dist/remediation/local/primary-strategy.d.ts +4 -0
- package/dist/remediation/local/primary-strategy.d.ts.map +1 -1
- package/dist/remediation/local/run.d.ts.map +1 -1
- package/dist/remediation/pipeline-runtime/tool-wrappers.d.ts.map +1 -1
- package/dist/remediation/proof-of-fix/runner.d.ts +52 -0
- package/dist/remediation/proof-of-fix/runner.d.ts.map +1 -1
- package/dist/remediation/refactor/call-site-finder.d.ts +21 -0
- package/dist/remediation/refactor/call-site-finder.d.ts.map +1 -1
- package/dist/remediation/refactor/refactor-agent.d.ts +18 -0
- package/dist/remediation/refactor/refactor-agent.d.ts.map +1 -1
- package/dist/remediation/tools/apply-package-override/index.d.ts +4 -0
- package/dist/remediation/tools/apply-package-override/index.d.ts.map +1 -1
- package/dist/remediation/tools/apply-patch-file/index.d.ts +2 -1
- package/dist/remediation/tools/apply-patch-file/index.d.ts.map +1 -1
- package/dist/remediation/tools/apply-version-bump.d.ts +4 -0
- package/dist/remediation/tools/apply-version-bump.d.ts.map +1 -1
- package/dist/remediation/tools/check-inventory.d.ts +1 -0
- package/dist/remediation/tools/check-inventory.d.ts.map +1 -1
- package/dist/remediation/tools/fetch-package-source.d.ts.map +1 -1
- package/dist/remediation/tools/find-fixed-version.d.ts.map +1 -1
- package/dist/remediation/tools/generate-patch/helpers.d.ts +3 -3
- package/dist/remediation/tools/generate-patch/helpers.d.ts.map +1 -1
- package/package.json +6 -4
- package/dist/api/change-request/index.test.d.ts +0 -2
- package/dist/api/change-request/index.test.d.ts.map +0 -1
- package/dist/api/delta-scan/delta-scan.test.d.ts +0 -2
- package/dist/api/delta-scan/delta-scan.test.d.ts.map +0 -1
- package/dist/api/evaluate/evaluate.test.d.ts +0 -2
- package/dist/api/evaluate/evaluate.test.d.ts.map +0 -1
- package/dist/api/index.test.d.ts +0 -2
- package/dist/api/index.test.d.ts.map +0 -1
- package/dist/api/jobs/index.test.d.ts +0 -2
- package/dist/api/jobs/index.test.d.ts.map +0 -1
- package/dist/api/patches/reconcile.test.d.ts +0 -2
- package/dist/api/patches/reconcile.test.d.ts.map +0 -1
- package/dist/api/patches.test.d.ts +0 -2
- package/dist/api/patches.test.d.ts.map +0 -1
- package/dist/api/portfolio/portfolio.test.d.ts +0 -2
- package/dist/api/portfolio/portfolio.test.d.ts.map +0 -1
- package/dist/api/portfolio/risk-score.test.d.ts +0 -2
- package/dist/api/portfolio/risk-score.test.d.ts.map +0 -1
- package/dist/api/reachability/reachability.test.d.ts +0 -2
- package/dist/api/reachability/reachability.test.d.ts.map +0 -1
- package/dist/api/update-outdated/index.test.d.ts +0 -2
- package/dist/api/update-outdated/index.test.d.ts.map +0 -1
- package/dist/api/vex/index.test.d.ts +0 -2
- package/dist/api/vex/index.test.d.ts.map +0 -1
- package/dist/cli/index.test.d.ts +0 -2
- package/dist/cli/index.test.d.ts.map +0 -1
- package/dist/intelligence/sources/certcc.test.d.ts +0 -2
- package/dist/intelligence/sources/certcc.test.d.ts.map +0 -1
- package/dist/intelligence/sources/cisa-kev.test.d.ts +0 -2
- package/dist/intelligence/sources/cisa-kev.test.d.ts.map +0 -1
- package/dist/intelligence/sources/cve-services.test.d.ts +0 -2
- package/dist/intelligence/sources/cve-services.test.d.ts.map +0 -1
- package/dist/intelligence/sources/deps-dev.test.d.ts +0 -2
- package/dist/intelligence/sources/deps-dev.test.d.ts.map +0 -1
- package/dist/intelligence/sources/epss.test.d.ts +0 -2
- package/dist/intelligence/sources/epss.test.d.ts.map +0 -1
- package/dist/intelligence/sources/external-feeds.test.d.ts +0 -2
- package/dist/intelligence/sources/external-feeds.test.d.ts.map +0 -1
- package/dist/intelligence/sources/gitlab-advisory.test.d.ts +0 -2
- package/dist/intelligence/sources/gitlab-advisory.test.d.ts.map +0 -1
- package/dist/intelligence/sources/nvd.test.d.ts +0 -2
- package/dist/intelligence/sources/nvd.test.d.ts.map +0 -1
- package/dist/intelligence/sources/ossf-scorecard.test.d.ts +0 -2
- package/dist/intelligence/sources/ossf-scorecard.test.d.ts.map +0 -1
- package/dist/intelligence/sources/osv.test.d.ts +0 -2
- package/dist/intelligence/sources/osv.test.d.ts.map +0 -1
- package/dist/intelligence/sources/registry.test.d.ts +0 -2
- package/dist/intelligence/sources/registry.test.d.ts.map +0 -1
- package/dist/intelligence/sources/snapshot.test.d.ts +0 -2
- package/dist/intelligence/sources/snapshot.test.d.ts.map +0 -1
- package/dist/mcp/server.test.d.ts +0 -2
- package/dist/mcp/server.test.d.ts.map +0 -1
- package/dist/openapi/server.test.d.ts +0 -2
- package/dist/openapi/server.test.d.ts.map +0 -1
- package/dist/platform/architecture-guardrails.test.d.ts +0 -2
- package/dist/platform/architecture-guardrails.test.d.ts.map +0 -1
- package/dist/platform/audit-logger.test.d.ts +0 -2
- package/dist/platform/audit-logger.test.d.ts.map +0 -1
- package/dist/platform/escalation.test.d.ts +0 -2
- package/dist/platform/escalation.test.d.ts.map +0 -1
- package/dist/platform/evidence.test.d.ts +0 -2
- package/dist/platform/evidence.test.d.ts.map +0 -1
- package/dist/platform/package-manager/index.test.d.ts +0 -2
- package/dist/platform/package-manager/index.test.d.ts.map +0 -1
- package/dist/platform/package-manager/list-parser.test.d.ts +0 -2
- package/dist/platform/package-manager/list-parser.test.d.ts.map +0 -1
- package/dist/platform/package-manager.test.d.ts +0 -2
- package/dist/platform/package-manager.test.d.ts.map +0 -1
- package/dist/platform/policy.test.d.ts +0 -2
- package/dist/platform/policy.test.d.ts.map +0 -1
- package/dist/platform/repo-lock.test.d.ts +0 -2
- package/dist/platform/repo-lock.test.d.ts.map +0 -1
- package/dist/platform/rollback.test.d.ts +0 -2
- package/dist/platform/rollback.test.d.ts.map +0 -1
- package/dist/platform/safe-exec.test.d.ts +0 -2
- package/dist/platform/safe-exec.test.d.ts.map +0 -1
- package/dist/platform/sla.test.d.ts +0 -2
- package/dist/platform/sla.test.d.ts.map +0 -1
- package/dist/platform/supply-chain.test.d.ts +0 -2
- package/dist/platform/supply-chain.test.d.ts.map +0 -1
- package/dist/platform/workspace.test.d.ts +0 -2
- package/dist/platform/workspace.test.d.ts.map +0 -1
- package/dist/remediation/local/consensus-gate.test.d.ts +0 -2
- package/dist/remediation/local/consensus-gate.test.d.ts.map +0 -1
- package/dist/remediation/local/disposition.test.d.ts +0 -2
- package/dist/remediation/local/disposition.test.d.ts.map +0 -1
- package/dist/remediation/local/run.secops.test.d.ts +0 -2
- package/dist/remediation/local/run.secops.test.d.ts.map +0 -1
- package/dist/remediation/local/sbom.test.d.ts +0 -2
- package/dist/remediation/local/sbom.test.d.ts.map +0 -1
- package/dist/remediation/local/vulnerability-match.test.d.ts +0 -2
- package/dist/remediation/local/vulnerability-match.test.d.ts.map +0 -1
- package/dist/remediation/pipeline.test.d.ts +0 -2
- package/dist/remediation/pipeline.test.d.ts.map +0 -1
- package/dist/remediation/proof-of-fix/runner.test.d.ts +0 -2
- package/dist/remediation/proof-of-fix/runner.test.d.ts.map +0 -1
- package/dist/remediation/refactor/refactor.test.d.ts +0 -2
- package/dist/remediation/refactor/refactor.test.d.ts.map +0 -1
- package/dist/remediation/tools/apply-package-override.test.d.ts +0 -2
- package/dist/remediation/tools/apply-package-override.test.d.ts.map +0 -1
- package/dist/remediation/tools/apply-patch-file.test.d.ts +0 -2
- package/dist/remediation/tools/apply-patch-file.test.d.ts.map +0 -1
- package/dist/remediation/tools/apply-version-bump.test.d.ts +0 -2
- package/dist/remediation/tools/apply-version-bump.test.d.ts.map +0 -1
- package/dist/remediation/tools/check-exploit-signal.test.d.ts +0 -2
- package/dist/remediation/tools/check-exploit-signal.test.d.ts.map +0 -1
- package/dist/remediation/tools/check-reachability.test.d.ts +0 -2
- package/dist/remediation/tools/check-reachability.test.d.ts.map +0 -1
- package/dist/remediation/tools/check-suppression.test.d.ts +0 -2
- package/dist/remediation/tools/check-suppression.test.d.ts.map +0 -1
- package/dist/remediation/tools/generate-patch/helpers.test.d.ts +0 -2
- package/dist/remediation/tools/generate-patch/helpers.test.d.ts.map +0 -1
- package/dist/remediation/tools/lookup-cve.test.d.ts +0 -2
- package/dist/remediation/tools/lookup-cve.test.d.ts.map +0 -1
- package/dist/scanner/adapters/npm-audit.test.d.ts +0 -2
- package/dist/scanner/adapters/npm-audit.test.d.ts.map +0 -1
- package/dist/scanner/adapters/sarif.test.d.ts +0 -2
- package/dist/scanner/adapters/sarif.test.d.ts.map +0 -1
- package/dist/scanner/adapters/yarn-audit.test.d.ts +0 -2
- package/dist/scanner/adapters/yarn-audit.test.d.ts.map +0 -1
- package/dist/scanner/lockfile-diff.test.d.ts +0 -2
- package/dist/scanner/lockfile-diff.test.d.ts.map +0 -1
- package/dist/scanner/parse-input.test.d.ts +0 -2
- package/dist/scanner/parse-input.test.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -827,6 +827,8 @@ function toSarifOutput(report) {
|
|
|
827
827
|
}
|
|
828
828
|
|
|
829
829
|
// src/api/vex/index.ts
|
|
830
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
831
|
+
import { dirname } from "path";
|
|
830
832
|
import { randomUUID } from "crypto";
|
|
831
833
|
function mapReportToVulnerability(report) {
|
|
832
834
|
const hasResolved = report.results.some((r) => r.applied && !r.unresolvedReason);
|
|
@@ -904,22 +906,22 @@ function toCycloneDxVex(report, options = {}) {
|
|
|
904
906
|
}
|
|
905
907
|
|
|
906
908
|
// src/api/patches/inspection.ts
|
|
907
|
-
import { existsSync as
|
|
909
|
+
import { existsSync as existsSync6 } from "fs";
|
|
908
910
|
import { readdir, readFile as readFile2, stat } from "fs/promises";
|
|
909
911
|
import { join as join5 } from "path";
|
|
910
912
|
|
|
911
913
|
// src/remediation/strategies/patch-utils.ts
|
|
912
|
-
import { existsSync as
|
|
914
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync4 } from "fs";
|
|
913
915
|
import { join as join4 } from "path";
|
|
914
916
|
import { execa as execa2 } from "execa";
|
|
915
917
|
|
|
916
918
|
// src/platform/package-manager/index.ts
|
|
917
|
-
import { existsSync as
|
|
919
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
918
920
|
import { join as join3 } from "path";
|
|
919
921
|
import { execa } from "execa";
|
|
920
922
|
|
|
921
923
|
// src/platform/package-manager/list-parser.ts
|
|
922
|
-
import { readFileSync } from "fs";
|
|
924
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
923
925
|
import { join } from "path";
|
|
924
926
|
function parsePackageManagerListOutput(pm, stdout) {
|
|
925
927
|
const versions = /* @__PURE__ */ new Map();
|
|
@@ -992,7 +994,7 @@ function resolveDenoInventory(cwd) {
|
|
|
992
994
|
const versions = /* @__PURE__ */ new Map();
|
|
993
995
|
let raw;
|
|
994
996
|
try {
|
|
995
|
-
raw =
|
|
997
|
+
raw = readFileSync2(join(cwd, "deno.lock"), "utf8");
|
|
996
998
|
} catch {
|
|
997
999
|
return versions;
|
|
998
1000
|
}
|
|
@@ -1032,7 +1034,7 @@ function extractNameVersion(spec, versions) {
|
|
|
1032
1034
|
}
|
|
1033
1035
|
|
|
1034
1036
|
// src/platform/package-manager/driver.ts
|
|
1035
|
-
import { existsSync } from "fs";
|
|
1037
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1036
1038
|
import { join as join2 } from "path";
|
|
1037
1039
|
|
|
1038
1040
|
// src/platform/package-manager/index.ts
|
|
@@ -1046,10 +1048,10 @@ async function getYarnMajorVersion(cwd) {
|
|
|
1046
1048
|
}
|
|
1047
1049
|
}
|
|
1048
1050
|
function detectPackageManager(cwd) {
|
|
1049
|
-
if (
|
|
1050
|
-
if (
|
|
1051
|
-
if (
|
|
1052
|
-
if (
|
|
1051
|
+
if (existsSync3(join3(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
1052
|
+
if (existsSync3(join3(cwd, "yarn.lock"))) return "yarn";
|
|
1053
|
+
if (existsSync3(join3(cwd, "bun.lockb")) || existsSync3(join3(cwd, "bun.lock"))) return "bun";
|
|
1054
|
+
if (existsSync3(join3(cwd, "deno.lock"))) return "deno";
|
|
1053
1055
|
return "npm";
|
|
1054
1056
|
}
|
|
1055
1057
|
function withWorkspace(command, pm, workspace) {
|
|
@@ -1110,10 +1112,10 @@ function resolveTestCommand(pm, constraints) {
|
|
|
1110
1112
|
function resolveTypecheckCommand(pm, constraints, cwd = process.cwd()) {
|
|
1111
1113
|
const pkgJsonPath = join3(cwd, "package.json");
|
|
1112
1114
|
let hasTypecheckScript = false;
|
|
1113
|
-
const hasTsConfig =
|
|
1114
|
-
if (
|
|
1115
|
+
const hasTsConfig = existsSync3(join3(cwd, "tsconfig.json"));
|
|
1116
|
+
if (existsSync3(pkgJsonPath)) {
|
|
1115
1117
|
try {
|
|
1116
|
-
const pkg = JSON.parse(
|
|
1118
|
+
const pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf8"));
|
|
1117
1119
|
if (pkg.scripts?.typecheck) {
|
|
1118
1120
|
hasTypecheckScript = true;
|
|
1119
1121
|
}
|
|
@@ -1248,7 +1250,7 @@ function validatePatchDiff(patchContent) {
|
|
|
1248
1250
|
}
|
|
1249
1251
|
|
|
1250
1252
|
// src/api/patches/helpers.ts
|
|
1251
|
-
import { existsSync as
|
|
1253
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1252
1254
|
import { readFile } from "fs/promises";
|
|
1253
1255
|
import { isAbsolute, resolve, sep } from "path";
|
|
1254
1256
|
var DEFAULT_PATCHES_DIR = "./patches";
|
|
@@ -1267,7 +1269,7 @@ function resolveArtifactPath(cwd, patchFilePath) {
|
|
|
1267
1269
|
return resolved;
|
|
1268
1270
|
}
|
|
1269
1271
|
async function readManifest(manifestFilePath) {
|
|
1270
|
-
if (!
|
|
1272
|
+
if (!existsSync5(manifestFilePath)) {
|
|
1271
1273
|
return void 0;
|
|
1272
1274
|
}
|
|
1273
1275
|
try {
|
|
@@ -1312,7 +1314,7 @@ function toSummary(inspection) {
|
|
|
1312
1314
|
async function listPatchArtifacts(options = {}) {
|
|
1313
1315
|
const cwd = options.cwd ?? process.cwd();
|
|
1314
1316
|
const patchesDirPath = resolvePatchesDir(cwd, options.patchesDir);
|
|
1315
|
-
if (!
|
|
1317
|
+
if (!existsSync6(patchesDirPath)) {
|
|
1316
1318
|
return [];
|
|
1317
1319
|
}
|
|
1318
1320
|
const entries = await readdir(patchesDirPath, { withFileTypes: true });
|
|
@@ -1330,10 +1332,10 @@ async function inspectPatchArtifact(patchFilePath, options = {}) {
|
|
|
1330
1332
|
const resolvedPatchPath = resolveArtifactPath(cwd, patchFilePath);
|
|
1331
1333
|
const patchFileName = resolvedPatchPath.split("/").pop() ?? resolvedPatchPath;
|
|
1332
1334
|
const manifestFilePath = `${resolvedPatchPath}.json`;
|
|
1333
|
-
if (!
|
|
1335
|
+
if (!existsSync6(resolvedPatchPath)) {
|
|
1334
1336
|
return {
|
|
1335
1337
|
patchFilePath: resolvedPatchPath,
|
|
1336
|
-
manifestFilePath:
|
|
1338
|
+
manifestFilePath: existsSync6(manifestFilePath) ? manifestFilePath : void 0,
|
|
1337
1339
|
patchFileName,
|
|
1338
1340
|
exists: false,
|
|
1339
1341
|
diffValid: false,
|
|
@@ -1377,14 +1379,14 @@ function defineTool(config) {
|
|
|
1377
1379
|
|
|
1378
1380
|
// src/remediation/tools/check-inventory.ts
|
|
1379
1381
|
import { z } from "zod";
|
|
1380
|
-
import { readFileSync as
|
|
1382
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
1381
1383
|
import { join as join8 } from "path";
|
|
1382
1384
|
import { execa as execa3 } from "execa";
|
|
1383
1385
|
|
|
1384
1386
|
// src/platform/policy.ts
|
|
1385
|
-
import { existsSync as
|
|
1386
|
-
import { join as join6 } from "path";
|
|
1387
|
-
import { parse as yamlParse } from "yaml";
|
|
1387
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
1388
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
1389
|
+
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
|
|
1388
1390
|
var DEFAULT_POLICY = {
|
|
1389
1391
|
allowMajorBumps: false,
|
|
1390
1392
|
denyPackages: [],
|
|
@@ -1414,14 +1416,28 @@ var DEFAULT_POLICY = {
|
|
|
1414
1416
|
escalationGraph: void 0
|
|
1415
1417
|
};
|
|
1416
1418
|
function loadPolicy(cwd, explicitPath) {
|
|
1417
|
-
|
|
1418
|
-
if (
|
|
1419
|
+
let content;
|
|
1420
|
+
if (explicitPath && (explicitPath.trim().startsWith("{") || explicitPath.trim().startsWith("---"))) {
|
|
1421
|
+
content = explicitPath;
|
|
1422
|
+
} else {
|
|
1423
|
+
const candidate = explicitPath ?? join6(cwd, ".github", "autoremediator.yml");
|
|
1424
|
+
if (!existsSync7(candidate)) return DEFAULT_POLICY;
|
|
1425
|
+
try {
|
|
1426
|
+
content = readFileSync5(candidate, "utf8");
|
|
1427
|
+
} catch {
|
|
1428
|
+
return DEFAULT_POLICY;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1419
1431
|
try {
|
|
1420
|
-
const parsed = yamlParse(
|
|
1432
|
+
const parsed = yamlParse(content);
|
|
1421
1433
|
return {
|
|
1422
1434
|
allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
|
|
1423
|
-
denyPackages: parsed.denyPackages
|
|
1424
|
-
|
|
1435
|
+
denyPackages: Array.isArray(parsed.denyPackages) ? parsed.denyPackages.filter(
|
|
1436
|
+
(p) => typeof p === "string" && p.trim().length > 0
|
|
1437
|
+
) : DEFAULT_POLICY.denyPackages,
|
|
1438
|
+
allowPackages: Array.isArray(parsed.allowPackages) ? parsed.allowPackages.filter(
|
|
1439
|
+
(p) => typeof p === "string" && p.trim().length > 0
|
|
1440
|
+
) : DEFAULT_POLICY.allowPackages,
|
|
1425
1441
|
constraints: {
|
|
1426
1442
|
directDependenciesOnly: parsed.constraints?.directDependenciesOnly ?? DEFAULT_POLICY.constraints?.directDependenciesOnly ?? false,
|
|
1427
1443
|
preferVersionBump: parsed.constraints?.preferVersionBump ?? DEFAULT_POLICY.constraints?.preferVersionBump ?? false,
|
|
@@ -1465,20 +1481,49 @@ function loadPolicy(cwd, explicitPath) {
|
|
|
1465
1481
|
return DEFAULT_POLICY;
|
|
1466
1482
|
}
|
|
1467
1483
|
}
|
|
1484
|
+
function isPackageInList(list, packageName) {
|
|
1485
|
+
if (!list || !Array.isArray(list) || typeof packageName !== "string" || !packageName.trim()) {
|
|
1486
|
+
return false;
|
|
1487
|
+
}
|
|
1488
|
+
const target = packageName.trim();
|
|
1489
|
+
return list.some((pattern) => {
|
|
1490
|
+
if (typeof pattern !== "string") return false;
|
|
1491
|
+
const cleanPattern = pattern.trim();
|
|
1492
|
+
if (!cleanPattern) return false;
|
|
1493
|
+
if (cleanPattern === target) return true;
|
|
1494
|
+
if (cleanPattern === "*") return true;
|
|
1495
|
+
if (cleanPattern.endsWith("/*")) {
|
|
1496
|
+
const prefix = cleanPattern.slice(0, -1);
|
|
1497
|
+
return target.startsWith(prefix);
|
|
1498
|
+
}
|
|
1499
|
+
if (cleanPattern.endsWith("*")) {
|
|
1500
|
+
const prefix = cleanPattern.slice(0, -1);
|
|
1501
|
+
return target.startsWith(prefix);
|
|
1502
|
+
}
|
|
1503
|
+
return false;
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1468
1506
|
function isPackageAllowed(policy, packageName) {
|
|
1469
|
-
if (policy
|
|
1470
|
-
|
|
1507
|
+
if (!policy || typeof policy !== "object" || typeof packageName !== "string") {
|
|
1508
|
+
return true;
|
|
1509
|
+
}
|
|
1510
|
+
if (isPackageInList(policy.denyPackages, packageName)) return false;
|
|
1511
|
+
const allowList = Array.isArray(policy.allowPackages) ? policy.allowPackages.filter((p) => typeof p === "string" && p.trim().length > 0) : [];
|
|
1512
|
+
if (allowList.length > 0 && !isPackageInList(allowList, packageName)) {
|
|
1471
1513
|
return false;
|
|
1472
1514
|
}
|
|
1473
1515
|
return true;
|
|
1474
1516
|
}
|
|
1475
1517
|
function isActiveSuppression(suppression) {
|
|
1518
|
+
if (!suppression || typeof suppression !== "object") return false;
|
|
1476
1519
|
if (!suppression.expiresAt) return true;
|
|
1477
|
-
|
|
1520
|
+
const expires = new Date(suppression.expiresAt).getTime();
|
|
1521
|
+
if (isNaN(expires)) return true;
|
|
1522
|
+
return expires > Date.now();
|
|
1478
1523
|
}
|
|
1479
1524
|
function loadSuppressionsFile(filePath) {
|
|
1480
1525
|
try {
|
|
1481
|
-
const content =
|
|
1526
|
+
const content = readFileSync5(filePath, "utf8");
|
|
1482
1527
|
const parsed = yamlParse(content);
|
|
1483
1528
|
return Array.isArray(parsed?.suppressions) ? parsed.suppressions : [];
|
|
1484
1529
|
} catch {
|
|
@@ -1486,6 +1531,9 @@ function loadSuppressionsFile(filePath) {
|
|
|
1486
1531
|
}
|
|
1487
1532
|
}
|
|
1488
1533
|
function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
|
|
1534
|
+
if (typeof severity !== "string" || !slaPolicy || typeof slaPolicy !== "object") {
|
|
1535
|
+
return null;
|
|
1536
|
+
}
|
|
1489
1537
|
const severityKey = severity.toLowerCase();
|
|
1490
1538
|
const deadlineHours = slaPolicy[severityKey];
|
|
1491
1539
|
if (typeof deadlineHours !== "number") return null;
|
|
@@ -1504,18 +1552,21 @@ function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
|
|
|
1504
1552
|
};
|
|
1505
1553
|
}
|
|
1506
1554
|
|
|
1555
|
+
// src/remediation/tools/check-inventory.ts
|
|
1556
|
+
import semver from "semver";
|
|
1557
|
+
|
|
1507
1558
|
// src/platform/workspace.ts
|
|
1508
|
-
import { existsSync as
|
|
1559
|
+
import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync6, statSync } from "fs";
|
|
1509
1560
|
import { join as join7, resolve as resolve2, relative } from "path";
|
|
1510
1561
|
import yaml from "yaml";
|
|
1511
1562
|
function findWorkspaceRoot(startDir) {
|
|
1512
1563
|
let curr = resolve2(startDir);
|
|
1513
1564
|
while (true) {
|
|
1514
|
-
if (
|
|
1565
|
+
if (existsSync8(join7(curr, "pnpm-workspace.yaml"))) return curr;
|
|
1515
1566
|
const pkgPath = join7(curr, "package.json");
|
|
1516
|
-
if (
|
|
1567
|
+
if (existsSync8(pkgPath)) {
|
|
1517
1568
|
try {
|
|
1518
|
-
const parsed = JSON.parse(
|
|
1569
|
+
const parsed = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
1519
1570
|
if (parsed.workspaces) return curr;
|
|
1520
1571
|
} catch {
|
|
1521
1572
|
}
|
|
@@ -1531,9 +1582,9 @@ function findWorkspacePackages(cwd) {
|
|
|
1531
1582
|
const packages = [];
|
|
1532
1583
|
const packageGlobs = [];
|
|
1533
1584
|
const pnpmWorkspaceFile = join7(rootCwd, "pnpm-workspace.yaml");
|
|
1534
|
-
if (
|
|
1585
|
+
if (existsSync8(pnpmWorkspaceFile)) {
|
|
1535
1586
|
try {
|
|
1536
|
-
const content =
|
|
1587
|
+
const content = readFileSync6(pnpmWorkspaceFile, "utf8");
|
|
1537
1588
|
const parsed = yaml.parse(content);
|
|
1538
1589
|
if (Array.isArray(parsed?.packages)) {
|
|
1539
1590
|
packageGlobs.push(...parsed.packages);
|
|
@@ -1542,9 +1593,9 @@ function findWorkspacePackages(cwd) {
|
|
|
1542
1593
|
}
|
|
1543
1594
|
}
|
|
1544
1595
|
const rootPkgFile = join7(rootCwd, "package.json");
|
|
1545
|
-
if (
|
|
1596
|
+
if (existsSync8(rootPkgFile)) {
|
|
1546
1597
|
try {
|
|
1547
|
-
const rootPkg = JSON.parse(
|
|
1598
|
+
const rootPkg = JSON.parse(readFileSync6(rootPkgFile, "utf8"));
|
|
1548
1599
|
if (Array.isArray(rootPkg.workspaces)) {
|
|
1549
1600
|
packageGlobs.push(...rootPkg.workspaces);
|
|
1550
1601
|
} else if (Array.isArray(rootPkg.workspaces?.packages)) {
|
|
@@ -1554,9 +1605,9 @@ function findWorkspacePackages(cwd) {
|
|
|
1554
1605
|
}
|
|
1555
1606
|
}
|
|
1556
1607
|
const denoJsonFile = join7(rootCwd, "deno.json");
|
|
1557
|
-
if (
|
|
1608
|
+
if (existsSync8(denoJsonFile)) {
|
|
1558
1609
|
try {
|
|
1559
|
-
const denoJson = JSON.parse(
|
|
1610
|
+
const denoJson = JSON.parse(readFileSync6(denoJsonFile, "utf8"));
|
|
1560
1611
|
if (Array.isArray(denoJson.workspace)) {
|
|
1561
1612
|
packageGlobs.push(...denoJson.workspace);
|
|
1562
1613
|
}
|
|
@@ -1572,7 +1623,7 @@ function findWorkspacePackages(cwd) {
|
|
|
1572
1623
|
if (!cleanGlob || cleanGlob.startsWith("!")) continue;
|
|
1573
1624
|
if (cleanGlob.endsWith("/*")) {
|
|
1574
1625
|
const parentDir = join7(rootCwd, cleanGlob.slice(0, -2));
|
|
1575
|
-
if (!
|
|
1626
|
+
if (!existsSync8(parentDir)) continue;
|
|
1576
1627
|
try {
|
|
1577
1628
|
const entries = readdirSync(parentDir);
|
|
1578
1629
|
for (const entry of entries) {
|
|
@@ -1589,7 +1640,7 @@ function findWorkspacePackages(cwd) {
|
|
|
1589
1640
|
}
|
|
1590
1641
|
} else {
|
|
1591
1642
|
const directDir = join7(rootCwd, cleanGlob);
|
|
1592
|
-
if (
|
|
1643
|
+
if (existsSync8(directDir)) {
|
|
1593
1644
|
try {
|
|
1594
1645
|
if (statSync(directDir).isDirectory()) {
|
|
1595
1646
|
addWorkspacePackage(rootCwd, directDir, packages, seenDirs);
|
|
@@ -1606,9 +1657,9 @@ function addWorkspacePackage(rootCwd, dir, out, seen) {
|
|
|
1606
1657
|
if (seen.has(norm)) return;
|
|
1607
1658
|
seen.add(norm);
|
|
1608
1659
|
const manifestPath = join7(dir, "package.json");
|
|
1609
|
-
if (!
|
|
1660
|
+
if (!existsSync8(manifestPath)) return;
|
|
1610
1661
|
try {
|
|
1611
|
-
const content =
|
|
1662
|
+
const content = readFileSync6(manifestPath, "utf8");
|
|
1612
1663
|
const manifest = JSON.parse(content);
|
|
1613
1664
|
out.push({
|
|
1614
1665
|
name: manifest.name ?? relative(rootCwd, dir),
|
|
@@ -1639,6 +1690,37 @@ function findDeclaringWorkspacePackage(cwd, dependencyName, filterWorkspace) {
|
|
|
1639
1690
|
}
|
|
1640
1691
|
|
|
1641
1692
|
// src/remediation/tools/check-inventory.ts
|
|
1693
|
+
function resolveInventoryVersion(rawVersion) {
|
|
1694
|
+
const trimmed = rawVersion.trim();
|
|
1695
|
+
if (semver.valid(trimmed)) {
|
|
1696
|
+
return semver.clean(trimmed) ?? trimmed;
|
|
1697
|
+
}
|
|
1698
|
+
let spec = trimmed;
|
|
1699
|
+
if (spec.startsWith("npm:") && spec.includes("@")) {
|
|
1700
|
+
spec = spec.slice(spec.lastIndexOf("@") + 1);
|
|
1701
|
+
}
|
|
1702
|
+
if (spec.startsWith("workspace:")) {
|
|
1703
|
+
spec = spec.slice("workspace:".length).trim();
|
|
1704
|
+
}
|
|
1705
|
+
if (semver.valid(spec)) {
|
|
1706
|
+
return semver.clean(spec) ?? spec;
|
|
1707
|
+
}
|
|
1708
|
+
try {
|
|
1709
|
+
const min = semver.minVersion(spec);
|
|
1710
|
+
if (min?.version && semver.valid(min.version)) {
|
|
1711
|
+
return min.version;
|
|
1712
|
+
}
|
|
1713
|
+
} catch {
|
|
1714
|
+
}
|
|
1715
|
+
try {
|
|
1716
|
+
const coerced = semver.coerce(spec);
|
|
1717
|
+
if (coerced?.version && semver.valid(coerced.version)) {
|
|
1718
|
+
return coerced.version;
|
|
1719
|
+
}
|
|
1720
|
+
} catch {
|
|
1721
|
+
}
|
|
1722
|
+
return semver.clean(trimmed) ?? trimmed;
|
|
1723
|
+
}
|
|
1642
1724
|
var checkInventoryTool = defineTool({
|
|
1643
1725
|
description: "Read the project's package.json and installed dependencies to list packages and exact versions. Must be called before checking version matches.",
|
|
1644
1726
|
parameters: z.object({
|
|
@@ -1655,7 +1737,7 @@ var checkInventoryTool = defineTool({
|
|
|
1655
1737
|
}) => {
|
|
1656
1738
|
let pkgJson;
|
|
1657
1739
|
try {
|
|
1658
|
-
pkgJson = JSON.parse(
|
|
1740
|
+
pkgJson = JSON.parse(readFileSync7(join8(cwd, "package.json"), "utf8"));
|
|
1659
1741
|
} catch {
|
|
1660
1742
|
return {
|
|
1661
1743
|
packages: [],
|
|
@@ -1698,11 +1780,12 @@ var checkInventoryTool = defineTool({
|
|
|
1698
1780
|
if (packages.length === 0) {
|
|
1699
1781
|
const allDeps = {
|
|
1700
1782
|
...pkgJson.dependencies,
|
|
1701
|
-
...pkgJson.devDependencies
|
|
1783
|
+
...pkgJson.devDependencies,
|
|
1784
|
+
...pkgJson.peerDependencies
|
|
1702
1785
|
};
|
|
1703
|
-
for (const [name,
|
|
1704
|
-
const
|
|
1705
|
-
packages.push({ name, version
|
|
1786
|
+
for (const [name, rawVersion] of Object.entries(allDeps)) {
|
|
1787
|
+
const version = resolveInventoryVersion(rawVersion);
|
|
1788
|
+
packages.push({ name, version, type: "direct" });
|
|
1706
1789
|
}
|
|
1707
1790
|
}
|
|
1708
1791
|
return { packages };
|
|
@@ -1812,13 +1895,12 @@ async function validatePatchArtifact(patchFilePath, options = {}) {
|
|
|
1812
1895
|
}
|
|
1813
1896
|
|
|
1814
1897
|
// src/api/patches/reconcile.ts
|
|
1815
|
-
import { existsSync as
|
|
1898
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1816
1899
|
import { readFile as readFile3, rm, writeFile } from "fs/promises";
|
|
1817
1900
|
import { join as join10 } from "path";
|
|
1818
|
-
import { execa as execa5 } from "execa";
|
|
1819
1901
|
|
|
1820
1902
|
// src/remediation/evaluate-package.ts
|
|
1821
|
-
import
|
|
1903
|
+
import semver3 from "semver";
|
|
1822
1904
|
|
|
1823
1905
|
// src/platform/http-client.ts
|
|
1824
1906
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
@@ -2179,10 +2261,10 @@ async function enrichWithCisaKev(details) {
|
|
|
2179
2261
|
}
|
|
2180
2262
|
|
|
2181
2263
|
// src/intelligence/sources/registry.ts
|
|
2182
|
-
import { readFileSync as
|
|
2264
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
2183
2265
|
import { join as join9 } from "path";
|
|
2184
2266
|
import { execa as execa4 } from "execa";
|
|
2185
|
-
import
|
|
2267
|
+
import semver2 from "semver";
|
|
2186
2268
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
2187
2269
|
async function fetchPackageVersions(packageName) {
|
|
2188
2270
|
const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
|
|
@@ -2218,15 +2300,15 @@ async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPat
|
|
|
2218
2300
|
majorOnlyFixAvailable: false
|
|
2219
2301
|
};
|
|
2220
2302
|
}
|
|
2221
|
-
const installed =
|
|
2222
|
-
const candidates = versions.filter((v) =>
|
|
2303
|
+
const installed = semver2.parse(installedVersion);
|
|
2304
|
+
const candidates = versions.filter((v) => semver2.valid(v) && semver2.gte(v, firstPatchedVersion)).filter((v) => {
|
|
2223
2305
|
if (!vulnerableRange) return true;
|
|
2224
2306
|
try {
|
|
2225
|
-
return !
|
|
2307
|
+
return !semver2.satisfies(v, vulnerableRange, { includePrerelease: false });
|
|
2226
2308
|
} catch {
|
|
2227
2309
|
return true;
|
|
2228
2310
|
}
|
|
2229
|
-
}).sort(
|
|
2311
|
+
}).sort(semver2.compare);
|
|
2230
2312
|
if (!candidates.length) {
|
|
2231
2313
|
return {
|
|
2232
2314
|
candidates: {},
|
|
@@ -2266,13 +2348,14 @@ async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPat
|
|
|
2266
2348
|
};
|
|
2267
2349
|
}
|
|
2268
2350
|
function classifyUpgradeLevel(installedVersion, candidateVersion) {
|
|
2269
|
-
const installed =
|
|
2270
|
-
const candidate =
|
|
2351
|
+
const installed = semver2.parse(installedVersion) ?? semver2.coerce(installedVersion);
|
|
2352
|
+
const candidate = semver2.parse(candidateVersion) ?? semver2.coerce(candidateVersion);
|
|
2271
2353
|
if (!installed || !candidate) return void 0;
|
|
2354
|
+
if (!semver2.gte(candidate.version, installed.version)) return void 0;
|
|
2272
2355
|
if (candidate.major > installed.major) return "major";
|
|
2273
|
-
if (candidate.minor > installed.minor) return "minor";
|
|
2274
|
-
if (candidate.
|
|
2275
|
-
return "patch";
|
|
2356
|
+
if (candidate.major === installed.major && candidate.minor > installed.minor) return "minor";
|
|
2357
|
+
if (candidate.major === installed.major && candidate.minor === installed.minor) {
|
|
2358
|
+
if (candidate.patch >= installed.patch) return "patch";
|
|
2276
2359
|
}
|
|
2277
2360
|
return void 0;
|
|
2278
2361
|
}
|
|
@@ -2356,7 +2439,7 @@ async function queryOutdatedPackages(cwd, options = {}) {
|
|
|
2356
2439
|
}
|
|
2357
2440
|
let directDeps;
|
|
2358
2441
|
try {
|
|
2359
|
-
const pkgRaw = JSON.parse(
|
|
2442
|
+
const pkgRaw = JSON.parse(readFileSync8(join9(cwd, "package.json"), "utf8"));
|
|
2360
2443
|
directDeps = /* @__PURE__ */ new Set([
|
|
2361
2444
|
...Object.keys(pkgRaw.dependencies ?? {}),
|
|
2362
2445
|
...Object.keys(pkgRaw.devDependencies ?? {})
|
|
@@ -2372,7 +2455,7 @@ async function queryOutdatedPackages(cwd, options = {}) {
|
|
|
2372
2455
|
if (!currentVersion || !latestVersion) continue;
|
|
2373
2456
|
const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
|
|
2374
2457
|
if (!options.includeTransitive && dependencyScope === "transitive") continue;
|
|
2375
|
-
const isMajorBump =
|
|
2458
|
+
const isMajorBump = semver2.valid(currentVersion) !== null && semver2.valid(latestVersion) !== null && semver2.major(latestVersion) > semver2.major(currentVersion);
|
|
2376
2459
|
result.set(name, {
|
|
2377
2460
|
currentVersion,
|
|
2378
2461
|
wantedVersion,
|
|
@@ -2393,7 +2476,7 @@ async function evaluatePackage(packageName, options = {}) {
|
|
|
2393
2476
|
try {
|
|
2394
2477
|
versions = await fetchPackageVersions(packageName);
|
|
2395
2478
|
if (versions.length > 0) {
|
|
2396
|
-
const valid = versions.filter((v) =>
|
|
2479
|
+
const valid = versions.filter((v) => semver3.valid(v)).sort((a, b) => semver3.compare(a, b));
|
|
2397
2480
|
latestVersion = valid[valid.length - 1];
|
|
2398
2481
|
}
|
|
2399
2482
|
} catch {
|
|
@@ -2419,8 +2502,8 @@ async function evaluatePackage(packageName, options = {}) {
|
|
|
2419
2502
|
(p) => p.name.toLowerCase() === packageName.toLowerCase()
|
|
2420
2503
|
);
|
|
2421
2504
|
if (version && affected?.vulnerableRange) {
|
|
2422
|
-
const cleanVer =
|
|
2423
|
-
if (
|
|
2505
|
+
const cleanVer = semver3.clean(version) || version;
|
|
2506
|
+
if (semver3.valid(cleanVer) && !semver3.satisfies(cleanVer, affected.vulnerableRange)) {
|
|
2424
2507
|
continue;
|
|
2425
2508
|
}
|
|
2426
2509
|
}
|
|
@@ -2483,7 +2566,7 @@ async function evaluatePackage(packageName, options = {}) {
|
|
|
2483
2566
|
}
|
|
2484
2567
|
let recommendedVersion;
|
|
2485
2568
|
if (isVulnerable) {
|
|
2486
|
-
const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v &&
|
|
2569
|
+
const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver3.valid(v))).sort((a, b) => semver3.compare(a, b));
|
|
2487
2570
|
recommendedVersion = upgradeCandidates[upgradeCandidates.length - 1] || latestVersion;
|
|
2488
2571
|
} else {
|
|
2489
2572
|
recommendedVersion = version || latestVersion;
|
|
@@ -2503,6 +2586,169 @@ async function evaluatePackage(packageName, options = {}) {
|
|
|
2503
2586
|
};
|
|
2504
2587
|
}
|
|
2505
2588
|
|
|
2589
|
+
// src/platform/safe-exec.ts
|
|
2590
|
+
import { execa as execa5 } from "execa";
|
|
2591
|
+
var DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([
|
|
2592
|
+
"NODE_OPTIONS",
|
|
2593
|
+
"NODE_PATH",
|
|
2594
|
+
"LD_PRELOAD",
|
|
2595
|
+
"LD_LIBRARY_PATH",
|
|
2596
|
+
"DYLD_INSERT_LIBRARIES",
|
|
2597
|
+
"DYLD_LIBRARY_PATH",
|
|
2598
|
+
"PERL5OPT",
|
|
2599
|
+
"PERL5LIB",
|
|
2600
|
+
"PYTHONWARNINGS",
|
|
2601
|
+
"PYTHONPATH",
|
|
2602
|
+
"RUBYOPT",
|
|
2603
|
+
"RUBYLIB",
|
|
2604
|
+
"BASH_ENV",
|
|
2605
|
+
"ENV"
|
|
2606
|
+
]);
|
|
2607
|
+
function sanitizeEnvironment(env = process.env, allowNodeOptions = false) {
|
|
2608
|
+
const sanitized = {};
|
|
2609
|
+
for (const [key, value] of Object.entries(env)) {
|
|
2610
|
+
if (value === void 0) continue;
|
|
2611
|
+
if (key.includes("\0") || typeof value === "string" && value.includes("\0")) continue;
|
|
2612
|
+
if (DANGEROUS_ENV_VARS.has(key)) {
|
|
2613
|
+
if (key === "NODE_OPTIONS" && allowNodeOptions) {
|
|
2614
|
+
sanitized[key] = value;
|
|
2615
|
+
}
|
|
2616
|
+
continue;
|
|
2617
|
+
}
|
|
2618
|
+
sanitized[key] = value;
|
|
2619
|
+
}
|
|
2620
|
+
return sanitized;
|
|
2621
|
+
}
|
|
2622
|
+
function validateCommandArguments(file, args = []) {
|
|
2623
|
+
if (file.includes("\0")) {
|
|
2624
|
+
throw new Error("Command execution blocked: executable path contains null bytes");
|
|
2625
|
+
}
|
|
2626
|
+
for (const arg of args) {
|
|
2627
|
+
if (arg.includes("\0")) {
|
|
2628
|
+
throw new Error(`Command execution blocked: argument contains null bytes: ${arg}`);
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
function safeExeca(file, args = [], options = {}) {
|
|
2633
|
+
validateCommandArguments(file, args);
|
|
2634
|
+
const timeout = options.timeoutMs ?? (typeof options.timeout === "number" ? options.timeout : 6e4);
|
|
2635
|
+
const sanitizedEnv = sanitizeEnvironment(
|
|
2636
|
+
options.env ?? process.env,
|
|
2637
|
+
options.allowNodeOptions
|
|
2638
|
+
);
|
|
2639
|
+
return execa5(file, args, {
|
|
2640
|
+
...options,
|
|
2641
|
+
timeout,
|
|
2642
|
+
env: sanitizedEnv
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
// src/remediation/tools/apply-package-override/helpers.ts
|
|
2647
|
+
async function collectDependencyTrace(cwd, pm, packageName, constraints) {
|
|
2648
|
+
try {
|
|
2649
|
+
const whyCommand = resolveWhyCommand(pm, packageName, constraints);
|
|
2650
|
+
if (whyCommand.length === 0) return void 0;
|
|
2651
|
+
const [whyCmd, ...whyArgs] = whyCommand;
|
|
2652
|
+
const result = await safeExeca(whyCmd, whyArgs, {
|
|
2653
|
+
cwd,
|
|
2654
|
+
stdio: "pipe",
|
|
2655
|
+
reject: false
|
|
2656
|
+
});
|
|
2657
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
2658
|
+
if (output.length === 0) return void 0;
|
|
2659
|
+
return output.slice(0, 3).join(" | ");
|
|
2660
|
+
} catch {
|
|
2661
|
+
return void 0;
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
function describeOverrideField(packageManager) {
|
|
2665
|
+
if (packageManager === "npm" || packageManager === "bun") return "overrides";
|
|
2666
|
+
if (packageManager === "pnpm") return "pnpm.overrides";
|
|
2667
|
+
if (packageManager === "deno") return "overrides";
|
|
2668
|
+
return "resolutions";
|
|
2669
|
+
}
|
|
2670
|
+
function getOverrideValue(pkgJson, packageManager, packageName) {
|
|
2671
|
+
if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno") {
|
|
2672
|
+
return pkgJson.overrides?.[packageName];
|
|
2673
|
+
}
|
|
2674
|
+
if (packageManager === "pnpm") return pkgJson.pnpm?.overrides?.[packageName];
|
|
2675
|
+
return pkgJson.resolutions?.[packageName];
|
|
2676
|
+
}
|
|
2677
|
+
function setOverrideValue(pkgJson, packageManager, packageName, version) {
|
|
2678
|
+
if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno") {
|
|
2679
|
+
pkgJson.overrides = { ...pkgJson.overrides, [packageName]: version };
|
|
2680
|
+
return;
|
|
2681
|
+
}
|
|
2682
|
+
if (packageManager === "pnpm") {
|
|
2683
|
+
pkgJson.pnpm = {
|
|
2684
|
+
...pkgJson.pnpm,
|
|
2685
|
+
overrides: {
|
|
2686
|
+
...pkgJson.pnpm?.overrides,
|
|
2687
|
+
[packageName]: version
|
|
2688
|
+
}
|
|
2689
|
+
};
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
pkgJson.resolutions = { ...pkgJson.resolutions, [packageName]: version };
|
|
2693
|
+
}
|
|
2694
|
+
function restoreOverrideValue(pkgJson, packageManager, packageName, previousValue) {
|
|
2695
|
+
if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno") {
|
|
2696
|
+
pkgJson.overrides = restoreRecord(pkgJson.overrides, packageName, previousValue);
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
if (packageManager === "pnpm") {
|
|
2700
|
+
pkgJson.pnpm = {
|
|
2701
|
+
...pkgJson.pnpm,
|
|
2702
|
+
overrides: restoreRecord(pkgJson.pnpm?.overrides, packageName, previousValue)
|
|
2703
|
+
};
|
|
2704
|
+
if (!pkgJson.pnpm.overrides) {
|
|
2705
|
+
delete pkgJson.pnpm.overrides;
|
|
2706
|
+
}
|
|
2707
|
+
if (Object.keys(pkgJson.pnpm).length === 0) {
|
|
2708
|
+
delete pkgJson.pnpm;
|
|
2709
|
+
}
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
pkgJson.resolutions = restoreRecord(pkgJson.resolutions, packageName, previousValue);
|
|
2713
|
+
}
|
|
2714
|
+
function getDenoJsonImportValue(denoJson, packageName) {
|
|
2715
|
+
const imports = denoJson.imports ?? {};
|
|
2716
|
+
if (imports[packageName] !== void 0) {
|
|
2717
|
+
return { key: packageName, value: imports[packageName] };
|
|
2718
|
+
}
|
|
2719
|
+
const npmKey = `npm:${packageName}`;
|
|
2720
|
+
if (imports[npmKey] !== void 0) {
|
|
2721
|
+
return { key: npmKey, value: imports[npmKey] };
|
|
2722
|
+
}
|
|
2723
|
+
return void 0;
|
|
2724
|
+
}
|
|
2725
|
+
function setDenoJsonImportValue(denoJson, packageName, version) {
|
|
2726
|
+
const existing = getDenoJsonImportValue(denoJson, packageName);
|
|
2727
|
+
const key = existing?.key ?? `npm:${packageName}`;
|
|
2728
|
+
denoJson.imports = { ...denoJson.imports, [key]: `npm:${packageName}@${version}` };
|
|
2729
|
+
}
|
|
2730
|
+
function restoreDenoJsonImportValue(denoJson, packageName, previousEntry) {
|
|
2731
|
+
const existing = getDenoJsonImportValue(denoJson, packageName);
|
|
2732
|
+
if (!existing) return;
|
|
2733
|
+
const imports = { ...denoJson.imports };
|
|
2734
|
+
if (previousEntry === void 0) {
|
|
2735
|
+
delete imports[existing.key];
|
|
2736
|
+
} else {
|
|
2737
|
+
delete imports[existing.key];
|
|
2738
|
+
imports[previousEntry.key] = previousEntry.value;
|
|
2739
|
+
}
|
|
2740
|
+
denoJson.imports = Object.keys(imports).length > 0 ? imports : void 0;
|
|
2741
|
+
}
|
|
2742
|
+
function restoreRecord(record, key, previousValue) {
|
|
2743
|
+
const nextRecord = { ...record };
|
|
2744
|
+
if (previousValue === void 0) {
|
|
2745
|
+
delete nextRecord[key];
|
|
2746
|
+
} else {
|
|
2747
|
+
nextRecord[key] = previousValue;
|
|
2748
|
+
}
|
|
2749
|
+
return Object.keys(nextRecord).length > 0 ? nextRecord : void 0;
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2506
2752
|
// src/api/patches/reconcile.ts
|
|
2507
2753
|
async function reconcilePatchArtifacts(options = {}) {
|
|
2508
2754
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -2556,7 +2802,8 @@ async function reconcilePatchArtifacts(options = {}) {
|
|
|
2556
2802
|
(v) => v.safeUpgradeVersion
|
|
2557
2803
|
)?.safeUpgradeVersion;
|
|
2558
2804
|
}
|
|
2559
|
-
} catch {
|
|
2805
|
+
} catch (evalError) {
|
|
2806
|
+
const _ignored = evalError;
|
|
2560
2807
|
}
|
|
2561
2808
|
if (!safeUpgradeVersion) {
|
|
2562
2809
|
results.push({
|
|
@@ -2581,10 +2828,23 @@ async function reconcilePatchArtifacts(options = {}) {
|
|
|
2581
2828
|
});
|
|
2582
2829
|
continue;
|
|
2583
2830
|
}
|
|
2831
|
+
const pkgJsonPath = join10(cwd, "package.json");
|
|
2832
|
+
const manifestPath = inspection.manifestFilePath ?? `${summary.patchFilePath}.json`;
|
|
2833
|
+
let originalPkgJsonContent;
|
|
2834
|
+
let originalPatchContent;
|
|
2835
|
+
let originalManifestContent;
|
|
2584
2836
|
try {
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2837
|
+
if (existsSync9(pkgJsonPath)) {
|
|
2838
|
+
originalPkgJsonContent = await readFile3(pkgJsonPath, "utf8");
|
|
2839
|
+
}
|
|
2840
|
+
if (existsSync9(summary.patchFilePath)) {
|
|
2841
|
+
originalPatchContent = await readFile3(summary.patchFilePath, "utf8");
|
|
2842
|
+
}
|
|
2843
|
+
if (existsSync9(manifestPath)) {
|
|
2844
|
+
originalManifestContent = await readFile3(manifestPath, "utf8");
|
|
2845
|
+
}
|
|
2846
|
+
if (originalPkgJsonContent) {
|
|
2847
|
+
const pkgJson = JSON.parse(originalPkgJsonContent);
|
|
2588
2848
|
let updated = false;
|
|
2589
2849
|
const depSections = [
|
|
2590
2850
|
"dependencies",
|
|
@@ -2592,16 +2852,24 @@ async function reconcilePatchArtifacts(options = {}) {
|
|
|
2592
2852
|
"peerDependencies",
|
|
2593
2853
|
"optionalDependencies"
|
|
2594
2854
|
];
|
|
2855
|
+
let foundInDirectDeps = false;
|
|
2595
2856
|
for (const depSection of depSections) {
|
|
2596
2857
|
if (pkgJson[depSection] && pkgJson[depSection][packageName]) {
|
|
2597
2858
|
pkgJson[depSection][packageName] = `^${safeUpgradeVersion}`;
|
|
2598
2859
|
updated = true;
|
|
2860
|
+
foundInDirectDeps = true;
|
|
2599
2861
|
}
|
|
2600
2862
|
}
|
|
2863
|
+
const existingOverride = getOverrideValue(pkgJson, pm, packageName);
|
|
2864
|
+
if (existingOverride || !foundInDirectDeps) {
|
|
2865
|
+
setOverrideValue(pkgJson, pm, packageName, safeUpgradeVersion);
|
|
2866
|
+
updated = true;
|
|
2867
|
+
}
|
|
2601
2868
|
if (pkgJson.pnpm?.patchedDependencies) {
|
|
2602
|
-
|
|
2869
|
+
const patched = pkgJson.pnpm.patchedDependencies;
|
|
2870
|
+
for (const key of Object.keys(patched)) {
|
|
2603
2871
|
if (key.startsWith(`${packageName}@`)) {
|
|
2604
|
-
delete
|
|
2872
|
+
delete patched[key];
|
|
2605
2873
|
updated = true;
|
|
2606
2874
|
}
|
|
2607
2875
|
}
|
|
@@ -2611,14 +2879,60 @@ async function reconcilePatchArtifacts(options = {}) {
|
|
|
2611
2879
|
}
|
|
2612
2880
|
}
|
|
2613
2881
|
await rm(summary.patchFilePath, { force: true });
|
|
2614
|
-
if (
|
|
2615
|
-
await rm(
|
|
2882
|
+
if (existsSync9(manifestPath)) {
|
|
2883
|
+
await rm(manifestPath, { force: true });
|
|
2616
2884
|
}
|
|
2617
2885
|
const installCmd = resolveInstallCommand(pm);
|
|
2618
|
-
|
|
2886
|
+
try {
|
|
2887
|
+
await safeExeca(installCmd[0], installCmd.slice(1), { cwd, stdio: "pipe" });
|
|
2888
|
+
} catch (installErr) {
|
|
2889
|
+
if (originalPkgJsonContent) {
|
|
2890
|
+
await writeFile(pkgJsonPath, originalPkgJsonContent, "utf8");
|
|
2891
|
+
}
|
|
2892
|
+
if (originalPatchContent) {
|
|
2893
|
+
await writeFile(summary.patchFilePath, originalPatchContent, "utf8");
|
|
2894
|
+
}
|
|
2895
|
+
if (originalManifestContent) {
|
|
2896
|
+
await writeFile(manifestPath, originalManifestContent, "utf8");
|
|
2897
|
+
}
|
|
2898
|
+
const msg = installErr instanceof Error ? installErr.message : String(installErr);
|
|
2899
|
+
results.push({
|
|
2900
|
+
patchFilePath: summary.patchFilePath,
|
|
2901
|
+
packageName,
|
|
2902
|
+
cveId,
|
|
2903
|
+
installedVersion: vulnerableVersion,
|
|
2904
|
+
safeUpstreamVersion: safeUpgradeVersion,
|
|
2905
|
+
status: "error",
|
|
2906
|
+
reason: `Package manager installation failed (${installCmd.join(" ")}): ${msg}. Restored original package.json and patch artifacts.`
|
|
2907
|
+
});
|
|
2908
|
+
continue;
|
|
2909
|
+
}
|
|
2619
2910
|
if (options.runTests) {
|
|
2620
2911
|
const testCmd = resolveTestCommand(pm);
|
|
2621
|
-
|
|
2912
|
+
try {
|
|
2913
|
+
await safeExeca(testCmd[0], testCmd.slice(1), { cwd, stdio: "pipe" });
|
|
2914
|
+
} catch (testErr) {
|
|
2915
|
+
if (originalPkgJsonContent) {
|
|
2916
|
+
await writeFile(pkgJsonPath, originalPkgJsonContent, "utf8");
|
|
2917
|
+
}
|
|
2918
|
+
if (originalPatchContent) {
|
|
2919
|
+
await writeFile(summary.patchFilePath, originalPatchContent, "utf8");
|
|
2920
|
+
}
|
|
2921
|
+
if (originalManifestContent) {
|
|
2922
|
+
await writeFile(manifestPath, originalManifestContent, "utf8");
|
|
2923
|
+
}
|
|
2924
|
+
const msg = testErr instanceof Error ? testErr.message : String(testErr);
|
|
2925
|
+
results.push({
|
|
2926
|
+
patchFilePath: summary.patchFilePath,
|
|
2927
|
+
packageName,
|
|
2928
|
+
cveId,
|
|
2929
|
+
installedVersion: vulnerableVersion,
|
|
2930
|
+
safeUpstreamVersion: safeUpgradeVersion,
|
|
2931
|
+
status: "error",
|
|
2932
|
+
reason: `Validation tests failed post-upgrade (${testCmd.join(" ")}): ${msg}. Restored original package.json and patch artifacts.`
|
|
2933
|
+
});
|
|
2934
|
+
continue;
|
|
2935
|
+
}
|
|
2622
2936
|
}
|
|
2623
2937
|
results.push({
|
|
2624
2938
|
patchFilePath: summary.patchFilePath,
|
|
@@ -2653,7 +2967,7 @@ async function reconcilePatchArtifacts(options = {}) {
|
|
|
2653
2967
|
}
|
|
2654
2968
|
|
|
2655
2969
|
// src/platform/idempotency.ts
|
|
2656
|
-
import { existsSync as
|
|
2970
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
2657
2971
|
import { join as join11 } from "path";
|
|
2658
2972
|
var DEFAULT_INDEX = {
|
|
2659
2973
|
schemaVersion: "1.0",
|
|
@@ -2667,9 +2981,9 @@ function entryKey(idempotencyKey, cveId) {
|
|
|
2667
2981
|
}
|
|
2668
2982
|
function loadIndex(cwd) {
|
|
2669
2983
|
const filePath = indexFilePath(cwd);
|
|
2670
|
-
if (!
|
|
2984
|
+
if (!existsSync10(filePath)) return DEFAULT_INDEX;
|
|
2671
2985
|
try {
|
|
2672
|
-
const parsed = JSON.parse(
|
|
2986
|
+
const parsed = JSON.parse(readFileSync9(filePath, "utf8"));
|
|
2673
2987
|
if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
|
|
2674
2988
|
return parsed;
|
|
2675
2989
|
}
|
|
@@ -2680,8 +2994,8 @@ function loadIndex(cwd) {
|
|
|
2680
2994
|
}
|
|
2681
2995
|
function saveIndex(cwd, index) {
|
|
2682
2996
|
const filePath = indexFilePath(cwd);
|
|
2683
|
-
|
|
2684
|
-
|
|
2997
|
+
mkdirSync4(join11(cwd, ".autoremediator", "state"), { recursive: true });
|
|
2998
|
+
writeFileSync4(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
|
|
2685
2999
|
}
|
|
2686
3000
|
function readIdempotentReport(cwd, idempotencyKey, cveId) {
|
|
2687
3001
|
const index = loadIndex(cwd);
|
|
@@ -2704,7 +3018,7 @@ function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
|
|
|
2704
3018
|
import { generateText as generateText2 } from "ai";
|
|
2705
3019
|
|
|
2706
3020
|
// src/remediation/local/run.ts
|
|
2707
|
-
import
|
|
3021
|
+
import semver7 from "semver";
|
|
2708
3022
|
|
|
2709
3023
|
// src/intelligence/sources/github-advisory.ts
|
|
2710
3024
|
var GITHUB_API_BASE = "https://api.github.com/advisories";
|
|
@@ -2843,9 +3157,9 @@ async function enrichWithNvd(details) {
|
|
|
2843
3157
|
}
|
|
2844
3158
|
|
|
2845
3159
|
// src/intelligence/sources/snapshot.ts
|
|
2846
|
-
import { readFileSync as
|
|
3160
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
2847
3161
|
function loadIntelligenceSnapshot(filePath) {
|
|
2848
|
-
const raw =
|
|
3162
|
+
const raw = readFileSync10(filePath, "utf8");
|
|
2849
3163
|
const parsed = JSON.parse(raw);
|
|
2850
3164
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2851
3165
|
throw new Error(`Intelligence snapshot at ${filePath} must be a JSON object keyed by CVE ID.`);
|
|
@@ -2853,11 +3167,37 @@ function loadIntelligenceSnapshot(filePath) {
|
|
|
2853
3167
|
return parsed;
|
|
2854
3168
|
}
|
|
2855
3169
|
function lookupSnapshotCve(snapshot, cveId) {
|
|
2856
|
-
|
|
3170
|
+
const store = snapshot.cves && typeof snapshot.cves === "object" && !Array.isArray(snapshot.cves) ? snapshot.cves : snapshot;
|
|
3171
|
+
const normalizedId = cveId.toUpperCase();
|
|
3172
|
+
const rawEntry = store[normalizedId] ?? store[cveId] ?? store[cveId.toLowerCase()];
|
|
3173
|
+
if (!rawEntry || typeof rawEntry !== "object") return null;
|
|
3174
|
+
if (typeof rawEntry.id === "string" && Array.isArray(rawEntry.affectedPackages) && typeof rawEntry.summary === "string") {
|
|
3175
|
+
return rawEntry;
|
|
3176
|
+
}
|
|
3177
|
+
const affectedPackages = Array.isArray(rawEntry.affectedPackages) ? rawEntry.affectedPackages : rawEntry.packageName ? [
|
|
3178
|
+
{
|
|
3179
|
+
name: rawEntry.packageName,
|
|
3180
|
+
ecosystem: "npm",
|
|
3181
|
+
vulnerableRange: rawEntry.vulnerableRange ?? "",
|
|
3182
|
+
firstPatchedVersion: rawEntry.firstPatchedVersion,
|
|
3183
|
+
source: "offline-snapshot"
|
|
3184
|
+
}
|
|
3185
|
+
] : [];
|
|
3186
|
+
const details = {
|
|
3187
|
+
id: rawEntry.id ?? rawEntry.cveId ?? normalizedId,
|
|
3188
|
+
summary: rawEntry.summary ?? `Offline intelligence details for ${normalizedId}`,
|
|
3189
|
+
severity: rawEntry.severity ?? "UNKNOWN",
|
|
3190
|
+
references: Array.isArray(rawEntry.references) ? rawEntry.references : [],
|
|
3191
|
+
affectedPackages,
|
|
3192
|
+
cvssScore: typeof rawEntry.cvssScore === "number" ? rawEntry.cvssScore : void 0,
|
|
3193
|
+
epss: rawEntry.epss ?? (typeof rawEntry.epssScore === "number" ? { score: rawEntry.epssScore, percentile: rawEntry.epssScore } : void 0),
|
|
3194
|
+
kev: rawEntry.kev ?? (rawEntry.cisaKev !== void 0 ? { knownExploited: Boolean(rawEntry.cisaKev) } : void 0)
|
|
3195
|
+
};
|
|
3196
|
+
return details;
|
|
2857
3197
|
}
|
|
2858
3198
|
|
|
2859
3199
|
// src/remediation/tools/check-reachability.ts
|
|
2860
|
-
import { existsSync as
|
|
3200
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync11, statSync as statSync2 } from "fs";
|
|
2861
3201
|
import { isAbsolute as isAbsolute2, join as join12, resolve as resolve3 } from "path";
|
|
2862
3202
|
import { parseSync } from "oxc-parser";
|
|
2863
3203
|
import { z as z2 } from "zod";
|
|
@@ -2875,9 +3215,9 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
2875
3215
|
function loadTsconfigPaths(cwd) {
|
|
2876
3216
|
for (const configName of ["tsconfig.json", "jsconfig.json"]) {
|
|
2877
3217
|
const fullPath = join12(cwd, configName);
|
|
2878
|
-
if (!
|
|
3218
|
+
if (!existsSync11(fullPath)) continue;
|
|
2879
3219
|
try {
|
|
2880
|
-
const raw =
|
|
3220
|
+
const raw = readFileSync11(fullPath, "utf8");
|
|
2881
3221
|
const cleaned = raw.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
|
|
2882
3222
|
const parsed = JSON.parse(cleaned);
|
|
2883
3223
|
const compilerOptions = parsed.compilerOptions;
|
|
@@ -2971,9 +3311,9 @@ function toRootPackageName(importSource) {
|
|
|
2971
3311
|
function findTransitiveDependencyPath(cwd, targetPackage, importedDirectPackages) {
|
|
2972
3312
|
for (const directPkg of importedDirectPackages) {
|
|
2973
3313
|
const manifestPath = join12(cwd, "node_modules", directPkg, "package.json");
|
|
2974
|
-
if (!
|
|
3314
|
+
if (!existsSync11(manifestPath)) continue;
|
|
2975
3315
|
try {
|
|
2976
|
-
const manifest = JSON.parse(
|
|
3316
|
+
const manifest = JSON.parse(readFileSync11(manifestPath, "utf8"));
|
|
2977
3317
|
if (manifest.dependencies && manifest.dependencies[targetPackage]) {
|
|
2978
3318
|
return {
|
|
2979
3319
|
directDependency: directPkg,
|
|
@@ -3146,7 +3486,7 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
|
|
|
3146
3486
|
scanned++;
|
|
3147
3487
|
let content;
|
|
3148
3488
|
try {
|
|
3149
|
-
content =
|
|
3489
|
+
content = readFileSync11(filePath, "utf8");
|
|
3150
3490
|
} catch {
|
|
3151
3491
|
continue;
|
|
3152
3492
|
}
|
|
@@ -3390,85 +3730,295 @@ function buildSbom(packages, vulnerableNames, results) {
|
|
|
3390
3730
|
|
|
3391
3731
|
// src/remediation/tools/apply-version-bump.ts
|
|
3392
3732
|
import { z as z4 } from "zod";
|
|
3393
|
-
import { join as
|
|
3394
|
-
import { readFileSync as
|
|
3733
|
+
import { join as join16 } from "path";
|
|
3734
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
|
|
3735
|
+
import { rm as rm3 } from "fs/promises";
|
|
3736
|
+
import semver4 from "semver";
|
|
3395
3737
|
|
|
3396
|
-
// src/
|
|
3397
|
-
import {
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
"PERL5OPT",
|
|
3406
|
-
"PERL5LIB",
|
|
3407
|
-
"PYTHONWARNINGS",
|
|
3408
|
-
"PYTHONPATH",
|
|
3409
|
-
"RUBYOPT",
|
|
3410
|
-
"RUBYLIB",
|
|
3411
|
-
"BASH_ENV",
|
|
3412
|
-
"ENV"
|
|
3413
|
-
]);
|
|
3414
|
-
function sanitizeEnvironment(env = process.env, allowNodeOptions = false) {
|
|
3415
|
-
const sanitized = {};
|
|
3416
|
-
for (const [key, value] of Object.entries(env)) {
|
|
3417
|
-
if (value === void 0) continue;
|
|
3418
|
-
if (key.includes("\0") || typeof value === "string" && value.includes("\0")) continue;
|
|
3419
|
-
if (DANGEROUS_ENV_VARS.has(key)) {
|
|
3420
|
-
if (key === "NODE_OPTIONS" && allowNodeOptions) {
|
|
3421
|
-
sanitized[key] = value;
|
|
3422
|
-
}
|
|
3423
|
-
continue;
|
|
3424
|
-
}
|
|
3425
|
-
sanitized[key] = value;
|
|
3738
|
+
// src/remediation/proof-of-fix/runner.ts
|
|
3739
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3740
|
+
import { mkdir, writeFile as writeFile2 } from "fs/promises";
|
|
3741
|
+
import { isAbsolute as isAbsolute3, join as join13 } from "path";
|
|
3742
|
+
function sanitizePathLeakage(text, paths = []) {
|
|
3743
|
+
let sanitized = text;
|
|
3744
|
+
for (const p of paths) {
|
|
3745
|
+
if (!p || p.trim().length === 0) continue;
|
|
3746
|
+
sanitized = sanitized.split(p).join("<path>");
|
|
3426
3747
|
}
|
|
3427
3748
|
return sanitized;
|
|
3428
3749
|
}
|
|
3429
|
-
function
|
|
3430
|
-
|
|
3431
|
-
|
|
3750
|
+
function stripStackTraceFrames(text) {
|
|
3751
|
+
return text.split("\n").filter((line) => !/^\s*at\s+/.test(line)).join("\n");
|
|
3752
|
+
}
|
|
3753
|
+
function isHarnessFailure(context) {
|
|
3754
|
+
const { exitCode, output, errorMsg = "", signal } = context;
|
|
3755
|
+
const errorMsgEmbedsPath = errorMsg.includes("Command failed") || context.testFilePath && errorMsg.includes(context.testFilePath) || errorMsg.includes("/") || errorMsg.includes("\\");
|
|
3756
|
+
const filteredErrorMsg = errorMsgEmbedsPath ? "" : errorMsg;
|
|
3757
|
+
const combined = filteredErrorMsg ? `${output}
|
|
3758
|
+
${filteredErrorMsg}` : output;
|
|
3759
|
+
if (signal !== void 0) {
|
|
3760
|
+
return true;
|
|
3432
3761
|
}
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3762
|
+
if (typeof exitCode === "number" && (exitCode === 126 || exitCode === 127 || exitCode > 128)) {
|
|
3763
|
+
return true;
|
|
3764
|
+
}
|
|
3765
|
+
if (combined.includes("ENOENT") || combined.includes("no such file or directory")) {
|
|
3766
|
+
return true;
|
|
3767
|
+
}
|
|
3768
|
+
if (combined.includes("bad option:") || combined.includes("bad option") || combined.includes("Unrecognized option") || combined.includes("illegal option") || combined.includes("invalid option")) {
|
|
3769
|
+
return true;
|
|
3770
|
+
}
|
|
3771
|
+
if (combined.includes("SyntaxError") || combined.includes("Unexpected token") || combined.includes("Cannot use import statement outside a module")) {
|
|
3772
|
+
return true;
|
|
3773
|
+
}
|
|
3774
|
+
if (combined.includes("Cannot find module") || combined.includes("Cannot find package") || combined.includes("MODULE_NOT_FOUND") || combined.includes("ERR_MODULE_NOT_FOUND") || combined.includes("ERR_REQUIRE_ESM") || combined.includes("ERR_UNKNOWN_FILE_EXTENSION") || combined.includes("ERR_PACKAGE_PATH_NOT_EXPORTED") || combined.includes("ERR_PACKAGE_IMPORT_NOT_DEFINED") || combined.includes("ERR_UNSUPPORTED_DIR_IMPORT") || combined.includes("ERR_UNSUPPORTED_ESM_URL_SCHEME") || combined.includes("ERR_INVALID_MODULE_SPECIFIER")) {
|
|
3775
|
+
return true;
|
|
3776
|
+
}
|
|
3777
|
+
if (combined.includes("Allocation failed - JavaScript heap out of memory") || combined.includes("FATAL ERROR:") || combined.includes("UnhandledPromiseRejection:") || combined.includes("ERR_UNHANDLED_REJECTION") || combined.includes("(Use `node --trace-uncaught") || /^\s*(?!(?:AssertionError)\b)[A-Z]\w*Error:\s/m.test(combined)) {
|
|
3778
|
+
return true;
|
|
3779
|
+
}
|
|
3780
|
+
return false;
|
|
3781
|
+
}
|
|
3782
|
+
function isAssertionFailure(output, exitCode) {
|
|
3783
|
+
if (exitCode !== 1) {
|
|
3784
|
+
return false;
|
|
3785
|
+
}
|
|
3786
|
+
const nonStackOutput = stripStackTraceFrames(output);
|
|
3787
|
+
const hasUnhandledCrash = /^\s*(?:(?!(?:AssertionError)\b)[A-Z]\w*Error|Error|UnhandledPromiseRejection):\s/m.test(
|
|
3788
|
+
nonStackOutput
|
|
3789
|
+
) || /^\s*FATAL ERROR:/m.test(nonStackOutput) || /\(Use `node --trace-uncaught/m.test(nonStackOutput);
|
|
3790
|
+
if (hasUnhandledCrash) {
|
|
3791
|
+
return false;
|
|
3792
|
+
}
|
|
3793
|
+
return /^\s*AssertionError(?:\s*\[ERR_ASSERTION\])?(?::|$)/m.test(nonStackOutput) || /^\s*(?:Error\s*)?\[ERR_ASSERTION\](?::|$)/m.test(nonStackOutput) || /^\s*ERR_ASSERTION(?::|\s*$)/m.test(nonStackOutput) || /^\s*EXPLOIT_REPRODUCED(?::|\s*$)/m.test(nonStackOutput) || /^\s*Assertion failed(?::|$)/im.test(nonStackOutput);
|
|
3794
|
+
}
|
|
3795
|
+
async function prepareReproductionTestFile(params) {
|
|
3796
|
+
const {
|
|
3797
|
+
cwd,
|
|
3798
|
+
packageName,
|
|
3799
|
+
vulnerableVersion,
|
|
3800
|
+
patchesDir = "./patches",
|
|
3801
|
+
proofOfFix,
|
|
3802
|
+
reproductionTest
|
|
3803
|
+
} = params;
|
|
3804
|
+
const patchesDirPath = isAbsolute3(patchesDir) ? patchesDir : join13(cwd, patchesDir);
|
|
3805
|
+
const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
|
|
3806
|
+
const defaultTestPath = join13(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
|
|
3807
|
+
if (typeof proofOfFix === "string" && proofOfFix.trim().length > 0) {
|
|
3808
|
+
const resolvedPath = isAbsolute3(proofOfFix) ? proofOfFix : join13(cwd, proofOfFix);
|
|
3809
|
+
if (existsSync12(resolvedPath)) {
|
|
3810
|
+
return { testFilePath: resolvedPath, isCreated: false };
|
|
3436
3811
|
}
|
|
3812
|
+
await mkdir(patchesDirPath, { recursive: true });
|
|
3813
|
+
await writeFile2(defaultTestPath, proofOfFix, "utf8");
|
|
3814
|
+
return { testFilePath: defaultTestPath, isCreated: true };
|
|
3437
3815
|
}
|
|
3816
|
+
if (reproductionTest && reproductionTest.trim().length > 0) {
|
|
3817
|
+
const resolvedPath = isAbsolute3(reproductionTest) ? reproductionTest : join13(cwd, reproductionTest);
|
|
3818
|
+
if (existsSync12(resolvedPath)) {
|
|
3819
|
+
return { testFilePath: resolvedPath, isCreated: false };
|
|
3820
|
+
}
|
|
3821
|
+
await mkdir(patchesDirPath, { recursive: true });
|
|
3822
|
+
await writeFile2(defaultTestPath, reproductionTest, "utf8");
|
|
3823
|
+
return { testFilePath: defaultTestPath, isCreated: true };
|
|
3824
|
+
}
|
|
3825
|
+
if (proofOfFix === true && existsSync12(defaultTestPath)) {
|
|
3826
|
+
return { testFilePath: defaultTestPath, isCreated: false };
|
|
3827
|
+
}
|
|
3828
|
+
return { testFilePath: void 0, isCreated: false };
|
|
3438
3829
|
}
|
|
3439
|
-
function
|
|
3440
|
-
|
|
3441
|
-
const
|
|
3442
|
-
const
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3830
|
+
async function runProofOfFixPhase(options) {
|
|
3831
|
+
const { stage, testFilePath, cwd, timeoutMs = 15e3 } = options;
|
|
3832
|
+
const started = Date.now();
|
|
3833
|
+
const isTs = testFilePath.endsWith(".ts");
|
|
3834
|
+
const nodeArgs = isTs ? ["--experimental-strip-types", testFilePath] : [testFilePath];
|
|
3835
|
+
try {
|
|
3836
|
+
const result = await safeExeca("node", nodeArgs, {
|
|
3837
|
+
cwd,
|
|
3838
|
+
timeoutMs,
|
|
3839
|
+
stdio: "pipe"
|
|
3840
|
+
});
|
|
3841
|
+
const durationMs = Date.now() - started;
|
|
3842
|
+
const stdout = String(result.stdout ?? "");
|
|
3843
|
+
const stderr = String(result.stderr ?? "");
|
|
3844
|
+
const output = `${stdout}
|
|
3845
|
+
${stderr}`.trim();
|
|
3846
|
+
if (stage === "negative") {
|
|
3847
|
+
return {
|
|
3848
|
+
executed: true,
|
|
3849
|
+
passed: false,
|
|
3850
|
+
exitCode: 0,
|
|
3851
|
+
output,
|
|
3852
|
+
durationMs,
|
|
3853
|
+
error: "Exploit reproduction test passed on vulnerable target (failed to reproduce vulnerability)."
|
|
3854
|
+
};
|
|
3855
|
+
}
|
|
3856
|
+
return {
|
|
3857
|
+
executed: true,
|
|
3858
|
+
passed: true,
|
|
3859
|
+
exitCode: 0,
|
|
3860
|
+
output,
|
|
3861
|
+
durationMs
|
|
3862
|
+
};
|
|
3863
|
+
} catch (err) {
|
|
3864
|
+
const durationMs = Date.now() - started;
|
|
3865
|
+
const execaErr = err;
|
|
3866
|
+
const exitCode = typeof execaErr.exitCode === "number" ? execaErr.exitCode : 1;
|
|
3867
|
+
const errorMsg = execaErr.message || String(err);
|
|
3868
|
+
const rawStdout = String(execaErr.stdout ?? "").trim();
|
|
3869
|
+
const rawStderr = String(execaErr.stderr ?? "").trim();
|
|
3870
|
+
const processOutput = [rawStdout, rawStderr].filter(Boolean).join("\n").trim();
|
|
3871
|
+
const output = processOutput || errorMsg;
|
|
3872
|
+
if (stage === "negative") {
|
|
3873
|
+
if (execaErr.timedOut || errorMsg.includes("timed out") || output.includes("timed out") || execaErr.code === "ETIMEDOUT") {
|
|
3874
|
+
return {
|
|
3875
|
+
executed: true,
|
|
3876
|
+
passed: false,
|
|
3877
|
+
harnessFailure: true,
|
|
3878
|
+
exitCode,
|
|
3879
|
+
output,
|
|
3880
|
+
durationMs,
|
|
3881
|
+
error: "Exploit reproduction test timed out (execution hung)."
|
|
3882
|
+
};
|
|
3883
|
+
}
|
|
3884
|
+
if (isHarnessFailure({
|
|
3885
|
+
exitCode,
|
|
3886
|
+
output,
|
|
3887
|
+
errorMsg,
|
|
3888
|
+
testFilePath,
|
|
3889
|
+
signal: execaErr.signal
|
|
3890
|
+
})) {
|
|
3891
|
+
return {
|
|
3892
|
+
executed: false,
|
|
3893
|
+
passed: false,
|
|
3894
|
+
harnessFailure: true,
|
|
3895
|
+
exitCode,
|
|
3896
|
+
output,
|
|
3897
|
+
durationMs,
|
|
3898
|
+
error: `Reproduction test failed to execute (harness error): ${errorMsg}`
|
|
3899
|
+
};
|
|
3900
|
+
}
|
|
3901
|
+
const knownPaths = [
|
|
3902
|
+
cwd,
|
|
3903
|
+
testFilePath,
|
|
3904
|
+
isAbsolute3(testFilePath) ? testFilePath : join13(cwd, testFilePath)
|
|
3905
|
+
];
|
|
3906
|
+
if (rawStderr) {
|
|
3907
|
+
const stderrSanitized = sanitizePathLeakage(stripStackTraceFrames(rawStderr), knownPaths);
|
|
3908
|
+
if (isAssertionFailure(stderrSanitized, exitCode)) {
|
|
3909
|
+
return {
|
|
3910
|
+
executed: true,
|
|
3911
|
+
passed: true,
|
|
3912
|
+
exitCode,
|
|
3913
|
+
output,
|
|
3914
|
+
durationMs
|
|
3915
|
+
};
|
|
3916
|
+
}
|
|
3917
|
+
const hasStderrCrash = /^\s*(?:(?!(?:AssertionError)\b)[A-Z]\w*Error|Error|UnhandledPromiseRejection|UnhandledPromiseRejectionWarning):\s/m.test(
|
|
3918
|
+
stderrSanitized
|
|
3919
|
+
) || /^\s*FATAL ERROR:/m.test(stderrSanitized) || stderrSanitized.includes("Allocation failed - JavaScript heap out of memory") || /\(Use `node --trace-uncaught/m.test(rawStderr) || /\bERR_UNHANDLED_REJECTION\b/.test(rawStderr) || /^\s*at\s+/m.test(rawStderr) || /Node\.js v\d+/m.test(rawStderr);
|
|
3920
|
+
if (hasStderrCrash) {
|
|
3921
|
+
return {
|
|
3922
|
+
executed: false,
|
|
3923
|
+
passed: false,
|
|
3924
|
+
harnessFailure: true,
|
|
3925
|
+
exitCode,
|
|
3926
|
+
output,
|
|
3927
|
+
durationMs,
|
|
3928
|
+
error: `Reproduction test failed with runtime error instead of exploit assertion: ${errorMsg}`
|
|
3929
|
+
};
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
if (rawStdout) {
|
|
3933
|
+
const stdoutSanitized = sanitizePathLeakage(stripStackTraceFrames(rawStdout), knownPaths);
|
|
3934
|
+
if (isAssertionFailure(stdoutSanitized, exitCode)) {
|
|
3935
|
+
return {
|
|
3936
|
+
executed: true,
|
|
3937
|
+
passed: true,
|
|
3938
|
+
exitCode,
|
|
3939
|
+
output,
|
|
3940
|
+
durationMs
|
|
3941
|
+
};
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
if (errorMsg) {
|
|
3945
|
+
const errorMsgSanitized = sanitizePathLeakage(stripStackTraceFrames(errorMsg), knownPaths);
|
|
3946
|
+
if (isAssertionFailure(errorMsgSanitized, exitCode)) {
|
|
3947
|
+
return {
|
|
3948
|
+
executed: true,
|
|
3949
|
+
passed: true,
|
|
3950
|
+
exitCode,
|
|
3951
|
+
output,
|
|
3952
|
+
durationMs
|
|
3953
|
+
};
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
return {
|
|
3957
|
+
executed: false,
|
|
3958
|
+
passed: false,
|
|
3959
|
+
harnessFailure: true,
|
|
3960
|
+
exitCode,
|
|
3961
|
+
output,
|
|
3962
|
+
durationMs,
|
|
3963
|
+
error: `Reproduction test failed with runtime error instead of exploit assertion: ${errorMsg}`
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3966
|
+
return {
|
|
3967
|
+
executed: true,
|
|
3968
|
+
passed: false,
|
|
3969
|
+
exitCode,
|
|
3970
|
+
output,
|
|
3971
|
+
durationMs,
|
|
3972
|
+
error: `Reproduction test failed on patched target: ${errorMsg}`
|
|
3973
|
+
};
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
function assembleProofOfFixVerification(params) {
|
|
3977
|
+
const { reproductionTestPath, negativePhase, positivePhase, regressionPhase } = params;
|
|
3978
|
+
const regPhase = regressionPhase ?? {
|
|
3979
|
+
executed: false,
|
|
3980
|
+
passed: true
|
|
3981
|
+
};
|
|
3982
|
+
let status = "verified";
|
|
3983
|
+
let failureReason;
|
|
3984
|
+
if (!negativePhase.passed) {
|
|
3985
|
+
status = "failed";
|
|
3986
|
+
failureReason = negativePhase.error || "negative-phase-failed";
|
|
3987
|
+
} else if (!positivePhase.passed) {
|
|
3988
|
+
status = "failed";
|
|
3989
|
+
failureReason = positivePhase.error || "positive-phase-failed";
|
|
3990
|
+
} else if (regPhase.executed && !regPhase.passed) {
|
|
3991
|
+
status = "failed";
|
|
3992
|
+
failureReason = regPhase.error || "regression-phase-failed";
|
|
3993
|
+
}
|
|
3994
|
+
const durationMs = (negativePhase.durationMs || 0) + (positivePhase.durationMs || 0) + (regPhase.durationMs || 0);
|
|
3995
|
+
return {
|
|
3996
|
+
status,
|
|
3997
|
+
reproductionTestPath,
|
|
3998
|
+
negativePhase,
|
|
3999
|
+
positivePhase,
|
|
4000
|
+
regressionPhase: regPhase,
|
|
4001
|
+
durationMs,
|
|
4002
|
+
failureReason
|
|
4003
|
+
};
|
|
3451
4004
|
}
|
|
3452
|
-
|
|
3453
|
-
// src/remediation/tools/apply-version-bump.ts
|
|
3454
|
-
import semver3 from "semver";
|
|
3455
4005
|
|
|
3456
4006
|
// src/platform/repo-lock.ts
|
|
3457
|
-
import { mkdir, rm as rm2 } from "fs/promises";
|
|
3458
|
-
import { join as
|
|
4007
|
+
import { mkdir as mkdir2, rm as rm2 } from "fs/promises";
|
|
4008
|
+
import { join as join14 } from "path";
|
|
3459
4009
|
async function sleep(ms) {
|
|
3460
|
-
await new Promise((
|
|
4010
|
+
await new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
3461
4011
|
}
|
|
3462
4012
|
async function acquireRepoLock(cwd, options = {}) {
|
|
3463
4013
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
3464
4014
|
const retryDelayMs = options.retryDelayMs ?? 125;
|
|
3465
|
-
const lockRoot =
|
|
3466
|
-
const lockPath =
|
|
4015
|
+
const lockRoot = join14(cwd, ".autoremediator", "locks");
|
|
4016
|
+
const lockPath = join14(cwd, ".autoremediator", "locks", "remediation.lock");
|
|
3467
4017
|
const startedAt = Date.now();
|
|
3468
|
-
await
|
|
4018
|
+
await mkdir2(lockRoot, { recursive: true });
|
|
3469
4019
|
while (true) {
|
|
3470
4020
|
try {
|
|
3471
|
-
await
|
|
4021
|
+
await mkdir2(lockPath, { recursive: false });
|
|
3472
4022
|
return {
|
|
3473
4023
|
lockPath,
|
|
3474
4024
|
release: async () => {
|
|
@@ -3493,8 +4043,8 @@ async function withRepoLock(cwd, fn, options) {
|
|
|
3493
4043
|
}
|
|
3494
4044
|
|
|
3495
4045
|
// src/remediation/refactor/call-site-finder.ts
|
|
3496
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
3497
|
-
import { join as
|
|
4046
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
|
|
4047
|
+
import { join as join15 } from "path";
|
|
3498
4048
|
import { parseSync as parseSync2 } from "oxc-parser";
|
|
3499
4049
|
var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
|
|
3500
4050
|
var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
|
|
@@ -3516,7 +4066,7 @@ function* walkFiles(dir) {
|
|
|
3516
4066
|
}
|
|
3517
4067
|
for (const entry of entries) {
|
|
3518
4068
|
if (IGNORED_DIRS2.has(entry)) continue;
|
|
3519
|
-
const full =
|
|
4069
|
+
const full = join15(dir, entry);
|
|
3520
4070
|
let stat2;
|
|
3521
4071
|
try {
|
|
3522
4072
|
stat2 = statSync3(full);
|
|
@@ -3538,7 +4088,7 @@ function findPackageCallSites(cwd, packageName) {
|
|
|
3538
4088
|
for (const filePath of walkFiles(cwd)) {
|
|
3539
4089
|
let content;
|
|
3540
4090
|
try {
|
|
3541
|
-
content =
|
|
4091
|
+
content = readFileSync12(filePath, "utf8");
|
|
3542
4092
|
} catch {
|
|
3543
4093
|
continue;
|
|
3544
4094
|
}
|
|
@@ -3546,6 +4096,94 @@ function findPackageCallSites(cwd, packageName) {
|
|
|
3546
4096
|
continue;
|
|
3547
4097
|
}
|
|
3548
4098
|
try {
|
|
4099
|
+
let getLineCol2 = function(offset) {
|
|
4100
|
+
let low = 0;
|
|
4101
|
+
let high = lineStarts.length - 1;
|
|
4102
|
+
while (low <= high) {
|
|
4103
|
+
const mid = Math.floor((low + high) / 2);
|
|
4104
|
+
if (lineStarts[mid] <= offset) {
|
|
4105
|
+
if (mid === lineStarts.length - 1 || lineStarts[mid + 1] > offset) {
|
|
4106
|
+
return { line: mid + 1, column: offset - lineStarts[mid] + 1 };
|
|
4107
|
+
}
|
|
4108
|
+
low = mid + 1;
|
|
4109
|
+
} else {
|
|
4110
|
+
high = mid - 1;
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
return { line: 1, column: offset + 1 };
|
|
4114
|
+
}, walk2 = function(node, parentType) {
|
|
4115
|
+
if (!node || typeof node !== "object") return;
|
|
4116
|
+
if (node.type === "CallExpression") {
|
|
4117
|
+
let localSymbol = "";
|
|
4118
|
+
let memberName;
|
|
4119
|
+
if (node.callee?.type === "Identifier" && localNames.has(node.callee.name)) {
|
|
4120
|
+
localSymbol = node.callee.name;
|
|
4121
|
+
} else if (node.callee?.type === "MemberExpression" && node.callee.object?.type === "Identifier" && localNames.has(node.callee.object.name)) {
|
|
4122
|
+
localSymbol = node.callee.object.name;
|
|
4123
|
+
memberName = node.callee.property?.name;
|
|
4124
|
+
}
|
|
4125
|
+
if (localSymbol) {
|
|
4126
|
+
const { line, column } = getLineCol2(node.start);
|
|
4127
|
+
referencedLinesSet.add(line);
|
|
4128
|
+
const argumentSpans = (node.arguments || []).map((arg) => ({
|
|
4129
|
+
start: arg.start,
|
|
4130
|
+
end: arg.end,
|
|
4131
|
+
text: content.slice(arg.start, arg.end)
|
|
4132
|
+
}));
|
|
4133
|
+
astCallSites.push({
|
|
4134
|
+
start: node.start,
|
|
4135
|
+
end: node.end,
|
|
4136
|
+
line,
|
|
4137
|
+
column,
|
|
4138
|
+
calleeText: content.slice(node.callee.start, node.callee.end),
|
|
4139
|
+
expressionType: "call",
|
|
4140
|
+
localSymbol,
|
|
4141
|
+
memberName,
|
|
4142
|
+
argumentCount: (node.arguments || []).length,
|
|
4143
|
+
argumentSpans
|
|
4144
|
+
});
|
|
4145
|
+
}
|
|
4146
|
+
} else if (node.type === "MemberExpression") {
|
|
4147
|
+
if (node.object?.type === "Identifier" && localNames.has(node.object.name)) {
|
|
4148
|
+
const { line, column } = getLineCol2(node.start);
|
|
4149
|
+
referencedLinesSet.add(line);
|
|
4150
|
+
if (parentType !== "CallExpression") {
|
|
4151
|
+
astCallSites.push({
|
|
4152
|
+
start: node.start,
|
|
4153
|
+
end: node.end,
|
|
4154
|
+
line,
|
|
4155
|
+
column,
|
|
4156
|
+
calleeText: content.slice(node.start, node.end),
|
|
4157
|
+
expressionType: "property-access",
|
|
4158
|
+
localSymbol: node.object.name,
|
|
4159
|
+
memberName: node.property?.name,
|
|
4160
|
+
argumentCount: 0,
|
|
4161
|
+
argumentSpans: []
|
|
4162
|
+
});
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
} else if (node.type === "Identifier") {
|
|
4166
|
+
if (localNames.has(node.name)) {
|
|
4167
|
+
const isImportDecl = Boolean(parentType?.startsWith("Import"));
|
|
4168
|
+
if (!isImportDecl) {
|
|
4169
|
+
const { line } = getLineCol2(node.start);
|
|
4170
|
+
referencedLinesSet.add(line);
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
for (const key of Object.keys(node)) {
|
|
4175
|
+
if (key === "parent") continue;
|
|
4176
|
+
const child = node[key];
|
|
4177
|
+
if (Array.isArray(child)) {
|
|
4178
|
+
for (const item of child) {
|
|
4179
|
+
walk2(item, node.type);
|
|
4180
|
+
}
|
|
4181
|
+
} else if (child && typeof child === "object") {
|
|
4182
|
+
walk2(child, node.type);
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
};
|
|
4186
|
+
var getLineCol = getLineCol2, walk = walk2;
|
|
3549
4187
|
const parseResult = parseSync2(filePath, content);
|
|
3550
4188
|
if (!parseResult.program) continue;
|
|
3551
4189
|
const program = parseResult.program;
|
|
@@ -3554,7 +4192,7 @@ function findPackageCallSites(cwd, packageName) {
|
|
|
3554
4192
|
const localNames = /* @__PURE__ */ new Set();
|
|
3555
4193
|
for (const statement of program.body) {
|
|
3556
4194
|
if (statement.type === "ImportDeclaration" && statement.source?.value) {
|
|
3557
|
-
const importSource = statement.source.value;
|
|
4195
|
+
const importSource = String(statement.source.value);
|
|
3558
4196
|
if (importSource === packageName || importSource.startsWith(`${packageName}/`)) {
|
|
3559
4197
|
importSources.push(importSource);
|
|
3560
4198
|
for (const spec of statement.specifiers || []) {
|
|
@@ -3566,47 +4204,90 @@ function findPackageCallSites(cwd, packageName) {
|
|
|
3566
4204
|
localName,
|
|
3567
4205
|
importedName: "default",
|
|
3568
4206
|
isDefault: true,
|
|
3569
|
-
isNamespace: false
|
|
4207
|
+
isNamespace: false,
|
|
4208
|
+
kind: "default",
|
|
4209
|
+
start: spec.local?.start,
|
|
4210
|
+
end: spec.local?.end
|
|
3570
4211
|
});
|
|
3571
4212
|
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
3572
4213
|
imports.push({
|
|
3573
4214
|
localName,
|
|
3574
4215
|
importedName: "*",
|
|
3575
4216
|
isDefault: false,
|
|
3576
|
-
isNamespace: true
|
|
4217
|
+
isNamespace: true,
|
|
4218
|
+
kind: "namespace",
|
|
4219
|
+
start: spec.local?.start,
|
|
4220
|
+
end: spec.local?.end
|
|
3577
4221
|
});
|
|
3578
4222
|
} else if (spec.type === "ImportSpecifier") {
|
|
3579
4223
|
imports.push({
|
|
3580
4224
|
localName,
|
|
3581
4225
|
importedName: spec.imported?.name || localName,
|
|
3582
4226
|
isDefault: false,
|
|
3583
|
-
isNamespace: false
|
|
4227
|
+
isNamespace: false,
|
|
4228
|
+
kind: "named",
|
|
4229
|
+
start: spec.local?.start,
|
|
4230
|
+
end: spec.local?.end
|
|
3584
4231
|
});
|
|
3585
4232
|
}
|
|
3586
4233
|
}
|
|
3587
4234
|
}
|
|
3588
4235
|
}
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
4236
|
+
if (statement.type === "VariableDeclaration") {
|
|
4237
|
+
for (const decl of statement.declarations || []) {
|
|
4238
|
+
if (decl.init?.type === "CallExpression" && decl.init.callee?.type === "Identifier" && decl.init.callee.name === "require" && decl.init.arguments?.[0]?.value) {
|
|
4239
|
+
const importSource = String(decl.init.arguments[0].value);
|
|
4240
|
+
if (importSource === packageName || importSource.startsWith(`${packageName}/`)) {
|
|
4241
|
+
importSources.push(importSource);
|
|
4242
|
+
if (decl.id?.type === "Identifier") {
|
|
4243
|
+
const localName = decl.id.name;
|
|
4244
|
+
localNames.add(localName);
|
|
4245
|
+
imports.push({
|
|
4246
|
+
localName,
|
|
4247
|
+
importedName: "default",
|
|
4248
|
+
isDefault: true,
|
|
4249
|
+
isNamespace: false,
|
|
4250
|
+
kind: "require-default"
|
|
4251
|
+
});
|
|
4252
|
+
} else if (decl.id?.type === "ObjectPattern") {
|
|
4253
|
+
for (const prop of decl.id.properties || []) {
|
|
4254
|
+
const localName = prop.value?.name || prop.key?.name;
|
|
4255
|
+
if (localName) {
|
|
4256
|
+
localNames.add(localName);
|
|
4257
|
+
imports.push({
|
|
4258
|
+
localName,
|
|
4259
|
+
importedName: prop.key?.name || localName,
|
|
4260
|
+
isDefault: false,
|
|
4261
|
+
isNamespace: false,
|
|
4262
|
+
kind: "require-destructured"
|
|
4263
|
+
});
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
3599
4268
|
}
|
|
3600
4269
|
}
|
|
3601
|
-
}
|
|
3602
|
-
callSites.push({
|
|
3603
|
-
filePath,
|
|
3604
|
-
sourceContent: content,
|
|
3605
|
-
importSources,
|
|
3606
|
-
imports,
|
|
3607
|
-
referencedLines
|
|
3608
|
-
});
|
|
4270
|
+
}
|
|
3609
4271
|
}
|
|
4272
|
+
if (imports.length === 0 && importSources.length === 0) {
|
|
4273
|
+
continue;
|
|
4274
|
+
}
|
|
4275
|
+
const lineStarts = [0];
|
|
4276
|
+
for (let i = 0; i < content.length; i++) {
|
|
4277
|
+
if (content[i] === "\n") lineStarts.push(i + 1);
|
|
4278
|
+
}
|
|
4279
|
+
const referencedLinesSet = /* @__PURE__ */ new Set();
|
|
4280
|
+
const astCallSites = [];
|
|
4281
|
+
walk2(program);
|
|
4282
|
+
const referencedLines = Array.from(referencedLinesSet).sort((a, b) => a - b);
|
|
4283
|
+
callSites.push({
|
|
4284
|
+
filePath,
|
|
4285
|
+
sourceContent: content,
|
|
4286
|
+
importSources,
|
|
4287
|
+
imports,
|
|
4288
|
+
referencedLines,
|
|
4289
|
+
callSites: astCallSites
|
|
4290
|
+
});
|
|
3610
4291
|
} catch {
|
|
3611
4292
|
}
|
|
3612
4293
|
}
|
|
@@ -3614,7 +4295,17 @@ function findPackageCallSites(cwd, packageName) {
|
|
|
3614
4295
|
}
|
|
3615
4296
|
|
|
3616
4297
|
// src/remediation/refactor/refactor-agent.ts
|
|
3617
|
-
import { readFileSync as
|
|
4298
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
|
|
4299
|
+
import { parseSync as parseSync3 } from "oxc-parser";
|
|
4300
|
+
function applyEdits(source, edits) {
|
|
4301
|
+
if (edits.length === 0) return source;
|
|
4302
|
+
const sorted = [...edits].sort((a, b) => b.start - a.start);
|
|
4303
|
+
let result = source;
|
|
4304
|
+
for (const edit of sorted) {
|
|
4305
|
+
result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);
|
|
4306
|
+
}
|
|
4307
|
+
return result;
|
|
4308
|
+
}
|
|
3618
4309
|
function parseCompilerDiagnostics(diagnosticError) {
|
|
3619
4310
|
const diagnostics = [];
|
|
3620
4311
|
const lines = diagnosticError.split("\n");
|
|
@@ -3649,173 +4340,332 @@ function parseCompilerDiagnostics(diagnosticError) {
|
|
|
3649
4340
|
function revertRefactorSnapshots(snapshots) {
|
|
3650
4341
|
for (const [filePath, content] of Object.entries(snapshots)) {
|
|
3651
4342
|
try {
|
|
3652
|
-
|
|
4343
|
+
writeFileSync5(filePath, content, "utf8");
|
|
3653
4344
|
} catch {
|
|
3654
4345
|
}
|
|
3655
4346
|
}
|
|
3656
4347
|
}
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
4348
|
+
function transformCallArguments(content, options) {
|
|
4349
|
+
const parseResult = parseSync3(options.filePath ?? "file.ts", content);
|
|
4350
|
+
if (!parseResult.program) {
|
|
4351
|
+
return { modified: false, newContent: content };
|
|
4352
|
+
}
|
|
4353
|
+
const edits = [];
|
|
4354
|
+
function walk(node) {
|
|
4355
|
+
if (!node || typeof node !== "object") return;
|
|
4356
|
+
if (node.type === "CallExpression") {
|
|
4357
|
+
const calleeText = content.slice(node.callee.start, node.callee.end);
|
|
4358
|
+
const matchesCallee = calleeText === options.callee || calleeText.endsWith(`.${options.callee}`);
|
|
4359
|
+
if (matchesCallee) {
|
|
4360
|
+
const args = node.arguments || [];
|
|
4361
|
+
if (options.dropIndex !== void 0) {
|
|
4362
|
+
const idx = options.dropIndex;
|
|
4363
|
+
if (idx >= 0 && idx < args.length) {
|
|
4364
|
+
if (args.length === 1) {
|
|
4365
|
+
edits.push({
|
|
4366
|
+
start: args[0].start,
|
|
4367
|
+
end: args[0].end,
|
|
4368
|
+
replacement: ""
|
|
4369
|
+
});
|
|
4370
|
+
} else if (idx < args.length - 1) {
|
|
4371
|
+
edits.push({
|
|
4372
|
+
start: args[idx].start,
|
|
4373
|
+
end: args[idx + 1].start,
|
|
4374
|
+
replacement: ""
|
|
4375
|
+
});
|
|
4376
|
+
} else {
|
|
4377
|
+
edits.push({
|
|
4378
|
+
start: args[idx - 1].end,
|
|
4379
|
+
end: args[idx].end,
|
|
4380
|
+
replacement: ""
|
|
4381
|
+
});
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
if (options.reorder) {
|
|
4386
|
+
const [i, j] = options.reorder;
|
|
4387
|
+
if (i >= 0 && j >= 0 && i < args.length && j < args.length && i !== j) {
|
|
4388
|
+
const argIText = content.slice(args[i].start, args[i].end);
|
|
4389
|
+
const argJText = content.slice(args[j].start, args[j].end);
|
|
4390
|
+
edits.push({
|
|
4391
|
+
start: args[i].start,
|
|
4392
|
+
end: args[i].end,
|
|
4393
|
+
replacement: argJText
|
|
4394
|
+
});
|
|
4395
|
+
edits.push({
|
|
4396
|
+
start: args[j].start,
|
|
4397
|
+
end: args[j].end,
|
|
4398
|
+
replacement: argIText
|
|
4399
|
+
});
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
3696
4402
|
}
|
|
3697
4403
|
}
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
` + res;
|
|
3706
|
-
return res.replace(/mkdirp\.sync\(/g, "mkdirpSync(");
|
|
4404
|
+
for (const key of Object.keys(node)) {
|
|
4405
|
+
if (key === "parent") continue;
|
|
4406
|
+
const child = node[key];
|
|
4407
|
+
if (Array.isArray(child)) {
|
|
4408
|
+
for (const item of child) walk(item);
|
|
4409
|
+
} else if (child && typeof child === "object") {
|
|
4410
|
+
walk(child);
|
|
3707
4411
|
}
|
|
3708
4412
|
}
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
4413
|
+
}
|
|
4414
|
+
walk(parseResult.program);
|
|
4415
|
+
if (edits.length === 0) {
|
|
4416
|
+
return { modified: false, newContent: content };
|
|
4417
|
+
}
|
|
4418
|
+
return { modified: true, newContent: applyEdits(content, edits) };
|
|
3713
4419
|
}
|
|
3714
|
-
function transformFileContent(content, packageName, diagnosticError, filePath) {
|
|
4420
|
+
function transformFileContent(content, packageName, diagnosticError, filePath, argumentTransforms) {
|
|
4421
|
+
let currentContent = content;
|
|
3715
4422
|
let modified = false;
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
4423
|
+
const parseResult = parseSync3(filePath ?? "file.ts", currentContent);
|
|
4424
|
+
if (!parseResult.program) {
|
|
4425
|
+
return { modified: false, newContent: content };
|
|
4426
|
+
}
|
|
4427
|
+
const edits = [];
|
|
4428
|
+
const body = parseResult.program.body || [];
|
|
4429
|
+
let defaultLocalName;
|
|
4430
|
+
let namespaceLocalName;
|
|
4431
|
+
let defaultImportDecl;
|
|
4432
|
+
let subpathImportDecl;
|
|
4433
|
+
let subpathName;
|
|
4434
|
+
for (const statement of body) {
|
|
4435
|
+
if (statement.type === "ImportDeclaration" && statement.source?.value) {
|
|
4436
|
+
const specText = String(statement.source.value);
|
|
4437
|
+
if (specText === packageName) {
|
|
4438
|
+
for (const spec of statement.specifiers || []) {
|
|
4439
|
+
if (spec.type === "ImportDefaultSpecifier") {
|
|
4440
|
+
defaultLocalName = spec.local?.name;
|
|
4441
|
+
defaultImportDecl = statement;
|
|
4442
|
+
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
4443
|
+
namespaceLocalName = spec.local?.name;
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
} else if (specText.startsWith(`${packageName}/`)) {
|
|
4447
|
+
subpathImportDecl = statement;
|
|
4448
|
+
subpathName = specText.slice(packageName.length + 1);
|
|
4449
|
+
for (const spec of statement.specifiers || []) {
|
|
4450
|
+
if (spec.type === "ImportDefaultSpecifier") {
|
|
4451
|
+
defaultLocalName = spec.local?.name;
|
|
4452
|
+
}
|
|
3725
4453
|
}
|
|
3726
4454
|
}
|
|
3727
4455
|
}
|
|
3728
4456
|
}
|
|
3729
|
-
if (
|
|
3730
|
-
const
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
4457
|
+
if (subpathImportDecl && (diagnosticError.includes(`Cannot find module '${packageName}/`) || diagnosticError.includes(`Cannot find module "${packageName}/`))) {
|
|
4458
|
+
const local = defaultLocalName || subpathName || "default";
|
|
4459
|
+
const newImport = `import { ${subpathName} as ${local} } from "${packageName}";`;
|
|
4460
|
+
edits.push({
|
|
4461
|
+
start: subpathImportDecl.start,
|
|
4462
|
+
end: subpathImportDecl.end,
|
|
4463
|
+
replacement: newImport
|
|
4464
|
+
});
|
|
4465
|
+
}
|
|
4466
|
+
if (packageName === "rimraf" && (diagnosticError.includes("sync") || diagnosticError.includes("rimrafSync") || currentContent.includes("rimraf.sync(") || defaultLocalName && currentContent.includes(`${defaultLocalName}.sync(`))) {
|
|
4467
|
+
let walkRimraf2 = function(node) {
|
|
4468
|
+
if (!node || typeof node !== "object") return;
|
|
4469
|
+
if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.object?.type === "Identifier" && node.callee.object.name === targetLocal && node.callee.property?.name === "sync") {
|
|
4470
|
+
edits.push({
|
|
4471
|
+
start: node.callee.start,
|
|
4472
|
+
end: node.callee.end,
|
|
4473
|
+
replacement: "rimrafSync"
|
|
4474
|
+
});
|
|
4475
|
+
}
|
|
4476
|
+
for (const k of Object.keys(node)) {
|
|
4477
|
+
if (k === "parent") continue;
|
|
4478
|
+
const v = node[k];
|
|
4479
|
+
if (Array.isArray(v)) v.forEach(walkRimraf2);
|
|
4480
|
+
else if (v && typeof v === "object") walkRimraf2(v);
|
|
4481
|
+
}
|
|
4482
|
+
};
|
|
4483
|
+
var walkRimraf = walkRimraf2;
|
|
4484
|
+
const targetLocal = defaultLocalName || "rimraf";
|
|
4485
|
+
walkRimraf2(parseResult.program);
|
|
4486
|
+
if (defaultImportDecl) {
|
|
4487
|
+
edits.push({
|
|
4488
|
+
start: defaultImportDecl.start,
|
|
4489
|
+
end: defaultImportDecl.end,
|
|
4490
|
+
replacement: `import { rimrafSync } from "rimraf";`
|
|
4491
|
+
});
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
if (packageName === "uuid" && (diagnosticError.includes("v4") || diagnosticError.includes("uuidv4") || currentContent.includes("uuid.v4(") || defaultLocalName && currentContent.includes(`${defaultLocalName}.v4(`))) {
|
|
4495
|
+
let walkUuid2 = function(node) {
|
|
4496
|
+
if (!node || typeof node !== "object") return;
|
|
4497
|
+
if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.object?.type === "Identifier" && node.callee.object.name === targetLocal && node.callee.property?.name === "v4") {
|
|
4498
|
+
edits.push({
|
|
4499
|
+
start: node.callee.start,
|
|
4500
|
+
end: node.callee.end,
|
|
4501
|
+
replacement: "uuidv4"
|
|
4502
|
+
});
|
|
4503
|
+
}
|
|
4504
|
+
for (const k of Object.keys(node)) {
|
|
4505
|
+
if (k === "parent") continue;
|
|
4506
|
+
const v = node[k];
|
|
4507
|
+
if (Array.isArray(v)) v.forEach(walkUuid2);
|
|
4508
|
+
else if (v && typeof v === "object") walkUuid2(v);
|
|
4509
|
+
}
|
|
4510
|
+
};
|
|
4511
|
+
var walkUuid = walkUuid2;
|
|
4512
|
+
const targetLocal = defaultLocalName || "uuid";
|
|
4513
|
+
walkUuid2(parseResult.program);
|
|
4514
|
+
if (defaultImportDecl) {
|
|
4515
|
+
edits.push({
|
|
4516
|
+
start: defaultImportDecl.start,
|
|
4517
|
+
end: defaultImportDecl.end,
|
|
4518
|
+
replacement: `import { v4 as uuidv4 } from "uuid";`
|
|
4519
|
+
});
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
if (packageName === "glob" && (diagnosticError.includes("sync") || diagnosticError.includes("globSync") || currentContent.includes("glob.sync(") || defaultLocalName && currentContent.includes(`${defaultLocalName}.sync(`))) {
|
|
4523
|
+
let walkGlob2 = function(node) {
|
|
4524
|
+
if (!node || typeof node !== "object") return;
|
|
4525
|
+
if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.object?.type === "Identifier" && node.callee.object.name === targetLocal && node.callee.property?.name === "sync") {
|
|
4526
|
+
edits.push({
|
|
4527
|
+
start: node.callee.start,
|
|
4528
|
+
end: node.callee.end,
|
|
4529
|
+
replacement: "globSync"
|
|
4530
|
+
});
|
|
4531
|
+
}
|
|
4532
|
+
for (const k of Object.keys(node)) {
|
|
4533
|
+
if (k === "parent") continue;
|
|
4534
|
+
const v = node[k];
|
|
4535
|
+
if (Array.isArray(v)) v.forEach(walkGlob2);
|
|
4536
|
+
else if (v && typeof v === "object") walkGlob2(v);
|
|
4537
|
+
}
|
|
4538
|
+
};
|
|
4539
|
+
var walkGlob = walkGlob2;
|
|
4540
|
+
const targetLocal = defaultLocalName || "glob";
|
|
4541
|
+
walkGlob2(parseResult.program);
|
|
4542
|
+
if (defaultImportDecl) {
|
|
4543
|
+
edits.push({
|
|
4544
|
+
start: defaultImportDecl.start,
|
|
4545
|
+
end: defaultImportDecl.end,
|
|
4546
|
+
replacement: `import { globSync } from "glob";`
|
|
4547
|
+
});
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
if (packageName === "mkdirp" && (diagnosticError.includes("sync") || diagnosticError.includes("mkdirpSync") || currentContent.includes("mkdirp.sync(") || defaultLocalName && currentContent.includes(`${defaultLocalName}.sync(`))) {
|
|
4551
|
+
let walkMkdirp2 = function(node) {
|
|
4552
|
+
if (!node || typeof node !== "object") return;
|
|
4553
|
+
if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.object?.type === "Identifier" && node.callee.object.name === targetLocal && node.callee.property?.name === "sync") {
|
|
4554
|
+
edits.push({
|
|
4555
|
+
start: node.callee.start,
|
|
4556
|
+
end: node.callee.end,
|
|
4557
|
+
replacement: "mkdirpSync"
|
|
4558
|
+
});
|
|
4559
|
+
}
|
|
4560
|
+
for (const k of Object.keys(node)) {
|
|
4561
|
+
if (k === "parent") continue;
|
|
4562
|
+
const v = node[k];
|
|
4563
|
+
if (Array.isArray(v)) v.forEach(walkMkdirp2);
|
|
4564
|
+
else if (v && typeof v === "object") walkMkdirp2(v);
|
|
4565
|
+
}
|
|
4566
|
+
};
|
|
4567
|
+
var walkMkdirp = walkMkdirp2;
|
|
4568
|
+
const targetLocal = defaultLocalName || "mkdirp";
|
|
4569
|
+
walkMkdirp2(parseResult.program);
|
|
4570
|
+
if (defaultImportDecl) {
|
|
4571
|
+
edits.push({
|
|
4572
|
+
start: defaultImportDecl.start,
|
|
4573
|
+
end: defaultImportDecl.end,
|
|
4574
|
+
replacement: `import { mkdirpSync } from "mkdirp";`
|
|
4575
|
+
});
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
if (defaultImportDecl && defaultLocalName && (diagnosticError.includes("no default export") || diagnosticError.includes("has no exported member 'default'") || diagnosticError.includes("has no exported member default") || diagnosticError.includes("TS2614") || diagnosticError.includes("TS1192"))) {
|
|
4579
|
+
edits.push({
|
|
4580
|
+
start: defaultImportDecl.start,
|
|
4581
|
+
end: defaultImportDecl.end,
|
|
4582
|
+
replacement: `import * as ${defaultLocalName} from "${packageName}";`
|
|
4583
|
+
});
|
|
3741
4584
|
}
|
|
3742
|
-
if (diagnosticError.includes("can only be used with a default import") || diagnosticError.includes("only be used with a default import") || diagnosticError.includes("TS1259")) {
|
|
3743
|
-
const
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
);
|
|
3752
|
-
modified = true;
|
|
4585
|
+
if (namespaceLocalName && (diagnosticError.includes("can only be used with a default import") || diagnosticError.includes("only be used with a default import") || diagnosticError.includes("TS1259"))) {
|
|
4586
|
+
for (const statement of body) {
|
|
4587
|
+
if (statement.type === "ImportDeclaration" && String(statement.source?.value) === packageName) {
|
|
4588
|
+
edits.push({
|
|
4589
|
+
start: statement.start,
|
|
4590
|
+
end: statement.end,
|
|
4591
|
+
replacement: `import ${namespaceLocalName} from "${packageName}";`
|
|
4592
|
+
});
|
|
4593
|
+
}
|
|
3753
4594
|
}
|
|
3754
4595
|
}
|
|
3755
4596
|
const diagnostics = parseCompilerDiagnostics(diagnosticError);
|
|
3756
4597
|
for (const diag of diagnostics) {
|
|
3757
4598
|
if (diag.missingMember && diag.suggestedSymbol) {
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
4599
|
+
let getLine2 = function(offset) {
|
|
4600
|
+
let low = 0;
|
|
4601
|
+
let high = lineStarts.length - 1;
|
|
4602
|
+
while (low <= high) {
|
|
4603
|
+
const mid = Math.floor((low + high) / 2);
|
|
4604
|
+
if (lineStarts[mid] <= offset) {
|
|
4605
|
+
if (mid === lineStarts.length - 1 || lineStarts[mid + 1] > offset) {
|
|
4606
|
+
return mid + 1;
|
|
4607
|
+
}
|
|
4608
|
+
low = mid + 1;
|
|
4609
|
+
} else {
|
|
4610
|
+
high = mid - 1;
|
|
3770
4611
|
}
|
|
3771
4612
|
}
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
if (
|
|
3775
|
-
|
|
3776
|
-
|
|
4613
|
+
return 1;
|
|
4614
|
+
}, walkDiagnostics2 = function(node) {
|
|
4615
|
+
if (!node || typeof node !== "object") return;
|
|
4616
|
+
if (node.type === "MemberExpression" && node.property?.name === missing) {
|
|
4617
|
+
const line = getLine2(node.start);
|
|
4618
|
+
const lineMatches = !diag.line || diag.line === line;
|
|
4619
|
+
if (lineMatches) {
|
|
4620
|
+
edits.push({
|
|
4621
|
+
start: node.property.start,
|
|
4622
|
+
end: node.property.end,
|
|
4623
|
+
replacement: suggested
|
|
4624
|
+
});
|
|
4625
|
+
}
|
|
3777
4626
|
}
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
);
|
|
3787
|
-
|
|
4627
|
+
if (node.type === "ImportSpecifier" && (node.imported?.name === missing || node.local?.name === missing)) {
|
|
4628
|
+
edits.push({
|
|
4629
|
+
start: node.local.start,
|
|
4630
|
+
end: node.local.end,
|
|
4631
|
+
replacement: suggested
|
|
4632
|
+
});
|
|
4633
|
+
}
|
|
4634
|
+
for (const k of Object.keys(node)) {
|
|
4635
|
+
if (k === "parent") continue;
|
|
4636
|
+
const v = node[k];
|
|
4637
|
+
if (Array.isArray(v)) v.forEach(walkDiagnostics2);
|
|
4638
|
+
else if (v && typeof v === "object") walkDiagnostics2(v);
|
|
3788
4639
|
}
|
|
4640
|
+
};
|
|
4641
|
+
var getLine = getLine2, walkDiagnostics = walkDiagnostics2;
|
|
4642
|
+
const missing = diag.missingMember;
|
|
4643
|
+
const suggested = diag.suggestedSymbol;
|
|
4644
|
+
const lineStarts = [0];
|
|
4645
|
+
for (let i = 0; i < currentContent.length; i++) {
|
|
4646
|
+
if (currentContent[i] === "\n") lineStarts.push(i + 1);
|
|
3789
4647
|
}
|
|
4648
|
+
walkDiagnostics2(parseResult.program);
|
|
3790
4649
|
}
|
|
3791
4650
|
}
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
if (
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
const indent = fileLines[targetLineIdx]?.match(/^\s*/)?.[0] || "";
|
|
3805
|
-
fileLines.splice(
|
|
3806
|
-
targetLineIdx,
|
|
3807
|
-
0,
|
|
3808
|
-
`${indent}// @ts-expect-error [autoremediator-refactor]: breaking change in ${packageName}`
|
|
3809
|
-
);
|
|
3810
|
-
newContent = fileLines.join("\n");
|
|
3811
|
-
modified = true;
|
|
3812
|
-
}
|
|
4651
|
+
if (edits.length > 0) {
|
|
4652
|
+
currentContent = applyEdits(currentContent, edits);
|
|
4653
|
+
modified = true;
|
|
4654
|
+
}
|
|
4655
|
+
if (argumentTransforms) {
|
|
4656
|
+
const argResult = transformCallArguments(currentContent, {
|
|
4657
|
+
...argumentTransforms,
|
|
4658
|
+
filePath
|
|
4659
|
+
});
|
|
4660
|
+
if (argResult.modified) {
|
|
4661
|
+
currentContent = argResult.newContent;
|
|
4662
|
+
modified = true;
|
|
3813
4663
|
}
|
|
3814
4664
|
}
|
|
3815
|
-
return { modified, newContent };
|
|
4665
|
+
return { modified, newContent: currentContent };
|
|
3816
4666
|
}
|
|
3817
4667
|
async function refactorConsumerCallSites(params) {
|
|
3818
|
-
const { packageName, diagnosticError, callSites } = params;
|
|
4668
|
+
const { packageName, diagnosticError, callSites, argumentTransforms } = params;
|
|
3819
4669
|
if (callSites.length === 0) {
|
|
3820
4670
|
return {
|
|
3821
4671
|
applied: false,
|
|
@@ -3828,16 +4678,17 @@ async function refactorConsumerCallSites(params) {
|
|
|
3828
4678
|
const refactoredFiles = [];
|
|
3829
4679
|
try {
|
|
3830
4680
|
for (const site of callSites) {
|
|
3831
|
-
const originalContent = site.sourceContent ||
|
|
4681
|
+
const originalContent = site.sourceContent || readFileSync13(site.filePath, "utf8");
|
|
3832
4682
|
snapshots[site.filePath] = originalContent;
|
|
3833
4683
|
const { modified, newContent } = transformFileContent(
|
|
3834
4684
|
originalContent,
|
|
3835
4685
|
packageName,
|
|
3836
4686
|
diagnosticError,
|
|
3837
|
-
site.filePath
|
|
4687
|
+
site.filePath,
|
|
4688
|
+
argumentTransforms
|
|
3838
4689
|
);
|
|
3839
4690
|
if (modified && newContent !== originalContent) {
|
|
3840
|
-
|
|
4691
|
+
writeFileSync5(site.filePath, newContent, "utf8");
|
|
3841
4692
|
refactoredFiles.push(site.filePath);
|
|
3842
4693
|
}
|
|
3843
4694
|
}
|
|
@@ -3860,7 +4711,7 @@ async function refactorConsumerCallSites(params) {
|
|
|
3860
4711
|
return {
|
|
3861
4712
|
applied: false,
|
|
3862
4713
|
refactoredFiles: [],
|
|
3863
|
-
snapshots
|
|
4714
|
+
snapshots,
|
|
3864
4715
|
error: `Failed to apply breaking change refactoring: ${message}`
|
|
3865
4716
|
};
|
|
3866
4717
|
}
|
|
@@ -3883,7 +4734,13 @@ var applyVersionBumpTool = defineTool({
|
|
|
3883
4734
|
installPreferOffline: z4.boolean().optional(),
|
|
3884
4735
|
enforceFrozenLockfile: z4.boolean().optional(),
|
|
3885
4736
|
workspace: z4.string().optional(),
|
|
3886
|
-
breakingChangeRefactor: z4.boolean().optional().describe(
|
|
4737
|
+
breakingChangeRefactor: z4.boolean().optional().describe(
|
|
4738
|
+
"If true, attempt agentic call-site refactoring when validation fails after major upgrade"
|
|
4739
|
+
),
|
|
4740
|
+
reproductionTest: z4.string().optional().describe("Optional reproduction test script verifying fix"),
|
|
4741
|
+
proofOfFix: z4.union([z4.boolean(), z4.string()]).optional().describe("If true or script path/content, enforce dual-stage exploit reproduction"),
|
|
4742
|
+
requireProofOfFix: z4.boolean().optional().describe("If true, fail and rollback version bump if proof-of-fix verification fails"),
|
|
4743
|
+
patchesDir: z4.string().optional().default("./patches").describe("Directory containing patch/reproduction artifacts")
|
|
3887
4744
|
}),
|
|
3888
4745
|
execute: async ({
|
|
3889
4746
|
cwd,
|
|
@@ -3899,10 +4756,14 @@ var applyVersionBumpTool = defineTool({
|
|
|
3899
4756
|
installPreferOffline,
|
|
3900
4757
|
enforceFrozenLockfile,
|
|
3901
4758
|
workspace,
|
|
3902
|
-
breakingChangeRefactor
|
|
4759
|
+
breakingChangeRefactor,
|
|
4760
|
+
reproductionTest,
|
|
4761
|
+
proofOfFix,
|
|
4762
|
+
requireProofOfFix,
|
|
4763
|
+
patchesDir
|
|
3903
4764
|
}) => {
|
|
3904
4765
|
const pm = packageManager ?? detectPackageManager(cwd);
|
|
3905
|
-
let pkgPath =
|
|
4766
|
+
let pkgPath = join16(cwd, "package.json");
|
|
3906
4767
|
const loadedPolicy = loadPolicy(cwd, policy);
|
|
3907
4768
|
const commandConstraints = {
|
|
3908
4769
|
...loadedPolicy.constraints,
|
|
@@ -3924,7 +4785,7 @@ var applyVersionBumpTool = defineTool({
|
|
|
3924
4785
|
message: `Policy blocked changes for package "${packageName}".`
|
|
3925
4786
|
};
|
|
3926
4787
|
}
|
|
3927
|
-
const isMajorBump =
|
|
4788
|
+
const isMajorBump = semver4.valid(fromVersion) && semver4.valid(toVersion) && semver4.major(toVersion) > semver4.major(fromVersion);
|
|
3928
4789
|
if (isMajorBump && !loadedPolicy.allowMajorBumps) {
|
|
3929
4790
|
return {
|
|
3930
4791
|
packageName,
|
|
@@ -3937,10 +4798,10 @@ var applyVersionBumpTool = defineTool({
|
|
|
3937
4798
|
message: `Policy blocked major bump for "${packageName}" (${fromVersion} -> ${toVersion}).`
|
|
3938
4799
|
};
|
|
3939
4800
|
}
|
|
3940
|
-
pkgPath =
|
|
4801
|
+
pkgPath = join16(cwd, "package.json");
|
|
3941
4802
|
let pkgJson;
|
|
3942
4803
|
try {
|
|
3943
|
-
pkgJson = JSON.parse(
|
|
4804
|
+
pkgJson = JSON.parse(readFileSync14(pkgPath, "utf8"));
|
|
3944
4805
|
} catch {
|
|
3945
4806
|
return {
|
|
3946
4807
|
packageName,
|
|
@@ -4000,8 +4861,57 @@ var applyVersionBumpTool = defineTool({
|
|
|
4000
4861
|
};
|
|
4001
4862
|
}
|
|
4002
4863
|
return withRepoLock(cwd, async () => {
|
|
4864
|
+
const prepared = await prepareReproductionTestFile({
|
|
4865
|
+
cwd,
|
|
4866
|
+
packageName,
|
|
4867
|
+
vulnerableVersion: fromVersion,
|
|
4868
|
+
patchesDir,
|
|
4869
|
+
proofOfFix,
|
|
4870
|
+
reproductionTest
|
|
4871
|
+
});
|
|
4872
|
+
const reproTestPath = prepared.testFilePath;
|
|
4873
|
+
const isProofActive = Boolean(reproTestPath || proofOfFix || requireProofOfFix);
|
|
4874
|
+
let negativePhaseResult;
|
|
4875
|
+
if (isProofActive) {
|
|
4876
|
+
if (!reproTestPath) {
|
|
4877
|
+
if (requireProofOfFix) {
|
|
4878
|
+
return {
|
|
4879
|
+
packageName,
|
|
4880
|
+
strategy: "none",
|
|
4881
|
+
fromVersion,
|
|
4882
|
+
toVersion,
|
|
4883
|
+
applied: false,
|
|
4884
|
+
dryRun,
|
|
4885
|
+
unresolvedReason: "policy-blocked",
|
|
4886
|
+
message: "Proof-of-fix verification required, but no reproduction test or script was supplied."
|
|
4887
|
+
};
|
|
4888
|
+
}
|
|
4889
|
+
} else {
|
|
4890
|
+
negativePhaseResult = await runProofOfFixPhase({
|
|
4891
|
+
stage: "negative",
|
|
4892
|
+
testFilePath: reproTestPath,
|
|
4893
|
+
cwd
|
|
4894
|
+
});
|
|
4895
|
+
if (!negativePhaseResult.passed) {
|
|
4896
|
+
if (prepared.isCreated && reproTestPath) {
|
|
4897
|
+
await rm3(reproTestPath, { force: true }).catch(() => {
|
|
4898
|
+
});
|
|
4899
|
+
}
|
|
4900
|
+
return {
|
|
4901
|
+
packageName,
|
|
4902
|
+
strategy: "version-bump",
|
|
4903
|
+
fromVersion,
|
|
4904
|
+
toVersion,
|
|
4905
|
+
applied: false,
|
|
4906
|
+
dryRun: false,
|
|
4907
|
+
unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
|
|
4908
|
+
message: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`
|
|
4909
|
+
};
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4003
4913
|
pkgJson[depField][packageName] = newRange;
|
|
4004
|
-
|
|
4914
|
+
writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
4005
4915
|
try {
|
|
4006
4916
|
const [installCmd, ...installArgs] = effectiveInstallCommand;
|
|
4007
4917
|
await safeExeca(installCmd, installArgs, {
|
|
@@ -4010,7 +4920,7 @@ var applyVersionBumpTool = defineTool({
|
|
|
4010
4920
|
});
|
|
4011
4921
|
} catch (err) {
|
|
4012
4922
|
pkgJson[depField][packageName] = currentRange;
|
|
4013
|
-
|
|
4923
|
+
writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
4014
4924
|
const message = err instanceof Error ? err.message : String(err);
|
|
4015
4925
|
return {
|
|
4016
4926
|
packageName,
|
|
@@ -4082,7 +4992,7 @@ var applyVersionBumpTool = defineTool({
|
|
|
4082
4992
|
}
|
|
4083
4993
|
if (!validationResult.passed) {
|
|
4084
4994
|
pkgJson[depField][packageName] = currentRange;
|
|
4085
|
-
|
|
4995
|
+
writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
4086
4996
|
try {
|
|
4087
4997
|
const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
|
|
4088
4998
|
await safeExeca(rollbackCmd, rollbackArgs, {
|
|
@@ -4104,6 +5014,49 @@ var applyVersionBumpTool = defineTool({
|
|
|
4104
5014
|
message: `${prefix2} after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${failureMessage}`
|
|
4105
5015
|
};
|
|
4106
5016
|
}
|
|
5017
|
+
let proofOfFixVerification;
|
|
5018
|
+
if (reproTestPath && negativePhaseResult?.passed) {
|
|
5019
|
+
const positivePhaseResult = await runProofOfFixPhase({
|
|
5020
|
+
stage: "positive",
|
|
5021
|
+
testFilePath: reproTestPath,
|
|
5022
|
+
cwd
|
|
5023
|
+
});
|
|
5024
|
+
if (!positivePhaseResult.passed) {
|
|
5025
|
+
pkgJson[depField][packageName] = currentRange;
|
|
5026
|
+
writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
5027
|
+
try {
|
|
5028
|
+
const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
|
|
5029
|
+
await safeExeca(rollbackCmd, rollbackArgs, {
|
|
5030
|
+
cwd,
|
|
5031
|
+
stdio: "pipe"
|
|
5032
|
+
});
|
|
5033
|
+
} catch {
|
|
5034
|
+
}
|
|
5035
|
+
if (prepared.isCreated && reproTestPath) {
|
|
5036
|
+
await rm3(reproTestPath, { force: true }).catch(() => {
|
|
5037
|
+
});
|
|
5038
|
+
}
|
|
5039
|
+
return {
|
|
5040
|
+
packageName,
|
|
5041
|
+
strategy: "version-bump",
|
|
5042
|
+
fromVersion,
|
|
5043
|
+
toVersion,
|
|
5044
|
+
applied: false,
|
|
5045
|
+
dryRun: false,
|
|
5046
|
+
unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
|
|
5047
|
+
message: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`
|
|
5048
|
+
};
|
|
5049
|
+
}
|
|
5050
|
+
proofOfFixVerification = assembleProofOfFixVerification({
|
|
5051
|
+
reproductionTestPath: reproTestPath,
|
|
5052
|
+
negativePhase: negativePhaseResult,
|
|
5053
|
+
positivePhase: positivePhaseResult,
|
|
5054
|
+
regressionPhase: {
|
|
5055
|
+
executed: runTests,
|
|
5056
|
+
passed: validationResult.passed
|
|
5057
|
+
}
|
|
5058
|
+
});
|
|
5059
|
+
}
|
|
4107
5060
|
if (refactoredFilesList && refactoredFilesList.length > 0) {
|
|
4108
5061
|
return {
|
|
4109
5062
|
packageName,
|
|
@@ -4113,6 +5066,7 @@ var applyVersionBumpTool = defineTool({
|
|
|
4113
5066
|
applied: true,
|
|
4114
5067
|
dryRun: false,
|
|
4115
5068
|
refactoredFiles: refactoredFilesList,
|
|
5069
|
+
proofOfFix: proofOfFixVerification,
|
|
4116
5070
|
message: `Upgraded "${packageName}" from ${fromVersion} to ${toVersion} and refactored ${refactoredFilesList.length} call-site file(s) across breaking API changes.`
|
|
4117
5071
|
};
|
|
4118
5072
|
}
|
|
@@ -4136,6 +5090,7 @@ var applyVersionBumpTool = defineTool({
|
|
|
4136
5090
|
toVersion,
|
|
4137
5091
|
applied: true,
|
|
4138
5092
|
dryRun: false,
|
|
5093
|
+
proofOfFix: proofOfFixVerification,
|
|
4139
5094
|
message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion} in ${depField}, then ran ${effectiveInstallCommand.join(" ")}${runTests ? ` and passed ${effectiveTestCommand.join(" ")}` : ""}.${dedupeNote}`
|
|
4140
5095
|
};
|
|
4141
5096
|
});
|
|
@@ -4144,117 +5099,10 @@ var applyVersionBumpTool = defineTool({
|
|
|
4144
5099
|
|
|
4145
5100
|
// src/remediation/tools/apply-package-override/index.ts
|
|
4146
5101
|
import { z as z5 } from "zod";
|
|
4147
|
-
import { join as
|
|
4148
|
-
import { existsSync as
|
|
4149
|
-
import
|
|
4150
|
-
|
|
4151
|
-
// src/remediation/tools/apply-package-override/helpers.ts
|
|
4152
|
-
async function collectDependencyTrace(cwd, pm, packageName, constraints) {
|
|
4153
|
-
try {
|
|
4154
|
-
const whyCommand = resolveWhyCommand(pm, packageName, constraints);
|
|
4155
|
-
if (whyCommand.length === 0) return void 0;
|
|
4156
|
-
const [whyCmd, ...whyArgs] = whyCommand;
|
|
4157
|
-
const result = await safeExeca(whyCmd, whyArgs, {
|
|
4158
|
-
cwd,
|
|
4159
|
-
stdio: "pipe",
|
|
4160
|
-
reject: false
|
|
4161
|
-
});
|
|
4162
|
-
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
4163
|
-
if (output.length === 0) return void 0;
|
|
4164
|
-
return output.slice(0, 3).join(" | ");
|
|
4165
|
-
} catch {
|
|
4166
|
-
return void 0;
|
|
4167
|
-
}
|
|
4168
|
-
}
|
|
4169
|
-
function describeOverrideField(packageManager) {
|
|
4170
|
-
if (packageManager === "npm" || packageManager === "bun") return "overrides";
|
|
4171
|
-
if (packageManager === "pnpm") return "pnpm.overrides";
|
|
4172
|
-
if (packageManager === "deno") return "overrides";
|
|
4173
|
-
return "resolutions";
|
|
4174
|
-
}
|
|
4175
|
-
function getOverrideValue(pkgJson, packageManager, packageName) {
|
|
4176
|
-
if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno") {
|
|
4177
|
-
return pkgJson.overrides?.[packageName];
|
|
4178
|
-
}
|
|
4179
|
-
if (packageManager === "pnpm") return pkgJson.pnpm?.overrides?.[packageName];
|
|
4180
|
-
return pkgJson.resolutions?.[packageName];
|
|
4181
|
-
}
|
|
4182
|
-
function setOverrideValue(pkgJson, packageManager, packageName, version) {
|
|
4183
|
-
if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno") {
|
|
4184
|
-
pkgJson.overrides = { ...pkgJson.overrides, [packageName]: version };
|
|
4185
|
-
return;
|
|
4186
|
-
}
|
|
4187
|
-
if (packageManager === "pnpm") {
|
|
4188
|
-
pkgJson.pnpm = {
|
|
4189
|
-
...pkgJson.pnpm,
|
|
4190
|
-
overrides: {
|
|
4191
|
-
...pkgJson.pnpm?.overrides,
|
|
4192
|
-
[packageName]: version
|
|
4193
|
-
}
|
|
4194
|
-
};
|
|
4195
|
-
return;
|
|
4196
|
-
}
|
|
4197
|
-
pkgJson.resolutions = { ...pkgJson.resolutions, [packageName]: version };
|
|
4198
|
-
}
|
|
4199
|
-
function restoreOverrideValue(pkgJson, packageManager, packageName, previousValue) {
|
|
4200
|
-
if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno") {
|
|
4201
|
-
pkgJson.overrides = restoreRecord(pkgJson.overrides, packageName, previousValue);
|
|
4202
|
-
return;
|
|
4203
|
-
}
|
|
4204
|
-
if (packageManager === "pnpm") {
|
|
4205
|
-
pkgJson.pnpm = {
|
|
4206
|
-
...pkgJson.pnpm,
|
|
4207
|
-
overrides: restoreRecord(pkgJson.pnpm?.overrides, packageName, previousValue)
|
|
4208
|
-
};
|
|
4209
|
-
if (!pkgJson.pnpm.overrides) {
|
|
4210
|
-
delete pkgJson.pnpm.overrides;
|
|
4211
|
-
}
|
|
4212
|
-
if (Object.keys(pkgJson.pnpm).length === 0) {
|
|
4213
|
-
delete pkgJson.pnpm;
|
|
4214
|
-
}
|
|
4215
|
-
return;
|
|
4216
|
-
}
|
|
4217
|
-
pkgJson.resolutions = restoreRecord(pkgJson.resolutions, packageName, previousValue);
|
|
4218
|
-
}
|
|
4219
|
-
function getDenoJsonImportValue(denoJson, packageName) {
|
|
4220
|
-
const imports = denoJson.imports ?? {};
|
|
4221
|
-
if (imports[packageName] !== void 0) {
|
|
4222
|
-
return { key: packageName, value: imports[packageName] };
|
|
4223
|
-
}
|
|
4224
|
-
const npmKey = `npm:${packageName}`;
|
|
4225
|
-
if (imports[npmKey] !== void 0) {
|
|
4226
|
-
return { key: npmKey, value: imports[npmKey] };
|
|
4227
|
-
}
|
|
4228
|
-
return void 0;
|
|
4229
|
-
}
|
|
4230
|
-
function setDenoJsonImportValue(denoJson, packageName, version) {
|
|
4231
|
-
const existing = getDenoJsonImportValue(denoJson, packageName);
|
|
4232
|
-
const key = existing?.key ?? `npm:${packageName}`;
|
|
4233
|
-
denoJson.imports = { ...denoJson.imports, [key]: `npm:${packageName}@${version}` };
|
|
4234
|
-
}
|
|
4235
|
-
function restoreDenoJsonImportValue(denoJson, packageName, previousEntry) {
|
|
4236
|
-
const existing = getDenoJsonImportValue(denoJson, packageName);
|
|
4237
|
-
if (!existing) return;
|
|
4238
|
-
const imports = { ...denoJson.imports };
|
|
4239
|
-
if (previousEntry === void 0) {
|
|
4240
|
-
delete imports[existing.key];
|
|
4241
|
-
} else {
|
|
4242
|
-
delete imports[existing.key];
|
|
4243
|
-
imports[previousEntry.key] = previousEntry.value;
|
|
4244
|
-
}
|
|
4245
|
-
denoJson.imports = Object.keys(imports).length > 0 ? imports : void 0;
|
|
4246
|
-
}
|
|
4247
|
-
function restoreRecord(record, key, previousValue) {
|
|
4248
|
-
const nextRecord = { ...record };
|
|
4249
|
-
if (previousValue === void 0) {
|
|
4250
|
-
delete nextRecord[key];
|
|
4251
|
-
} else {
|
|
4252
|
-
nextRecord[key] = previousValue;
|
|
4253
|
-
}
|
|
4254
|
-
return Object.keys(nextRecord).length > 0 ? nextRecord : void 0;
|
|
4255
|
-
}
|
|
4256
|
-
|
|
4257
|
-
// src/remediation/tools/apply-package-override/index.ts
|
|
5102
|
+
import { join as join17 } from "path";
|
|
5103
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
|
|
5104
|
+
import { rm as rm4 } from "fs/promises";
|
|
5105
|
+
import semver5 from "semver";
|
|
4258
5106
|
var applyPackageOverrideTool = defineTool({
|
|
4259
5107
|
description: "Apply a package-manager-native package.json override for a vulnerable transitive dependency and reinstall. Uses npm overrides, pnpm.overrides, yarn resolutions, bun overrides, or deno.json imports.",
|
|
4260
5108
|
parameters: z5.object({
|
|
@@ -4270,7 +5118,11 @@ var applyPackageOverrideTool = defineTool({
|
|
|
4270
5118
|
installMode: z5.enum(["standard", "prefer-offline", "deterministic"]).optional(),
|
|
4271
5119
|
installPreferOffline: z5.boolean().optional(),
|
|
4272
5120
|
enforceFrozenLockfile: z5.boolean().optional(),
|
|
4273
|
-
workspace: z5.string().optional()
|
|
5121
|
+
workspace: z5.string().optional(),
|
|
5122
|
+
reproductionTest: z5.string().optional().describe("Optional reproduction test script verifying fix"),
|
|
5123
|
+
proofOfFix: z5.union([z5.boolean(), z5.string()]).optional().describe("If true or script path/content, enforce dual-stage exploit reproduction"),
|
|
5124
|
+
requireProofOfFix: z5.boolean().optional().describe("If true, fail and rollback override if proof-of-fix verification fails"),
|
|
5125
|
+
patchesDir: z5.string().optional().default("./patches").describe("Directory containing patch/reproduction artifacts")
|
|
4274
5126
|
}),
|
|
4275
5127
|
execute: async ({
|
|
4276
5128
|
cwd,
|
|
@@ -4285,12 +5137,16 @@ var applyPackageOverrideTool = defineTool({
|
|
|
4285
5137
|
installMode,
|
|
4286
5138
|
installPreferOffline,
|
|
4287
5139
|
enforceFrozenLockfile,
|
|
4288
|
-
workspace
|
|
5140
|
+
workspace,
|
|
5141
|
+
reproductionTest,
|
|
5142
|
+
proofOfFix,
|
|
5143
|
+
requireProofOfFix,
|
|
5144
|
+
patchesDir
|
|
4289
5145
|
}) => {
|
|
4290
5146
|
const pm = packageManager ?? detectPackageManager(cwd);
|
|
4291
|
-
const pkgPath =
|
|
4292
|
-
const denoJsonPath =
|
|
4293
|
-
const isDenoNative = pm === "deno" && !
|
|
5147
|
+
const pkgPath = join17(cwd, "package.json");
|
|
5148
|
+
const denoJsonPath = join17(cwd, "deno.json");
|
|
5149
|
+
const isDenoNative = pm === "deno" && !existsSync13(pkgPath);
|
|
4294
5150
|
const loadedPolicy = loadPolicy(cwd, policy);
|
|
4295
5151
|
const commandConstraints = {
|
|
4296
5152
|
...loadedPolicy.constraints,
|
|
@@ -4316,7 +5172,7 @@ var applyPackageOverrideTool = defineTool({
|
|
|
4316
5172
|
message: `Policy blocked changes for package "${packageName}".`
|
|
4317
5173
|
};
|
|
4318
5174
|
}
|
|
4319
|
-
const isMajorBump =
|
|
5175
|
+
const isMajorBump = semver5.valid(fromVersion) && semver5.valid(toVersion) && semver5.major(toVersion) > semver5.major(fromVersion);
|
|
4320
5176
|
if (isMajorBump && !loadedPolicy.allowMajorBumps) {
|
|
4321
5177
|
return {
|
|
4322
5178
|
packageName,
|
|
@@ -4332,7 +5188,7 @@ var applyPackageOverrideTool = defineTool({
|
|
|
4332
5188
|
if (isDenoNative) {
|
|
4333
5189
|
let denoJson;
|
|
4334
5190
|
try {
|
|
4335
|
-
denoJson = JSON.parse(
|
|
5191
|
+
denoJson = JSON.parse(readFileSync15(denoJsonPath, "utf8"));
|
|
4336
5192
|
} catch {
|
|
4337
5193
|
return {
|
|
4338
5194
|
packageName,
|
|
@@ -4377,14 +5233,63 @@ var applyPackageOverrideTool = defineTool({
|
|
|
4377
5233
|
};
|
|
4378
5234
|
}
|
|
4379
5235
|
return withRepoLock(cwd, async () => {
|
|
5236
|
+
const prepared = await prepareReproductionTestFile({
|
|
5237
|
+
cwd,
|
|
5238
|
+
packageName,
|
|
5239
|
+
vulnerableVersion: fromVersion,
|
|
5240
|
+
patchesDir,
|
|
5241
|
+
proofOfFix,
|
|
5242
|
+
reproductionTest
|
|
5243
|
+
});
|
|
5244
|
+
const reproTestPath = prepared.testFilePath;
|
|
5245
|
+
const isProofActive = Boolean(reproTestPath || proofOfFix || requireProofOfFix);
|
|
5246
|
+
let negativePhaseResult;
|
|
5247
|
+
if (isProofActive) {
|
|
5248
|
+
if (!reproTestPath) {
|
|
5249
|
+
if (requireProofOfFix) {
|
|
5250
|
+
return {
|
|
5251
|
+
packageName,
|
|
5252
|
+
strategy: "none",
|
|
5253
|
+
fromVersion,
|
|
5254
|
+
toVersion,
|
|
5255
|
+
applied: false,
|
|
5256
|
+
dryRun,
|
|
5257
|
+
unresolvedReason: "policy-blocked",
|
|
5258
|
+
message: "Proof-of-fix verification required, but no reproduction test or script was supplied."
|
|
5259
|
+
};
|
|
5260
|
+
}
|
|
5261
|
+
} else {
|
|
5262
|
+
negativePhaseResult = await runProofOfFixPhase({
|
|
5263
|
+
stage: "negative",
|
|
5264
|
+
testFilePath: reproTestPath,
|
|
5265
|
+
cwd
|
|
5266
|
+
});
|
|
5267
|
+
if (!negativePhaseResult.passed) {
|
|
5268
|
+
if (prepared.isCreated && reproTestPath) {
|
|
5269
|
+
await rm4(reproTestPath, { force: true }).catch(() => {
|
|
5270
|
+
});
|
|
5271
|
+
}
|
|
5272
|
+
return {
|
|
5273
|
+
packageName,
|
|
5274
|
+
strategy: "override",
|
|
5275
|
+
fromVersion,
|
|
5276
|
+
toVersion,
|
|
5277
|
+
applied: false,
|
|
5278
|
+
dryRun: false,
|
|
5279
|
+
unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
|
|
5280
|
+
message: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`
|
|
5281
|
+
};
|
|
5282
|
+
}
|
|
5283
|
+
}
|
|
5284
|
+
}
|
|
4380
5285
|
setDenoJsonImportValue(denoJson, overrideSelector, toVersion);
|
|
4381
|
-
|
|
5286
|
+
writeFileSync7(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
|
|
4382
5287
|
try {
|
|
4383
5288
|
const [installCmd, ...installArgs] = installCommand;
|
|
4384
5289
|
await safeExeca(installCmd, installArgs, { cwd, stdio: "pipe" });
|
|
4385
5290
|
} catch (err) {
|
|
4386
5291
|
restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
|
|
4387
|
-
|
|
5292
|
+
writeFileSync7(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
|
|
4388
5293
|
const message = err instanceof Error ? err.message : String(err);
|
|
4389
5294
|
return {
|
|
4390
5295
|
packageName,
|
|
@@ -4403,7 +5308,7 @@ var applyPackageOverrideTool = defineTool({
|
|
|
4403
5308
|
await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
|
|
4404
5309
|
} catch (err) {
|
|
4405
5310
|
restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
|
|
4406
|
-
|
|
5311
|
+
writeFileSync7(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
|
|
4407
5312
|
try {
|
|
4408
5313
|
const [rollbackCmd, ...rollbackArgs] = installCommand;
|
|
4409
5314
|
await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
|
|
@@ -4422,6 +5327,46 @@ var applyPackageOverrideTool = defineTool({
|
|
|
4422
5327
|
};
|
|
4423
5328
|
}
|
|
4424
5329
|
}
|
|
5330
|
+
let proofOfFixVerification;
|
|
5331
|
+
if (reproTestPath && negativePhaseResult?.passed) {
|
|
5332
|
+
const positivePhaseResult = await runProofOfFixPhase({
|
|
5333
|
+
stage: "positive",
|
|
5334
|
+
testFilePath: reproTestPath,
|
|
5335
|
+
cwd
|
|
5336
|
+
});
|
|
5337
|
+
if (!positivePhaseResult.passed) {
|
|
5338
|
+
restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
|
|
5339
|
+
writeFileSync7(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
|
|
5340
|
+
try {
|
|
5341
|
+
const [rollbackCmd, ...rollbackArgs] = installCommand;
|
|
5342
|
+
await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
|
|
5343
|
+
} catch {
|
|
5344
|
+
}
|
|
5345
|
+
if (prepared.isCreated && reproTestPath) {
|
|
5346
|
+
await rm4(reproTestPath, { force: true }).catch(() => {
|
|
5347
|
+
});
|
|
5348
|
+
}
|
|
5349
|
+
return {
|
|
5350
|
+
packageName,
|
|
5351
|
+
strategy: "override",
|
|
5352
|
+
fromVersion,
|
|
5353
|
+
toVersion,
|
|
5354
|
+
applied: false,
|
|
5355
|
+
dryRun: false,
|
|
5356
|
+
unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
|
|
5357
|
+
message: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`
|
|
5358
|
+
};
|
|
5359
|
+
}
|
|
5360
|
+
proofOfFixVerification = assembleProofOfFixVerification({
|
|
5361
|
+
reproductionTestPath: reproTestPath,
|
|
5362
|
+
negativePhase: negativePhaseResult,
|
|
5363
|
+
positivePhase: positivePhaseResult,
|
|
5364
|
+
regressionPhase: {
|
|
5365
|
+
executed: runTests,
|
|
5366
|
+
passed: true
|
|
5367
|
+
}
|
|
5368
|
+
});
|
|
5369
|
+
}
|
|
4425
5370
|
return {
|
|
4426
5371
|
packageName,
|
|
4427
5372
|
strategy: "override",
|
|
@@ -4429,13 +5374,14 @@ var applyPackageOverrideTool = defineTool({
|
|
|
4429
5374
|
toVersion,
|
|
4430
5375
|
applied: true,
|
|
4431
5376
|
dryRun: false,
|
|
5377
|
+
proofOfFix: proofOfFixVerification,
|
|
4432
5378
|
message: `Successfully updated deno.json imports["${existingEntry.key}"] for "${overrideSelector}" from ${fromVersion} to ${toVersion}, then ran ${installCommand.join(" ")}${runTests ? ` and passed ${testCommand.join(" ")}` : ""}.${dependencyTraceSuffix2}`
|
|
4433
5379
|
};
|
|
4434
5380
|
});
|
|
4435
5381
|
}
|
|
4436
5382
|
let pkgJson;
|
|
4437
5383
|
try {
|
|
4438
|
-
pkgJson = JSON.parse(
|
|
5384
|
+
pkgJson = JSON.parse(readFileSync15(pkgPath, "utf8"));
|
|
4439
5385
|
} catch {
|
|
4440
5386
|
return {
|
|
4441
5387
|
packageName,
|
|
@@ -4464,14 +5410,63 @@ var applyPackageOverrideTool = defineTool({
|
|
|
4464
5410
|
};
|
|
4465
5411
|
}
|
|
4466
5412
|
return withRepoLock(cwd, async () => {
|
|
5413
|
+
const prepared = await prepareReproductionTestFile({
|
|
5414
|
+
cwd,
|
|
5415
|
+
packageName,
|
|
5416
|
+
vulnerableVersion: fromVersion,
|
|
5417
|
+
patchesDir,
|
|
5418
|
+
proofOfFix,
|
|
5419
|
+
reproductionTest
|
|
5420
|
+
});
|
|
5421
|
+
const reproTestPath = prepared.testFilePath;
|
|
5422
|
+
const isProofActive = Boolean(reproTestPath || proofOfFix || requireProofOfFix);
|
|
5423
|
+
let negativePhaseResult;
|
|
5424
|
+
if (isProofActive) {
|
|
5425
|
+
if (!reproTestPath) {
|
|
5426
|
+
if (requireProofOfFix) {
|
|
5427
|
+
return {
|
|
5428
|
+
packageName,
|
|
5429
|
+
strategy: "none",
|
|
5430
|
+
fromVersion,
|
|
5431
|
+
toVersion,
|
|
5432
|
+
applied: false,
|
|
5433
|
+
dryRun,
|
|
5434
|
+
unresolvedReason: "policy-blocked",
|
|
5435
|
+
message: "Proof-of-fix verification required, but no reproduction test or script was supplied."
|
|
5436
|
+
};
|
|
5437
|
+
}
|
|
5438
|
+
} else {
|
|
5439
|
+
negativePhaseResult = await runProofOfFixPhase({
|
|
5440
|
+
stage: "negative",
|
|
5441
|
+
testFilePath: reproTestPath,
|
|
5442
|
+
cwd
|
|
5443
|
+
});
|
|
5444
|
+
if (!negativePhaseResult.passed) {
|
|
5445
|
+
if (prepared.isCreated && reproTestPath) {
|
|
5446
|
+
await rm4(reproTestPath, { force: true }).catch(() => {
|
|
5447
|
+
});
|
|
5448
|
+
}
|
|
5449
|
+
return {
|
|
5450
|
+
packageName,
|
|
5451
|
+
strategy: "override",
|
|
5452
|
+
fromVersion,
|
|
5453
|
+
toVersion,
|
|
5454
|
+
applied: false,
|
|
5455
|
+
dryRun: false,
|
|
5456
|
+
unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
|
|
5457
|
+
message: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`
|
|
5458
|
+
};
|
|
5459
|
+
}
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
4467
5462
|
setOverrideValue(pkgJson, pm, overrideSelector, toVersion);
|
|
4468
|
-
|
|
5463
|
+
writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
4469
5464
|
try {
|
|
4470
5465
|
const [installCmd, ...installArgs] = installCommand;
|
|
4471
5466
|
await safeExeca(installCmd, installArgs, { cwd, stdio: "pipe" });
|
|
4472
5467
|
} catch (err) {
|
|
4473
5468
|
restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
|
|
4474
|
-
|
|
5469
|
+
writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
4475
5470
|
const message = err instanceof Error ? err.message : String(err);
|
|
4476
5471
|
return {
|
|
4477
5472
|
packageName,
|
|
@@ -4490,7 +5485,7 @@ var applyPackageOverrideTool = defineTool({
|
|
|
4490
5485
|
await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
|
|
4491
5486
|
} catch (err) {
|
|
4492
5487
|
restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
|
|
4493
|
-
|
|
5488
|
+
writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
4494
5489
|
try {
|
|
4495
5490
|
const [rollbackCmd, ...rollbackArgs] = installCommand;
|
|
4496
5491
|
await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
|
|
@@ -4509,6 +5504,46 @@ var applyPackageOverrideTool = defineTool({
|
|
|
4509
5504
|
};
|
|
4510
5505
|
}
|
|
4511
5506
|
}
|
|
5507
|
+
let proofOfFixVerification;
|
|
5508
|
+
if (reproTestPath && negativePhaseResult?.passed) {
|
|
5509
|
+
const positivePhaseResult = await runProofOfFixPhase({
|
|
5510
|
+
stage: "positive",
|
|
5511
|
+
testFilePath: reproTestPath,
|
|
5512
|
+
cwd
|
|
5513
|
+
});
|
|
5514
|
+
if (!positivePhaseResult.passed) {
|
|
5515
|
+
restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
|
|
5516
|
+
writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
5517
|
+
try {
|
|
5518
|
+
const [rollbackCmd, ...rollbackArgs] = installCommand;
|
|
5519
|
+
await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
|
|
5520
|
+
} catch {
|
|
5521
|
+
}
|
|
5522
|
+
if (prepared.isCreated && reproTestPath) {
|
|
5523
|
+
await rm4(reproTestPath, { force: true }).catch(() => {
|
|
5524
|
+
});
|
|
5525
|
+
}
|
|
5526
|
+
return {
|
|
5527
|
+
packageName,
|
|
5528
|
+
strategy: "override",
|
|
5529
|
+
fromVersion,
|
|
5530
|
+
toVersion,
|
|
5531
|
+
applied: false,
|
|
5532
|
+
dryRun: false,
|
|
5533
|
+
unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
|
|
5534
|
+
message: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`
|
|
5535
|
+
};
|
|
5536
|
+
}
|
|
5537
|
+
proofOfFixVerification = assembleProofOfFixVerification({
|
|
5538
|
+
reproductionTestPath: reproTestPath,
|
|
5539
|
+
negativePhase: negativePhaseResult,
|
|
5540
|
+
positivePhase: positivePhaseResult,
|
|
5541
|
+
regressionPhase: {
|
|
5542
|
+
executed: runTests,
|
|
5543
|
+
passed: true
|
|
5544
|
+
}
|
|
5545
|
+
});
|
|
5546
|
+
}
|
|
4512
5547
|
let dedupeNote = "";
|
|
4513
5548
|
if (dedupeCommand.length > 0) {
|
|
4514
5549
|
try {
|
|
@@ -4526,6 +5561,7 @@ var applyPackageOverrideTool = defineTool({
|
|
|
4526
5561
|
toVersion,
|
|
4527
5562
|
applied: true,
|
|
4528
5563
|
dryRun: false,
|
|
5564
|
+
proofOfFix: proofOfFixVerification,
|
|
4529
5565
|
message: `Successfully applied ${overrideLabel} for "${overrideSelector}" from ${fromVersion} to ${toVersion}, then ran ${installCommand.join(" ")}${runTests ? ` and passed ${testCommand.join(" ")}` : ""}.${dedupeNote}${dependencyTraceSuffix}`
|
|
4530
5566
|
};
|
|
4531
5567
|
});
|
|
@@ -4608,7 +5644,19 @@ async function verifyPackageTrust(packageName, targetVersion, policy) {
|
|
|
4608
5644
|
|
|
4609
5645
|
// src/remediation/local/primary-strategy.ts
|
|
4610
5646
|
async function resolvePrimaryResult(params) {
|
|
4611
|
-
const {
|
|
5647
|
+
const {
|
|
5648
|
+
vulnerable,
|
|
5649
|
+
cwd,
|
|
5650
|
+
packageManager,
|
|
5651
|
+
dryRun,
|
|
5652
|
+
policy,
|
|
5653
|
+
runTests,
|
|
5654
|
+
constraints,
|
|
5655
|
+
proofOfFix,
|
|
5656
|
+
requireProofOfFix = constraints.requireProofOfFix,
|
|
5657
|
+
reproductionTest,
|
|
5658
|
+
patchesDir
|
|
5659
|
+
} = params;
|
|
4612
5660
|
const pkg = vulnerable.installed;
|
|
4613
5661
|
const firstPatchedVersion = vulnerable.affected.firstPatchedVersion;
|
|
4614
5662
|
if (pkg.type === "transitive") {
|
|
@@ -4708,7 +5756,11 @@ async function resolvePrimaryResult(params) {
|
|
|
4708
5756
|
installMode: constraints.installMode,
|
|
4709
5757
|
installPreferOffline: constraints.installPreferOffline,
|
|
4710
5758
|
enforceFrozenLockfile: constraints.enforceFrozenLockfile,
|
|
4711
|
-
workspace: constraints.workspace
|
|
5759
|
+
workspace: constraints.workspace,
|
|
5760
|
+
proofOfFix,
|
|
5761
|
+
requireProofOfFix,
|
|
5762
|
+
reproductionTest,
|
|
5763
|
+
patchesDir
|
|
4712
5764
|
});
|
|
4713
5765
|
return {
|
|
4714
5766
|
steps: 2,
|
|
@@ -4785,7 +5837,11 @@ async function resolvePrimaryResult(params) {
|
|
|
4785
5837
|
installPreferOffline: constraints.installPreferOffline,
|
|
4786
5838
|
enforceFrozenLockfile: constraints.enforceFrozenLockfile,
|
|
4787
5839
|
workspace: constraints.workspace,
|
|
4788
|
-
breakingChangeRefactor: constraints.enableBreakingChangeRefactor
|
|
5840
|
+
breakingChangeRefactor: constraints.enableBreakingChangeRefactor,
|
|
5841
|
+
proofOfFix,
|
|
5842
|
+
requireProofOfFix,
|
|
5843
|
+
reproductionTest,
|
|
5844
|
+
patchesDir
|
|
4789
5845
|
});
|
|
4790
5846
|
return {
|
|
4791
5847
|
steps: 2,
|
|
@@ -4795,10 +5851,10 @@ async function resolvePrimaryResult(params) {
|
|
|
4795
5851
|
|
|
4796
5852
|
// src/remediation/tools/fetch-package-source.ts
|
|
4797
5853
|
import { z as z6 } from "zod";
|
|
4798
|
-
import { mkdir as
|
|
4799
|
-
import { join as
|
|
5854
|
+
import { mkdir as mkdir3, mkdtemp, readdir as readdir2, readFile as readFile4, rm as rm5 } from "fs/promises";
|
|
5855
|
+
import { join as join18 } from "path";
|
|
4800
5856
|
import { tmpdir } from "os";
|
|
4801
|
-
import { execa as
|
|
5857
|
+
import { execa as execa6 } from "execa";
|
|
4802
5858
|
var fetchPackageSourceTool = defineTool({
|
|
4803
5859
|
description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
|
|
4804
5860
|
parameters: z6.object({
|
|
@@ -4816,34 +5872,33 @@ var fetchPackageSourceTool = defineTool({
|
|
|
4816
5872
|
if (safePatterns.length === 0) {
|
|
4817
5873
|
return { success: false, error: "No valid file patterns provided." };
|
|
4818
5874
|
}
|
|
4819
|
-
const tempBaseDir = await mkdtemp(
|
|
4820
|
-
const extractDir =
|
|
5875
|
+
const tempBaseDir = await mkdtemp(join18(tmpdir(), "autoremediator-pkg-"));
|
|
5876
|
+
const extractDir = join18(tempBaseDir, "out");
|
|
4821
5877
|
try {
|
|
4822
5878
|
const scopedName = packageName.split("/").pop();
|
|
4823
5879
|
const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
|
|
4824
|
-
await
|
|
4825
|
-
const tarballPath =
|
|
4826
|
-
await
|
|
4827
|
-
await
|
|
4828
|
-
await
|
|
5880
|
+
await mkdir3(tempBaseDir, { recursive: true });
|
|
5881
|
+
const tarballPath = join18(tempBaseDir, "package.tgz");
|
|
5882
|
+
await execa6("curl", ["-f", "-s", "-S", "-L", "-o", tarballPath, npmUrl]);
|
|
5883
|
+
await mkdir3(extractDir, { recursive: true });
|
|
5884
|
+
await execa6("tar", ["-xzf", tarballPath, "-C", extractDir]);
|
|
4829
5885
|
const extractedContents = await readdir2(extractDir);
|
|
4830
|
-
const packageRootDir = extractedContents.includes("package") ?
|
|
5886
|
+
const packageRootDir = extractedContents.includes("package") ? join18(extractDir, "package") : extractDir;
|
|
4831
5887
|
const sourceCode = {};
|
|
4832
5888
|
async function walkDir(dir, relativeBase) {
|
|
4833
5889
|
try {
|
|
4834
5890
|
const files = await readdir2(dir, { withFileTypes: true });
|
|
4835
5891
|
for (const file of files) {
|
|
4836
|
-
const fullPath =
|
|
4837
|
-
const relPath =
|
|
5892
|
+
const fullPath = join18(dir, file.name);
|
|
5893
|
+
const relPath = join18(relativeBase, file.name);
|
|
4838
5894
|
if (file.isDirectory()) {
|
|
4839
|
-
if (!["node_modules", ".git", "
|
|
4840
|
-
file.name
|
|
4841
|
-
)) {
|
|
5895
|
+
if (!["node_modules", ".git", "coverage", ".next"].includes(file.name)) {
|
|
4842
5896
|
await walkDir(fullPath, relPath);
|
|
4843
5897
|
}
|
|
4844
5898
|
} else if (file.isFile()) {
|
|
4845
5899
|
const matches = safePatterns.some((pattern) => {
|
|
4846
|
-
const
|
|
5900
|
+
const escaped = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*");
|
|
5901
|
+
const regex = new RegExp(`^${escaped}$`);
|
|
4847
5902
|
return regex.test(file.name);
|
|
4848
5903
|
});
|
|
4849
5904
|
if (matches) {
|
|
@@ -4872,7 +5927,8 @@ var fetchPackageSourceTool = defineTool({
|
|
|
4872
5927
|
};
|
|
4873
5928
|
} catch (err) {
|
|
4874
5929
|
const message = err instanceof Error ? err.message : String(err);
|
|
4875
|
-
|
|
5930
|
+
const exitCode = typeof err.exitCode === "number" ? err.exitCode : void 0;
|
|
5931
|
+
if (exitCode === 22 || message.includes("404") || message.includes("not found") || message.includes("returned error: 404")) {
|
|
4876
5932
|
return {
|
|
4877
5933
|
success: false,
|
|
4878
5934
|
error: `Package ${packageName}@${version} not found on npm registry. It may not exist or the version may be incorrect.`
|
|
@@ -4883,7 +5939,7 @@ var fetchPackageSourceTool = defineTool({
|
|
|
4883
5939
|
error: `Failed to fetch and extract package ${packageName}@${version}: ${message}`
|
|
4884
5940
|
};
|
|
4885
5941
|
} finally {
|
|
4886
|
-
await
|
|
5942
|
+
await rm5(tempBaseDir, { recursive: true, force: true });
|
|
4887
5943
|
}
|
|
4888
5944
|
}
|
|
4889
5945
|
});
|
|
@@ -4964,7 +6020,7 @@ Important:
|
|
|
4964
6020
|
}
|
|
4965
6021
|
|
|
4966
6022
|
// src/remediation/tools/generate-patch/helpers.ts
|
|
4967
|
-
import { parseSync as
|
|
6023
|
+
import { parseSync as parseSync4 } from "oxc-parser";
|
|
4968
6024
|
import YAML from "yaml";
|
|
4969
6025
|
function parseLlmAnalysisResponse(text) {
|
|
4970
6026
|
try {
|
|
@@ -4987,7 +6043,7 @@ function validatePatchedCodeSyntax(filePath, code) {
|
|
|
4987
6043
|
const lowerPath = filePath.toLowerCase();
|
|
4988
6044
|
if (/\.[cm]?[jt]sx?$/i.test(lowerPath)) {
|
|
4989
6045
|
try {
|
|
4990
|
-
const res =
|
|
6046
|
+
const res = parseSync4(filePath, code);
|
|
4991
6047
|
if (res.errors && res.errors.length > 0) {
|
|
4992
6048
|
const errorMsg = res.errors.map((e) => e.message).join("; ");
|
|
4993
6049
|
return {
|
|
@@ -5039,32 +6095,179 @@ function buildGeneratedPatches(sourceFiles, fixedCodeByFile) {
|
|
|
5039
6095
|
}
|
|
5040
6096
|
return patches;
|
|
5041
6097
|
}
|
|
5042
|
-
function
|
|
6098
|
+
function splitLines(text) {
|
|
6099
|
+
if (text.length === 0) return [];
|
|
6100
|
+
const lines = text.split(/\r?\n/);
|
|
6101
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
6102
|
+
lines.pop();
|
|
6103
|
+
}
|
|
6104
|
+
return lines;
|
|
6105
|
+
}
|
|
6106
|
+
function myersDiff(aLines, bLines) {
|
|
6107
|
+
const N = aLines.length;
|
|
6108
|
+
const M = bLines.length;
|
|
6109
|
+
const max = N + M;
|
|
6110
|
+
if (max === 0) return [];
|
|
6111
|
+
const offset = max;
|
|
6112
|
+
const v = new Int32Array(2 * max + 1);
|
|
6113
|
+
const history = [];
|
|
6114
|
+
v[offset + 1] = 0;
|
|
6115
|
+
let dFound = -1;
|
|
6116
|
+
for (let d = 0; d <= max; d++) {
|
|
6117
|
+
const vCopy = new Int32Array(v);
|
|
6118
|
+
history.push(vCopy);
|
|
6119
|
+
for (let k = -d; k <= d; k += 2) {
|
|
6120
|
+
let x2;
|
|
6121
|
+
if (k === -d || k !== d && v[offset + k - 1] < v[offset + k + 1]) {
|
|
6122
|
+
x2 = v[offset + k + 1];
|
|
6123
|
+
} else {
|
|
6124
|
+
x2 = v[offset + k - 1] + 1;
|
|
6125
|
+
}
|
|
6126
|
+
let y2 = x2 - k;
|
|
6127
|
+
while (x2 < N && y2 < M && aLines[x2] === bLines[y2]) {
|
|
6128
|
+
x2++;
|
|
6129
|
+
y2++;
|
|
6130
|
+
}
|
|
6131
|
+
v[offset + k] = x2;
|
|
6132
|
+
if (x2 >= N && y2 >= M) {
|
|
6133
|
+
dFound = d;
|
|
6134
|
+
break;
|
|
6135
|
+
}
|
|
6136
|
+
}
|
|
6137
|
+
if (dFound !== -1) break;
|
|
6138
|
+
}
|
|
6139
|
+
let x = N;
|
|
6140
|
+
let y = M;
|
|
6141
|
+
const edits = [];
|
|
6142
|
+
for (let d = dFound; d > 0; d--) {
|
|
6143
|
+
const k = x - y;
|
|
6144
|
+
const vPrev = history[d];
|
|
6145
|
+
let prevK;
|
|
6146
|
+
if (k === -d || k !== d && vPrev[offset + k - 1] < vPrev[offset + k + 1]) {
|
|
6147
|
+
prevK = k + 1;
|
|
6148
|
+
} else {
|
|
6149
|
+
prevK = k - 1;
|
|
6150
|
+
}
|
|
6151
|
+
const prevX = vPrev[offset + prevK];
|
|
6152
|
+
const prevY = prevX - prevK;
|
|
6153
|
+
while (x > prevX && y > prevY) {
|
|
6154
|
+
edits.push({ type: "common", text: aLines[x - 1] });
|
|
6155
|
+
x--;
|
|
6156
|
+
y--;
|
|
6157
|
+
}
|
|
6158
|
+
if (x === prevX) {
|
|
6159
|
+
edits.push({ type: "insert", text: bLines[y - 1] });
|
|
6160
|
+
y--;
|
|
6161
|
+
} else {
|
|
6162
|
+
edits.push({ type: "delete", text: aLines[x - 1] });
|
|
6163
|
+
x--;
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
while (x > 0 && y > 0) {
|
|
6167
|
+
edits.push({ type: "common", text: aLines[x - 1] });
|
|
6168
|
+
x--;
|
|
6169
|
+
y--;
|
|
6170
|
+
}
|
|
6171
|
+
edits.reverse();
|
|
6172
|
+
return edits;
|
|
6173
|
+
}
|
|
6174
|
+
function generateUnifiedDiff(original, fixed, filePath, context = 3) {
|
|
5043
6175
|
if (original === fixed) {
|
|
5044
6176
|
return null;
|
|
5045
6177
|
}
|
|
5046
|
-
const
|
|
5047
|
-
const
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
6178
|
+
const aLines = splitLines(original);
|
|
6179
|
+
const bLines = splitLines(fixed);
|
|
6180
|
+
let prefixCount = 0;
|
|
6181
|
+
while (prefixCount < aLines.length && prefixCount < bLines.length && aLines[prefixCount] === bLines[prefixCount]) {
|
|
6182
|
+
prefixCount++;
|
|
6183
|
+
}
|
|
6184
|
+
let suffixCount = 0;
|
|
6185
|
+
while (suffixCount < aLines.length - prefixCount && suffixCount < bLines.length - prefixCount && aLines[aLines.length - 1 - suffixCount] === bLines[bLines.length - 1 - suffixCount]) {
|
|
6186
|
+
suffixCount++;
|
|
6187
|
+
}
|
|
6188
|
+
const prefixEdits = [];
|
|
6189
|
+
for (let i = 0; i < prefixCount; i++) {
|
|
6190
|
+
prefixEdits.push({ type: "common", text: aLines[i] });
|
|
6191
|
+
}
|
|
6192
|
+
const suffixEdits = [];
|
|
6193
|
+
for (let i = aLines.length - suffixCount; i < aLines.length; i++) {
|
|
6194
|
+
suffixEdits.push({ type: "common", text: aLines[i] });
|
|
6195
|
+
}
|
|
6196
|
+
const aMiddle = aLines.slice(prefixCount, aLines.length - suffixCount);
|
|
6197
|
+
const bMiddle = bLines.slice(prefixCount, bLines.length - suffixCount);
|
|
6198
|
+
const middleEdits = myersDiff(aMiddle, bMiddle);
|
|
6199
|
+
const edits = [...prefixEdits, ...middleEdits, ...suffixEdits];
|
|
6200
|
+
const changeIndices = [];
|
|
6201
|
+
for (let i = 0; i < edits.length; i++) {
|
|
6202
|
+
if (edits[i].type !== "common") {
|
|
6203
|
+
changeIndices.push(i);
|
|
6204
|
+
}
|
|
6205
|
+
}
|
|
6206
|
+
if (changeIndices.length === 0) {
|
|
6207
|
+
return null;
|
|
6208
|
+
}
|
|
6209
|
+
const groups = [];
|
|
6210
|
+
let currentGroup = [changeIndices[0]];
|
|
6211
|
+
for (let i = 1; i < changeIndices.length; i++) {
|
|
6212
|
+
const prev = changeIndices[i - 1];
|
|
6213
|
+
const curr = changeIndices[i];
|
|
6214
|
+
if (curr - prev - 1 <= 2 * context) {
|
|
6215
|
+
currentGroup.push(curr);
|
|
6216
|
+
} else {
|
|
6217
|
+
groups.push(currentGroup);
|
|
6218
|
+
currentGroup = [curr];
|
|
5065
6219
|
}
|
|
5066
6220
|
}
|
|
5067
|
-
|
|
6221
|
+
groups.push(currentGroup);
|
|
6222
|
+
const hunkStrings = [];
|
|
6223
|
+
for (const group of groups) {
|
|
6224
|
+
const firstChange = group[0];
|
|
6225
|
+
const lastChange = group[group.length - 1];
|
|
6226
|
+
const startIndex = Math.max(0, firstChange - context);
|
|
6227
|
+
const endIndex = Math.min(edits.length, lastChange + context + 1);
|
|
6228
|
+
const hunkEdits = edits.slice(startIndex, endIndex);
|
|
6229
|
+
let curOrig = 1;
|
|
6230
|
+
let curFixed = 1;
|
|
6231
|
+
for (let i = 0; i < startIndex; i++) {
|
|
6232
|
+
if (edits[i].type === "common") {
|
|
6233
|
+
curOrig++;
|
|
6234
|
+
curFixed++;
|
|
6235
|
+
} else if (edits[i].type === "delete") {
|
|
6236
|
+
curOrig++;
|
|
6237
|
+
} else if (edits[i].type === "insert") {
|
|
6238
|
+
curFixed++;
|
|
6239
|
+
}
|
|
6240
|
+
}
|
|
6241
|
+
let origStart = curOrig;
|
|
6242
|
+
let fixedStart = curFixed;
|
|
6243
|
+
let origCount = 0;
|
|
6244
|
+
let fixedCount = 0;
|
|
6245
|
+
for (const edit of hunkEdits) {
|
|
6246
|
+
if (edit.type === "common") {
|
|
6247
|
+
origCount++;
|
|
6248
|
+
fixedCount++;
|
|
6249
|
+
} else if (edit.type === "delete") {
|
|
6250
|
+
origCount++;
|
|
6251
|
+
} else if (edit.type === "insert") {
|
|
6252
|
+
fixedCount++;
|
|
6253
|
+
}
|
|
6254
|
+
}
|
|
6255
|
+
if (origCount === 0) origStart = Math.max(0, origStart - 1);
|
|
6256
|
+
if (fixedCount === 0) fixedStart = Math.max(0, fixedStart - 1);
|
|
6257
|
+
const lines = [];
|
|
6258
|
+
lines.push(`@@ -${origStart},${origCount} +${fixedStart},${fixedCount} @@`);
|
|
6259
|
+
for (const edit of hunkEdits) {
|
|
6260
|
+
if (edit.type === "common") {
|
|
6261
|
+
lines.push(` ${edit.text}`);
|
|
6262
|
+
} else if (edit.type === "delete") {
|
|
6263
|
+
lines.push(`-${edit.text}`);
|
|
6264
|
+
} else if (edit.type === "insert") {
|
|
6265
|
+
lines.push(`+${edit.text}`);
|
|
6266
|
+
}
|
|
6267
|
+
}
|
|
6268
|
+
hunkStrings.push(lines.join("\n"));
|
|
6269
|
+
}
|
|
6270
|
+
return [`--- a/${filePath}`, `+++ b/${filePath}`, ...hunkStrings].join("\n") + "\n";
|
|
5068
6271
|
}
|
|
5069
6272
|
|
|
5070
6273
|
// src/remediation/tools/generate-patch/index.ts
|
|
@@ -5258,126 +6461,209 @@ var generatePatchTool = defineTool({
|
|
|
5258
6461
|
|
|
5259
6462
|
// src/remediation/tools/apply-patch-file/index.ts
|
|
5260
6463
|
import { z as z8 } from "zod";
|
|
5261
|
-
import { mkdir as
|
|
5262
|
-
import {
|
|
5263
|
-
import {
|
|
5264
|
-
import { execa as execa9 } from "execa";
|
|
6464
|
+
import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
|
|
6465
|
+
import { join as join21 } from "path";
|
|
6466
|
+
import { execa as execa8 } from "execa";
|
|
5265
6467
|
|
|
5266
|
-
// src/
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
6468
|
+
// src/platform/rollback.ts
|
|
6469
|
+
import { execFileSync, execSync } from "child_process";
|
|
6470
|
+
import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync16, rmSync, statSync as statSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
6471
|
+
import { isAbsolute as isAbsolute4, join as join19, relative as relative2 } from "path";
|
|
6472
|
+
var MANIFEST_PATTERNS = [
|
|
6473
|
+
/package\.json$/,
|
|
6474
|
+
/.*lock.*/,
|
|
6475
|
+
/deno\.jsonc?$/,
|
|
6476
|
+
/\.patch$/,
|
|
6477
|
+
/\.autoremediator\.ya?ml$/,
|
|
6478
|
+
/tsconfig.*\.json$/
|
|
6479
|
+
];
|
|
6480
|
+
var IGNORED_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".pnpm-store"]);
|
|
6481
|
+
var activeSnapshots = /* @__PURE__ */ new Set();
|
|
6482
|
+
function recordFileTouch(filePath) {
|
|
6483
|
+
for (const snapshot of activeSnapshots) {
|
|
6484
|
+
const absPath = isAbsolute4(filePath) ? filePath : join19(snapshot.cwd, filePath);
|
|
6485
|
+
if (!absPath.startsWith(snapshot.cwd)) continue;
|
|
6486
|
+
const relPath = relative2(snapshot.cwd, absPath);
|
|
6487
|
+
if (!snapshot.fileSnapshots.has(relPath)) {
|
|
6488
|
+
if (existsSync14(absPath)) {
|
|
6489
|
+
try {
|
|
6490
|
+
snapshot.fileSnapshots.set(relPath, readFileSync16(absPath, "utf8"));
|
|
6491
|
+
} catch {
|
|
6492
|
+
try {
|
|
6493
|
+
snapshot.fileSnapshots.set(relPath, readFileSync16(absPath).toString("base64"));
|
|
6494
|
+
} catch {
|
|
6495
|
+
snapshot.fileSnapshots.set(relPath, null);
|
|
6496
|
+
}
|
|
6497
|
+
}
|
|
6498
|
+
} else {
|
|
6499
|
+
snapshot.fileSnapshots.set(relPath, null);
|
|
6500
|
+
}
|
|
6501
|
+
}
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
function discoverProjectFiles(dir, maxDepth = 4, currentDepth = 0) {
|
|
6505
|
+
const discovered = [];
|
|
6506
|
+
if (currentDepth > maxDepth) return discovered;
|
|
6507
|
+
let entries;
|
|
5272
6508
|
try {
|
|
5273
|
-
|
|
6509
|
+
entries = readdirSync4(dir);
|
|
6510
|
+
} catch {
|
|
6511
|
+
return discovered;
|
|
6512
|
+
}
|
|
6513
|
+
for (const entry of entries) {
|
|
6514
|
+
if (IGNORED_DIRS3.has(entry)) continue;
|
|
6515
|
+
const fullPath = join19(dir, entry);
|
|
6516
|
+
let stat2;
|
|
6517
|
+
try {
|
|
6518
|
+
stat2 = statSync4(fullPath);
|
|
6519
|
+
} catch {
|
|
6520
|
+
continue;
|
|
6521
|
+
}
|
|
6522
|
+
if (stat2.isDirectory()) {
|
|
6523
|
+
discovered.push(...discoverProjectFiles(fullPath, maxDepth, currentDepth + 1));
|
|
6524
|
+
} else if (stat2.isFile()) {
|
|
6525
|
+
const matchesPattern = MANIFEST_PATTERNS.some((pattern) => pattern.test(entry));
|
|
6526
|
+
if (matchesPattern) {
|
|
6527
|
+
discovered.push(fullPath);
|
|
6528
|
+
}
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
return discovered;
|
|
6532
|
+
}
|
|
6533
|
+
async function isGitRepository(cwd) {
|
|
6534
|
+
try {
|
|
6535
|
+
const res = await safeExeca("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
5274
6536
|
cwd,
|
|
5275
|
-
timeoutMs,
|
|
5276
6537
|
stdio: "pipe"
|
|
5277
6538
|
});
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
6539
|
+
return String(res.stdout).trim() === "true";
|
|
6540
|
+
} catch {
|
|
6541
|
+
return false;
|
|
6542
|
+
}
|
|
6543
|
+
}
|
|
6544
|
+
function parsePorcelainLines(stdout) {
|
|
6545
|
+
return stdout.split(/\r?\n/).map((l) => l.replace(/\r$/, "")).filter((l) => l.length >= 3 && l.slice(0, 2).trim().length > 0);
|
|
6546
|
+
}
|
|
6547
|
+
function parseGitPorcelainLine(line) {
|
|
6548
|
+
if (!line || line.length < 3) return null;
|
|
6549
|
+
const statusCode = line.slice(0, 2).trim();
|
|
6550
|
+
let filePath = line.slice(3).trim();
|
|
6551
|
+
if (filePath.includes(" -> ")) {
|
|
6552
|
+
filePath = filePath.split(" -> ").pop().trim();
|
|
6553
|
+
}
|
|
6554
|
+
if (filePath.startsWith('"') && filePath.endsWith('"')) {
|
|
6555
|
+
filePath = filePath.slice(1, -1).replace(/\\"/g, '"');
|
|
6556
|
+
}
|
|
6557
|
+
return { statusCode, filePath };
|
|
6558
|
+
}
|
|
6559
|
+
async function getGitStatusPorcelain(cwd) {
|
|
6560
|
+
try {
|
|
6561
|
+
const res = await safeExeca("git", ["status", "--porcelain=v1", "-uall"], {
|
|
6562
|
+
cwd,
|
|
6563
|
+
stdio: "pipe"
|
|
6564
|
+
});
|
|
6565
|
+
return parsePorcelainLines(String(res.stdout));
|
|
6566
|
+
} catch {
|
|
6567
|
+
return [];
|
|
6568
|
+
}
|
|
6569
|
+
}
|
|
6570
|
+
async function createProjectSnapshot(cwd, explicitFiles) {
|
|
6571
|
+
const isGit = await isGitRepository(cwd);
|
|
6572
|
+
const gitStatusBefore = isGit ? await getGitStatusPorcelain(cwd) : void 0;
|
|
6573
|
+
const fileSnapshots = /* @__PURE__ */ new Map();
|
|
6574
|
+
const targetFiles = explicitFiles ? explicitFiles.map((f) => f.startsWith(cwd) ? f : join19(cwd, f)) : discoverProjectFiles(cwd);
|
|
6575
|
+
for (const fullPath of targetFiles) {
|
|
6576
|
+
const relPath = relative2(cwd, fullPath);
|
|
6577
|
+
if (existsSync14(fullPath)) {
|
|
6578
|
+
try {
|
|
6579
|
+
fileSnapshots.set(relPath, readFileSync16(fullPath, "utf8"));
|
|
6580
|
+
} catch {
|
|
6581
|
+
try {
|
|
6582
|
+
fileSnapshots.set(relPath, readFileSync16(fullPath).toString("base64"));
|
|
6583
|
+
} catch {
|
|
6584
|
+
}
|
|
6585
|
+
}
|
|
6586
|
+
} else {
|
|
6587
|
+
fileSnapshots.set(relPath, null);
|
|
5290
6588
|
}
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
const
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
exitCode,
|
|
5310
|
-
output,
|
|
5311
|
-
durationMs,
|
|
5312
|
-
error: "Exploit reproduction test timed out (execution hung)."
|
|
5313
|
-
};
|
|
6589
|
+
}
|
|
6590
|
+
const snapshot = {
|
|
6591
|
+
cwd,
|
|
6592
|
+
isGit,
|
|
6593
|
+
gitStatusBefore,
|
|
6594
|
+
fileSnapshots
|
|
6595
|
+
};
|
|
6596
|
+
return snapshot;
|
|
6597
|
+
}
|
|
6598
|
+
function restoreProjectSnapshotSync(snapshot) {
|
|
6599
|
+
for (const [relPath, previousContent] of snapshot.fileSnapshots.entries()) {
|
|
6600
|
+
const fullPath = join19(snapshot.cwd, relPath);
|
|
6601
|
+
if (previousContent === null) {
|
|
6602
|
+
if (existsSync14(fullPath)) {
|
|
6603
|
+
try {
|
|
6604
|
+
rmSync(fullPath, { force: true, recursive: true });
|
|
6605
|
+
} catch {
|
|
6606
|
+
}
|
|
5314
6607
|
}
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
passed: false,
|
|
5320
|
-
exitCode,
|
|
5321
|
-
output,
|
|
5322
|
-
durationMs,
|
|
5323
|
-
error: `Reproduction test failed to execute (harness error): ${errorMsg}`
|
|
5324
|
-
};
|
|
6608
|
+
} else {
|
|
6609
|
+
try {
|
|
6610
|
+
writeFileSync8(fullPath, previousContent, "utf8");
|
|
6611
|
+
} catch {
|
|
5325
6612
|
}
|
|
5326
|
-
return {
|
|
5327
|
-
executed: true,
|
|
5328
|
-
passed: true,
|
|
5329
|
-
exitCode,
|
|
5330
|
-
output,
|
|
5331
|
-
durationMs
|
|
5332
|
-
};
|
|
5333
6613
|
}
|
|
5334
|
-
return {
|
|
5335
|
-
executed: true,
|
|
5336
|
-
passed: false,
|
|
5337
|
-
exitCode,
|
|
5338
|
-
output,
|
|
5339
|
-
durationMs,
|
|
5340
|
-
error: `Reproduction test failed on patched target: ${errorMsg}`
|
|
5341
|
-
};
|
|
5342
6614
|
}
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
6615
|
+
const currentFiles = discoverProjectFiles(snapshot.cwd);
|
|
6616
|
+
for (const fullPath of currentFiles) {
|
|
6617
|
+
const relPath = relative2(snapshot.cwd, fullPath);
|
|
6618
|
+
if (!snapshot.fileSnapshots.has(relPath)) {
|
|
6619
|
+
try {
|
|
6620
|
+
rmSync(fullPath, { force: true, recursive: true });
|
|
6621
|
+
} catch {
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6625
|
+
if (snapshot.isGit) {
|
|
6626
|
+
try {
|
|
6627
|
+
const stdout = execSync("git status --porcelain=v1 -uall", {
|
|
6628
|
+
cwd: snapshot.cwd,
|
|
6629
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
6630
|
+
encoding: "utf8"
|
|
6631
|
+
});
|
|
6632
|
+
const currentStatus = parsePorcelainLines(stdout);
|
|
6633
|
+
const beforeSet = new Set(snapshot.gitStatusBefore ?? []);
|
|
6634
|
+
for (const line of currentStatus) {
|
|
6635
|
+
if (beforeSet.has(line)) continue;
|
|
6636
|
+
const parsed = parseGitPorcelainLine(line);
|
|
6637
|
+
if (!parsed) continue;
|
|
6638
|
+
const { statusCode, filePath } = parsed;
|
|
6639
|
+
const fullPath = join19(snapshot.cwd, filePath);
|
|
6640
|
+
if (statusCode === "??" && existsSync14(fullPath)) {
|
|
6641
|
+
try {
|
|
6642
|
+
rmSync(fullPath, { force: true, recursive: true });
|
|
6643
|
+
} catch {
|
|
6644
|
+
}
|
|
6645
|
+
} else if (statusCode === "M" || statusCode === "MM" || statusCode === "D" || statusCode.includes("M") || statusCode.includes("D") || statusCode.includes("T")) {
|
|
6646
|
+
try {
|
|
6647
|
+
execFileSync("git", ["checkout", "--", filePath], {
|
|
6648
|
+
cwd: snapshot.cwd,
|
|
6649
|
+
stdio: "ignore"
|
|
6650
|
+
});
|
|
6651
|
+
} catch {
|
|
6652
|
+
}
|
|
6653
|
+
}
|
|
6654
|
+
}
|
|
6655
|
+
} catch {
|
|
6656
|
+
}
|
|
5361
6657
|
}
|
|
5362
|
-
const durationMs = (negativePhase.durationMs || 0) + (positivePhase.durationMs || 0) + (regPhase.durationMs || 0);
|
|
5363
|
-
return {
|
|
5364
|
-
status,
|
|
5365
|
-
reproductionTestPath,
|
|
5366
|
-
negativePhase,
|
|
5367
|
-
positivePhase,
|
|
5368
|
-
regressionPhase: regPhase,
|
|
5369
|
-
durationMs,
|
|
5370
|
-
failureReason
|
|
5371
|
-
};
|
|
5372
6658
|
}
|
|
5373
6659
|
|
|
5374
6660
|
// src/remediation/tools/apply-patch-file/helpers.ts
|
|
5375
|
-
import { existsSync as
|
|
5376
|
-
import { mkdtemp as mkdtemp2, readFile as readFile5, rm as
|
|
6661
|
+
import { existsSync as existsSync15 } from "fs";
|
|
6662
|
+
import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm6, writeFile as writeFile3 } from "fs/promises";
|
|
5377
6663
|
import { createHash } from "crypto";
|
|
5378
6664
|
import { tmpdir as tmpdir2 } from "os";
|
|
5379
|
-
import { join as
|
|
5380
|
-
import { execa as
|
|
6665
|
+
import { join as join20 } from "path";
|
|
6666
|
+
import { execa as execa7 } from "execa";
|
|
5381
6667
|
async function resolvePatchMode(packageManager, cwd) {
|
|
5382
6668
|
if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno")
|
|
5383
6669
|
return "patch-package";
|
|
@@ -5408,10 +6694,10 @@ function computePatchIntegrity(patchContent) {
|
|
|
5408
6694
|
return `sha256:${hex}`;
|
|
5409
6695
|
}
|
|
5410
6696
|
async function writePatchManifest(manifestFilePath, artifact) {
|
|
5411
|
-
await
|
|
6697
|
+
await writeFile3(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
|
|
5412
6698
|
}
|
|
5413
6699
|
async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
5414
|
-
const pkgJsonPath =
|
|
6700
|
+
const pkgJsonPath = join20(cwd, "package.json");
|
|
5415
6701
|
let pkgJson;
|
|
5416
6702
|
try {
|
|
5417
6703
|
pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf8"));
|
|
@@ -5426,7 +6712,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
|
5426
6712
|
try {
|
|
5427
6713
|
const commands = getPackageManagerCommands(packageManager);
|
|
5428
6714
|
const [cmd, ...args] = commands.installDev("patch-package");
|
|
5429
|
-
await
|
|
6715
|
+
await execa7(cmd, args, {
|
|
5430
6716
|
cwd,
|
|
5431
6717
|
stdio: "pipe"
|
|
5432
6718
|
});
|
|
@@ -5447,11 +6733,11 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
|
5447
6733
|
} else if (!currentPostinstall) {
|
|
5448
6734
|
pkgJson.scripts.postinstall = patchApplyCmd;
|
|
5449
6735
|
}
|
|
5450
|
-
await
|
|
6736
|
+
await writeFile3(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
5451
6737
|
return { success: true };
|
|
5452
6738
|
}
|
|
5453
6739
|
async function capturePackageJsonSnapshot(cwd) {
|
|
5454
|
-
const path =
|
|
6740
|
+
const path = join20(cwd, "package.json");
|
|
5455
6741
|
try {
|
|
5456
6742
|
const content = await readFile5(path, "utf8");
|
|
5457
6743
|
return { path, content };
|
|
@@ -5469,19 +6755,19 @@ async function cleanupPatchArtifacts(params) {
|
|
|
5469
6755
|
installCommand,
|
|
5470
6756
|
rerunInstall
|
|
5471
6757
|
} = params;
|
|
5472
|
-
await
|
|
6758
|
+
await rm6(patchFilePath, { force: true }).catch(() => void 0);
|
|
5473
6759
|
if (manifestFilePath) {
|
|
5474
|
-
await
|
|
6760
|
+
await rm6(manifestFilePath, { force: true }).catch(() => void 0);
|
|
5475
6761
|
}
|
|
5476
6762
|
if (patchMode === "patch-package" && packageJsonSnapshot) {
|
|
5477
|
-
await
|
|
6763
|
+
await writeFile3(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
|
|
5478
6764
|
() => void 0
|
|
5479
6765
|
);
|
|
5480
6766
|
}
|
|
5481
6767
|
if (!rerunInstall) return;
|
|
5482
6768
|
try {
|
|
5483
6769
|
const [installCmd, ...installArgs] = installCommand;
|
|
5484
|
-
await
|
|
6770
|
+
await execa7(installCmd, installArgs, {
|
|
5485
6771
|
cwd,
|
|
5486
6772
|
stdio: "pipe"
|
|
5487
6773
|
});
|
|
@@ -5495,7 +6781,7 @@ async function applyNativePatch(params) {
|
|
|
5495
6781
|
const createArgs = ["patch", packageSpec];
|
|
5496
6782
|
let patchDir;
|
|
5497
6783
|
try {
|
|
5498
|
-
const createResult = await
|
|
6784
|
+
const createResult = await execa7(createCommand, createArgs, {
|
|
5499
6785
|
cwd,
|
|
5500
6786
|
stdio: "pipe"
|
|
5501
6787
|
});
|
|
@@ -5523,17 +6809,17 @@ ${createResult.stderr}`);
|
|
|
5523
6809
|
error: `Could not determine native patch directory for ${packageSpec}.`
|
|
5524
6810
|
};
|
|
5525
6811
|
}
|
|
5526
|
-
const tempPatchDir = await mkdtemp2(
|
|
5527
|
-
const tempPatchFile =
|
|
6812
|
+
const tempPatchDir = await mkdtemp2(join20(tmpdir2(), "autoremediator-native-patch-"));
|
|
6813
|
+
const tempPatchFile = join20(tempPatchDir, "change.patch");
|
|
5528
6814
|
try {
|
|
5529
|
-
await
|
|
5530
|
-
await
|
|
6815
|
+
await writeFile3(tempPatchFile, patchContent, "utf8");
|
|
6816
|
+
await execa7("patch", ["-p1", "-i", tempPatchFile], {
|
|
5531
6817
|
cwd: patchDir,
|
|
5532
6818
|
stdio: "pipe"
|
|
5533
6819
|
});
|
|
5534
6820
|
const commitCommand = patchMode === "native-pnpm" ? "pnpm" : "yarn";
|
|
5535
6821
|
const commitArgs = patchMode === "native-pnpm" ? ["patch-commit", patchDir] : ["patch-commit", "-s", patchDir];
|
|
5536
|
-
await
|
|
6822
|
+
await execa7(commitCommand, commitArgs, {
|
|
5537
6823
|
cwd,
|
|
5538
6824
|
stdio: "pipe"
|
|
5539
6825
|
});
|
|
@@ -5548,19 +6834,19 @@ ${createResult.stderr}`);
|
|
|
5548
6834
|
error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
|
|
5549
6835
|
};
|
|
5550
6836
|
} finally {
|
|
5551
|
-
await
|
|
6837
|
+
await rm6(tempPatchDir, { recursive: true, force: true });
|
|
5552
6838
|
}
|
|
5553
6839
|
return { success: true };
|
|
5554
6840
|
}
|
|
5555
6841
|
function extractPatchDirectory(output) {
|
|
5556
6842
|
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
5557
6843
|
for (const line of lines) {
|
|
5558
|
-
if (
|
|
6844
|
+
if (existsSync15(line)) {
|
|
5559
6845
|
return line;
|
|
5560
6846
|
}
|
|
5561
6847
|
const tokens = line.split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, ""));
|
|
5562
6848
|
for (const token of tokens) {
|
|
5563
|
-
if (token.startsWith("/") &&
|
|
6849
|
+
if (token.startsWith("/") && existsSync15(token)) {
|
|
5564
6850
|
return token;
|
|
5565
6851
|
}
|
|
5566
6852
|
}
|
|
@@ -5570,7 +6856,7 @@ function extractPatchDirectory(output) {
|
|
|
5570
6856
|
async function validatePatchWithTests(cwd, testCommand) {
|
|
5571
6857
|
try {
|
|
5572
6858
|
const [cmd, ...args] = testCommand;
|
|
5573
|
-
const result = await
|
|
6859
|
+
const result = await execa7(cmd, args, {
|
|
5574
6860
|
cwd,
|
|
5575
6861
|
timeout: 6e4,
|
|
5576
6862
|
stdio: "pipe"
|
|
@@ -5664,6 +6950,18 @@ var applyPatchFileTool = defineTool({
|
|
|
5664
6950
|
try {
|
|
5665
6951
|
const pm = packageManager ?? detectPackageManager(cwd);
|
|
5666
6952
|
const loadedPolicy = loadPolicy(cwd, policy);
|
|
6953
|
+
if (!isPackageAllowed(loadedPolicy, packageName)) {
|
|
6954
|
+
return {
|
|
6955
|
+
success: false,
|
|
6956
|
+
packageName,
|
|
6957
|
+
vulnerableVersion,
|
|
6958
|
+
applied: false,
|
|
6959
|
+
dryRun,
|
|
6960
|
+
unresolvedReason: "policy-blocked",
|
|
6961
|
+
message: `Policy denyPackages blocked changes for package "${packageName}".`,
|
|
6962
|
+
error: `Policy denyPackages blocked changes for package "${packageName}".`
|
|
6963
|
+
};
|
|
6964
|
+
}
|
|
5667
6965
|
const commandConstraints = {
|
|
5668
6966
|
...loadedPolicy.constraints,
|
|
5669
6967
|
installMode: installMode ?? loadedPolicy.constraints?.installMode,
|
|
@@ -5709,7 +7007,7 @@ var applyPatchFileTool = defineTool({
|
|
|
5709
7007
|
};
|
|
5710
7008
|
}
|
|
5711
7009
|
const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
|
|
5712
|
-
const patchFilePath =
|
|
7010
|
+
const patchFilePath = join21(cwd, patchesDir, patchFileName);
|
|
5713
7011
|
const manifestFilePath = `${patchFilePath}.json`;
|
|
5714
7012
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5715
7013
|
const baseArtifact = {
|
|
@@ -5748,9 +7046,11 @@ var applyPatchFileTool = defineTool({
|
|
|
5748
7046
|
}
|
|
5749
7047
|
return withRepoLock(cwd, async () => {
|
|
5750
7048
|
const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
|
|
5751
|
-
const patchesDirPath =
|
|
5752
|
-
await
|
|
5753
|
-
|
|
7049
|
+
const patchesDirPath = join21(cwd, patchesDir);
|
|
7050
|
+
await mkdir4(patchesDirPath, { recursive: true });
|
|
7051
|
+
recordFileTouch(patchFilePath);
|
|
7052
|
+
recordFileTouch(manifestFilePath);
|
|
7053
|
+
await writeFile4(patchFilePath, selectedPatch, "utf8");
|
|
5754
7054
|
validationPhases.push({
|
|
5755
7055
|
phase: "patch-write",
|
|
5756
7056
|
passed: true,
|
|
@@ -5773,21 +7073,19 @@ var applyPatchFileTool = defineTool({
|
|
|
5773
7073
|
await writePatchManifest(manifestFilePath, artifact);
|
|
5774
7074
|
let reproductionTestPath;
|
|
5775
7075
|
let negativePhaseResult;
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
}
|
|
5790
|
-
const isProofOfFixActive = Boolean(proofOfFix || requireProofOfFix);
|
|
7076
|
+
const preparedRepro = await prepareReproductionTestFile({
|
|
7077
|
+
cwd,
|
|
7078
|
+
packageName,
|
|
7079
|
+
vulnerableVersion,
|
|
7080
|
+
patchesDir: patchesDirPath,
|
|
7081
|
+
proofOfFix,
|
|
7082
|
+
reproductionTest
|
|
7083
|
+
});
|
|
7084
|
+
reproductionTestPath = preparedRepro.testFilePath;
|
|
7085
|
+
const hasReproductionScript = Boolean(reproductionTestPath);
|
|
7086
|
+
const isProofOfFixActive = Boolean(
|
|
7087
|
+
hasReproductionScript || proofOfFix || requireProofOfFix
|
|
7088
|
+
);
|
|
5791
7089
|
if (isProofOfFixActive) {
|
|
5792
7090
|
if (!reproductionTestPath) {
|
|
5793
7091
|
if (requireProofOfFix) {
|
|
@@ -5806,6 +7104,7 @@ var applyPatchFileTool = defineTool({
|
|
|
5806
7104
|
vulnerableVersion,
|
|
5807
7105
|
applied: false,
|
|
5808
7106
|
dryRun: false,
|
|
7107
|
+
unresolvedReason: "policy-blocked",
|
|
5809
7108
|
message: "Proof-of-fix verification required, but no reproduction test or script was supplied.",
|
|
5810
7109
|
patchFilePath,
|
|
5811
7110
|
manifestFilePath,
|
|
@@ -5833,7 +7132,7 @@ var applyPatchFileTool = defineTool({
|
|
|
5833
7132
|
message: negativePhaseResult.passed ? "Reproduction test reproduced vulnerability on unpatched code." : void 0,
|
|
5834
7133
|
error: negativePhaseResult.passed ? void 0 : negativePhaseResult.error
|
|
5835
7134
|
});
|
|
5836
|
-
if (!negativePhaseResult.passed
|
|
7135
|
+
if (!negativePhaseResult.passed) {
|
|
5837
7136
|
await cleanupPatchArtifacts({
|
|
5838
7137
|
cwd,
|
|
5839
7138
|
patchFilePath,
|
|
@@ -5849,6 +7148,7 @@ var applyPatchFileTool = defineTool({
|
|
|
5849
7148
|
vulnerableVersion,
|
|
5850
7149
|
applied: false,
|
|
5851
7150
|
dryRun: false,
|
|
7151
|
+
unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
|
|
5852
7152
|
message: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`,
|
|
5853
7153
|
patchFilePath,
|
|
5854
7154
|
manifestFilePath,
|
|
@@ -5913,7 +7213,7 @@ var applyPatchFileTool = defineTool({
|
|
|
5913
7213
|
if (patchMode === "patch-package") {
|
|
5914
7214
|
try {
|
|
5915
7215
|
const [installCmd, ...installArgs] = installCommand;
|
|
5916
|
-
await
|
|
7216
|
+
await execa8(installCmd, installArgs, {
|
|
5917
7217
|
cwd,
|
|
5918
7218
|
stdio: "pipe"
|
|
5919
7219
|
});
|
|
@@ -6007,66 +7307,49 @@ var applyPatchFileTool = defineTool({
|
|
|
6007
7307
|
});
|
|
6008
7308
|
}
|
|
6009
7309
|
let positivePhaseResult;
|
|
6010
|
-
if (reproductionTestPath) {
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
7310
|
+
if (reproductionTestPath && isProofOfFixActive) {
|
|
7311
|
+
positivePhaseResult = await runProofOfFixPhase({
|
|
7312
|
+
stage: "positive",
|
|
7313
|
+
testFilePath: reproductionTestPath,
|
|
7314
|
+
cwd
|
|
7315
|
+
});
|
|
7316
|
+
validationPhases.push({
|
|
7317
|
+
phase: "proof-of-fix-positive",
|
|
7318
|
+
passed: positivePhaseResult.passed,
|
|
7319
|
+
message: positivePhaseResult.passed ? "Reproduction test passed on patched code (vulnerability mitigated)." : void 0,
|
|
7320
|
+
error: positivePhaseResult.passed ? void 0 : positivePhaseResult.error
|
|
7321
|
+
});
|
|
7322
|
+
if (!positivePhaseResult.passed) {
|
|
7323
|
+
await cleanupPatchArtifacts({
|
|
7324
|
+
cwd,
|
|
7325
|
+
patchFilePath,
|
|
7326
|
+
manifestFilePath,
|
|
7327
|
+
patchMode,
|
|
7328
|
+
packageJsonSnapshot,
|
|
7329
|
+
installCommand,
|
|
7330
|
+
rerunInstall: patchMode === "patch-package"
|
|
6022
7331
|
});
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
7332
|
+
return {
|
|
7333
|
+
success: false,
|
|
7334
|
+
packageName,
|
|
7335
|
+
vulnerableVersion,
|
|
7336
|
+
applied: false,
|
|
7337
|
+
dryRun: false,
|
|
7338
|
+
unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
|
|
7339
|
+
message: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`,
|
|
7340
|
+
patchFilePath,
|
|
7341
|
+
manifestFilePath,
|
|
7342
|
+
patchPath: patchFilePath,
|
|
7343
|
+
patchMode,
|
|
7344
|
+
postinstallConfigured: false,
|
|
7345
|
+
patchArtifact: {
|
|
7346
|
+
...artifact,
|
|
6037
7347
|
applied: false,
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
patchMode,
|
|
6044
|
-
postinstallConfigured: false,
|
|
6045
|
-
patchArtifact: {
|
|
6046
|
-
...artifact,
|
|
6047
|
-
applied: false,
|
|
6048
|
-
validationPhases
|
|
6049
|
-
},
|
|
6050
|
-
validationPhases,
|
|
6051
|
-
error: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`
|
|
6052
|
-
};
|
|
6053
|
-
}
|
|
6054
|
-
} else {
|
|
6055
|
-
try {
|
|
6056
|
-
const reproRun = await execa9("node", [reproductionTestPath], { cwd, stdio: "pipe" });
|
|
6057
|
-
validationPhases.push({
|
|
6058
|
-
phase: "reproduction-test",
|
|
6059
|
-
passed: reproRun.exitCode === 0,
|
|
6060
|
-
message: "Reproduction test verified fix successfully."
|
|
6061
|
-
});
|
|
6062
|
-
} catch (err) {
|
|
6063
|
-
const reproErr = err instanceof Error ? err.message : String(err);
|
|
6064
|
-
validationPhases.push({
|
|
6065
|
-
phase: "reproduction-test",
|
|
6066
|
-
passed: false,
|
|
6067
|
-
error: `Reproduction test failed: ${reproErr}`
|
|
6068
|
-
});
|
|
6069
|
-
}
|
|
7348
|
+
validationPhases
|
|
7349
|
+
},
|
|
7350
|
+
validationPhases,
|
|
7351
|
+
error: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`
|
|
7352
|
+
};
|
|
6070
7353
|
}
|
|
6071
7354
|
}
|
|
6072
7355
|
let proofOfFixVerification;
|
|
@@ -6198,12 +7481,29 @@ function resolvePatchProvider(provider) {
|
|
|
6198
7481
|
function shouldAttemptPatchFallback(result, preferVersionBump) {
|
|
6199
7482
|
if (preferVersionBump) return false;
|
|
6200
7483
|
if (result.applied || result.dryRun) return false;
|
|
6201
|
-
return result.unresolvedReason === "no-safe-version" || result.unresolvedReason === "install-failed" || result.unresolvedReason === "override-apply-failed" || result.unresolvedReason === "validation-failed" || result.unresolvedReason === "major-bump-required" || result.unresolvedReason === "transitive-dependency";
|
|
7484
|
+
return result.unresolvedReason === "no-safe-version" || result.unresolvedReason === "install-failed" || result.unresolvedReason === "override-apply-failed" || result.unresolvedReason === "validation-failed" || result.unresolvedReason === "major-bump-required" || result.unresolvedReason === "transitive-dependency" || result.unresolvedReason === "proof-of-fix-failed";
|
|
6202
7485
|
}
|
|
6203
7486
|
async function tryLocalPatchFallback(params) {
|
|
6204
7487
|
const usage = [];
|
|
6205
7488
|
let steps = 0;
|
|
6206
7489
|
let consensusVerdict;
|
|
7490
|
+
const loadedPolicy = loadPolicy(params.cwd, params.policy);
|
|
7491
|
+
if (!isPackageAllowed(loadedPolicy, params.packageName)) {
|
|
7492
|
+
return {
|
|
7493
|
+
steps: 0,
|
|
7494
|
+
usage,
|
|
7495
|
+
result: {
|
|
7496
|
+
packageName: params.packageName,
|
|
7497
|
+
strategy: "none",
|
|
7498
|
+
fromVersion: params.vulnerableVersion,
|
|
7499
|
+
applied: false,
|
|
7500
|
+
dryRun: params.dryRun,
|
|
7501
|
+
dependencyScope: params.dependencyScope,
|
|
7502
|
+
unresolvedReason: "policy-blocked",
|
|
7503
|
+
message: `Policy denyPackages blocked patch fallback for package "${params.packageName}".`
|
|
7504
|
+
}
|
|
7505
|
+
};
|
|
7506
|
+
}
|
|
6207
7507
|
const sourceResult = await fetchPackageSourceTool.execute({
|
|
6208
7508
|
packageName: params.packageName,
|
|
6209
7509
|
version: params.vulnerableVersion
|
|
@@ -6382,7 +7682,7 @@ async function tryLocalPatchFallback(params) {
|
|
|
6382
7682
|
riskLevel: patchResult.riskLevel,
|
|
6383
7683
|
vulnerableRange: params.vulnerableRange,
|
|
6384
7684
|
consensusVerdict,
|
|
6385
|
-
unresolvedReason: !applyResult.applied && !applyResult.dryRun ? applyResult.validation?.passed === false ? "patch-validation-failed" : "patch-apply-failed" : void 0,
|
|
7685
|
+
unresolvedReason: !applyResult.applied && !applyResult.dryRun ? applyResult.unresolvedReason ?? (applyResult.validation?.passed === false ? "patch-validation-failed" : "patch-apply-failed") : void 0,
|
|
6386
7686
|
message: applyResult.message ?? applyResult.error ?? "Patch-file strategy finished.",
|
|
6387
7687
|
validation: typeof applyResult.validation?.passed === "boolean" ? {
|
|
6388
7688
|
passed: applyResult.validation.passed,
|
|
@@ -6448,7 +7748,7 @@ function resolveLocalRunOptions(options) {
|
|
|
6448
7748
|
}
|
|
6449
7749
|
|
|
6450
7750
|
// src/remediation/local/vulnerability-match.ts
|
|
6451
|
-
import
|
|
7751
|
+
import semver6 from "semver";
|
|
6452
7752
|
function findVulnerablePackages(cveDetails, installedPackages) {
|
|
6453
7753
|
const vulnerablePackages = [];
|
|
6454
7754
|
for (const affected of cveDetails.affectedPackages) {
|
|
@@ -6457,10 +7757,10 @@ function findVulnerablePackages(cveDetails, installedPackages) {
|
|
|
6457
7757
|
if (affected.ecosystem !== "npm") continue;
|
|
6458
7758
|
const matches = installedPackages.filter((pkg) => pkg.name === affected.name);
|
|
6459
7759
|
for (const installed of matches) {
|
|
6460
|
-
if (!
|
|
7760
|
+
if (!semver6.valid(installed.version)) continue;
|
|
6461
7761
|
let isVulnerable = false;
|
|
6462
7762
|
try {
|
|
6463
|
-
isVulnerable =
|
|
7763
|
+
isVulnerable = semver6.satisfies(installed.version, affected.vulnerableRange, {
|
|
6464
7764
|
includePrerelease: false
|
|
6465
7765
|
});
|
|
6466
7766
|
} catch {
|
|
@@ -6555,6 +7855,15 @@ function applyDispositionAndContainment(result, context) {
|
|
|
6555
7855
|
dispositionReason: disposition.dispositionReason
|
|
6556
7856
|
};
|
|
6557
7857
|
if (context.containmentMode && withDisposition.disposition === "escalate" && withDisposition.applied) {
|
|
7858
|
+
if (context.snapshot) {
|
|
7859
|
+
restoreProjectSnapshotSync(context.snapshot);
|
|
7860
|
+
}
|
|
7861
|
+
if (context.onContainmentRollback) {
|
|
7862
|
+
try {
|
|
7863
|
+
context.onContainmentRollback();
|
|
7864
|
+
} catch {
|
|
7865
|
+
}
|
|
7866
|
+
}
|
|
6558
7867
|
return {
|
|
6559
7868
|
...withDisposition,
|
|
6560
7869
|
applied: false,
|
|
@@ -6586,35 +7895,7 @@ function computeEscalationAction(reason, graph) {
|
|
|
6586
7895
|
// src/remediation/local/run.ts
|
|
6587
7896
|
async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
6588
7897
|
const resolved = resolveLocalRunOptions(options);
|
|
6589
|
-
const {
|
|
6590
|
-
cwd,
|
|
6591
|
-
packageManager,
|
|
6592
|
-
dryRun,
|
|
6593
|
-
runTests,
|
|
6594
|
-
policy,
|
|
6595
|
-
patchesDir,
|
|
6596
|
-
constraints,
|
|
6597
|
-
llmProvider,
|
|
6598
|
-
providerSafetyProfile,
|
|
6599
|
-
requireConsensusForHighRisk,
|
|
6600
|
-
consensusProvider,
|
|
6601
|
-
consensusModel,
|
|
6602
|
-
patchConfidenceThresholds,
|
|
6603
|
-
dynamicModelRouting,
|
|
6604
|
-
dynamicRoutingThresholdChars,
|
|
6605
|
-
exploitSignalOverride,
|
|
6606
|
-
suppressions,
|
|
6607
|
-
suppressionsFile,
|
|
6608
|
-
slaCheck,
|
|
6609
|
-
slaPolicy,
|
|
6610
|
-
skipUnreachable,
|
|
6611
|
-
regressionCheck,
|
|
6612
|
-
escalationGraph,
|
|
6613
|
-
offlineIntelligence,
|
|
6614
|
-
intelligenceSnapshotPath,
|
|
6615
|
-
proofOfFix,
|
|
6616
|
-
requireProofOfFix
|
|
6617
|
-
} = resolved;
|
|
7898
|
+
const { cwd, packageManager, dryRun, constraints } = resolved;
|
|
6618
7899
|
const correlation = {
|
|
6619
7900
|
requestId: options.requestId,
|
|
6620
7901
|
sessionId: options.sessionId,
|
|
@@ -6628,10 +7909,10 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
6628
7909
|
const normalizedId = cveId.toUpperCase();
|
|
6629
7910
|
let osvDetails = null;
|
|
6630
7911
|
let ghPackages = [];
|
|
6631
|
-
if (offlineIntelligence) {
|
|
6632
|
-
if (intelligenceSnapshotPath) {
|
|
7912
|
+
if (resolved.offlineIntelligence) {
|
|
7913
|
+
if (resolved.intelligenceSnapshotPath) {
|
|
6633
7914
|
osvDetails = lookupSnapshotCve(
|
|
6634
|
-
loadIntelligenceSnapshot(intelligenceSnapshotPath),
|
|
7915
|
+
loadIntelligenceSnapshot(resolved.intelligenceSnapshotPath),
|
|
6635
7916
|
normalizedId
|
|
6636
7917
|
);
|
|
6637
7918
|
}
|
|
@@ -6649,7 +7930,7 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
6649
7930
|
vulnerablePackages,
|
|
6650
7931
|
results: collectedResults,
|
|
6651
7932
|
agentSteps,
|
|
6652
|
-
summary: offlineIntelligence ? `Offline mode: ${normalizedId} not found in intelligence snapshot.` : `Local mode failed at lookup-cve: ${normalizedId} not found in OSV or GitHub advisory data.`,
|
|
7933
|
+
summary: resolved.offlineIntelligence ? `Offline mode: ${normalizedId} not found in intelligence snapshot.` : `Local mode failed at lookup-cve: ${normalizedId} not found in OSV or GitHub advisory data.`,
|
|
6653
7934
|
correlation
|
|
6654
7935
|
};
|
|
6655
7936
|
}
|
|
@@ -6663,16 +7944,10 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
6663
7944
|
if (ghPackages.length > 0) {
|
|
6664
7945
|
cveDetails = mergeGhDataIntoCveDetails(cveDetails, ghPackages);
|
|
6665
7946
|
}
|
|
6666
|
-
if (!offlineIntelligence) {
|
|
7947
|
+
if (!resolved.offlineIntelligence) {
|
|
6667
7948
|
cveDetails = await enrichWithNvd(cveDetails);
|
|
6668
7949
|
}
|
|
6669
|
-
const preflight = await runSecOpsPreflight(normalizedId, cveDetails,
|
|
6670
|
-
suppressions,
|
|
6671
|
-
suppressionsFile,
|
|
6672
|
-
exploitSignalOverride,
|
|
6673
|
-
slaCheck,
|
|
6674
|
-
slaPolicy
|
|
6675
|
-
});
|
|
7950
|
+
const preflight = await runSecOpsPreflight(normalizedId, cveDetails, resolved);
|
|
6676
7951
|
if (preflight.suppressed) {
|
|
6677
7952
|
return {
|
|
6678
7953
|
cveId,
|
|
@@ -6721,35 +7996,57 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
6721
7996
|
const installedPackages = inventory.packages ?? [];
|
|
6722
7997
|
vulnerablePackages = findVulnerablePackages(cveDetails, installedPackages);
|
|
6723
7998
|
agentSteps += 1;
|
|
6724
|
-
const
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
slaBreaches,
|
|
6728
|
-
severity: cveDetails?.severity,
|
|
6729
|
-
policy: options.dispositionPolicy,
|
|
6730
|
-
containmentMode: options.containmentMode
|
|
6731
|
-
});
|
|
6732
|
-
if (withDisposition.unresolvedReason) {
|
|
6733
|
-
withDisposition.escalationAction = computeEscalationAction(
|
|
6734
|
-
withDisposition.unresolvedReason,
|
|
6735
|
-
escalationGraph
|
|
6736
|
-
);
|
|
6737
|
-
}
|
|
6738
|
-
return withDisposition;
|
|
6739
|
-
};
|
|
7999
|
+
const effectivePolicy = loadPolicy(cwd, options.policy);
|
|
8000
|
+
const containmentMode = options.containmentMode ?? effectivePolicy.containmentMode ?? false;
|
|
8001
|
+
const dispositionPolicy = options.dispositionPolicy ?? effectivePolicy.dispositionPolicy;
|
|
6740
8002
|
for (const vulnerable of vulnerablePackages) {
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
8003
|
+
const snapshot = await createProjectSnapshot(cwd);
|
|
8004
|
+
const finalizePatchResult = (result) => {
|
|
8005
|
+
const withDisposition = applyDispositionAndContainment(result, {
|
|
8006
|
+
exploitSignalTriggered,
|
|
8007
|
+
slaBreaches,
|
|
8008
|
+
severity: cveDetails?.severity,
|
|
8009
|
+
policy: dispositionPolicy,
|
|
8010
|
+
containmentMode,
|
|
8011
|
+
cwd,
|
|
8012
|
+
snapshot
|
|
8013
|
+
});
|
|
8014
|
+
if (withDisposition.unresolvedReason) {
|
|
8015
|
+
withDisposition.escalationAction = computeEscalationAction(
|
|
8016
|
+
withDisposition.unresolvedReason,
|
|
8017
|
+
resolved.escalationGraph
|
|
8018
|
+
);
|
|
8019
|
+
}
|
|
8020
|
+
return withDisposition;
|
|
8021
|
+
};
|
|
8022
|
+
if (!isPackageAllowed(effectivePolicy, vulnerable.installed.name)) {
|
|
8023
|
+
collectedResults.push(
|
|
8024
|
+
finalizePatchResult({
|
|
6745
8025
|
packageName: vulnerable.installed.name,
|
|
6746
8026
|
fromVersion: vulnerable.installed.version,
|
|
6747
8027
|
strategy: "none",
|
|
6748
8028
|
applied: false,
|
|
6749
8029
|
dryRun,
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
})
|
|
8030
|
+
unresolvedReason: "policy-blocked",
|
|
8031
|
+
message: `Policy denyPackages blocked changes for package "${vulnerable.installed.name}".`
|
|
8032
|
+
})
|
|
8033
|
+
);
|
|
8034
|
+
continue;
|
|
8035
|
+
}
|
|
8036
|
+
if (resolved.skipUnreachable) {
|
|
8037
|
+
const reach = assessPackageReachability(cwd, vulnerable.installed.name);
|
|
8038
|
+
if (reach.status === "not-reachable") {
|
|
8039
|
+
collectedResults.push(
|
|
8040
|
+
finalizePatchResult({
|
|
8041
|
+
packageName: vulnerable.installed.name,
|
|
8042
|
+
fromVersion: vulnerable.installed.version,
|
|
8043
|
+
strategy: "none",
|
|
8044
|
+
applied: false,
|
|
8045
|
+
dryRun,
|
|
8046
|
+
message: `Skipped: '${vulnerable.installed.name}' is not reachable from source code.`,
|
|
8047
|
+
reachability: reach
|
|
8048
|
+
})
|
|
8049
|
+
);
|
|
6753
8050
|
continue;
|
|
6754
8051
|
}
|
|
6755
8052
|
}
|
|
@@ -6758,9 +8055,13 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
6758
8055
|
cwd,
|
|
6759
8056
|
packageManager,
|
|
6760
8057
|
dryRun,
|
|
6761
|
-
policy,
|
|
6762
|
-
runTests,
|
|
6763
|
-
constraints
|
|
8058
|
+
policy: resolved.policy,
|
|
8059
|
+
runTests: resolved.runTests,
|
|
8060
|
+
constraints,
|
|
8061
|
+
proofOfFix: resolved.proofOfFix,
|
|
8062
|
+
requireProofOfFix: resolved.requireProofOfFix,
|
|
8063
|
+
reproductionTest: options.reproductionTest,
|
|
8064
|
+
patchesDir: resolved.patchesDir
|
|
6764
8065
|
});
|
|
6765
8066
|
agentSteps += primary.steps;
|
|
6766
8067
|
if (shouldAttemptPatchFallback(primary.result, constraints.preferVersionBump ?? false)) {
|
|
@@ -6773,26 +8074,26 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
6773
8074
|
cveSummary: cveDetails?.summary ?? normalizedId,
|
|
6774
8075
|
dependencyScope: vulnerable.installed.type === "direct" ? "direct" : "transitive",
|
|
6775
8076
|
dryRun,
|
|
6776
|
-
runTests,
|
|
6777
|
-
patchesDir,
|
|
6778
|
-
llmProvider,
|
|
8077
|
+
runTests: resolved.runTests,
|
|
8078
|
+
patchesDir: resolved.patchesDir,
|
|
8079
|
+
llmProvider: resolved.llmProvider,
|
|
6779
8080
|
model: options.model,
|
|
6780
8081
|
policy: options.policy,
|
|
6781
8082
|
modelPersonality: options.modelPersonality,
|
|
6782
|
-
providerSafetyProfile,
|
|
6783
|
-
requireConsensusForHighRisk,
|
|
6784
|
-
consensusProvider,
|
|
6785
|
-
consensusModel,
|
|
6786
|
-
patchConfidenceThresholds,
|
|
6787
|
-
dynamicModelRouting,
|
|
6788
|
-
dynamicRoutingThresholdChars,
|
|
8083
|
+
providerSafetyProfile: resolved.providerSafetyProfile,
|
|
8084
|
+
requireConsensusForHighRisk: resolved.requireConsensusForHighRisk,
|
|
8085
|
+
consensusProvider: resolved.consensusProvider,
|
|
8086
|
+
consensusModel: resolved.consensusModel,
|
|
8087
|
+
patchConfidenceThresholds: resolved.patchConfidenceThresholds,
|
|
8088
|
+
dynamicModelRouting: resolved.dynamicModelRouting,
|
|
8089
|
+
dynamicRoutingThresholdChars: resolved.dynamicRoutingThresholdChars,
|
|
6789
8090
|
installMode: constraints.installMode,
|
|
6790
8091
|
installPreferOffline: constraints.installPreferOffline,
|
|
6791
8092
|
enforceFrozenLockfile: constraints.enforceFrozenLockfile,
|
|
6792
8093
|
workspace: constraints.workspace,
|
|
6793
8094
|
vulnerableRange: vulnerable.affected.vulnerableRange,
|
|
6794
|
-
proofOfFix,
|
|
6795
|
-
requireProofOfFix
|
|
8095
|
+
proofOfFix: resolved.proofOfFix,
|
|
8096
|
+
requireProofOfFix: resolved.requireProofOfFix
|
|
6796
8097
|
});
|
|
6797
8098
|
agentSteps += fallback.steps;
|
|
6798
8099
|
collectedResults.push(finalizePatchResult(fallback.result));
|
|
@@ -6805,14 +8106,15 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
6805
8106
|
...primary.result,
|
|
6806
8107
|
dependencyScope: vulnerable.installed.type === "direct" ? "direct" : "transitive"
|
|
6807
8108
|
};
|
|
6808
|
-
if (regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
|
|
8109
|
+
if (resolved.regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
|
|
6809
8110
|
try {
|
|
6810
|
-
if (
|
|
8111
|
+
if (semver7.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
|
|
6811
8112
|
includePrerelease: false
|
|
6812
8113
|
})) {
|
|
6813
8114
|
primaryResult.regressionDetected = true;
|
|
6814
8115
|
}
|
|
6815
8116
|
} catch {
|
|
8117
|
+
primaryResult.regressionDetected = false;
|
|
6816
8118
|
}
|
|
6817
8119
|
}
|
|
6818
8120
|
collectedResults.push(finalizePatchResult(primaryResult));
|
|
@@ -6898,8 +8200,8 @@ function accumulateStepResults(params) {
|
|
|
6898
8200
|
}
|
|
6899
8201
|
|
|
6900
8202
|
// src/remediation/orchestration-prompt.ts
|
|
6901
|
-
import { existsSync as
|
|
6902
|
-
import { join as
|
|
8203
|
+
import { existsSync as existsSync16, readFileSync as readFileSync17 } from "fs";
|
|
8204
|
+
import { join as join22 } from "path";
|
|
6903
8205
|
function buildProviderAddendum(provider, personality = "balanced") {
|
|
6904
8206
|
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.";
|
|
6905
8207
|
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.";
|
|
@@ -6910,13 +8212,13 @@ Provider profile:
|
|
|
6910
8212
|
- ${personalityDirective}`;
|
|
6911
8213
|
}
|
|
6912
8214
|
function loadOrchestrationPrompt(ctx) {
|
|
6913
|
-
const promptPath =
|
|
8215
|
+
const promptPath = join22(
|
|
6914
8216
|
process.cwd(),
|
|
6915
8217
|
".github",
|
|
6916
8218
|
"instructions",
|
|
6917
8219
|
"orchestration.instructions.md"
|
|
6918
8220
|
);
|
|
6919
|
-
if (!
|
|
8221
|
+
if (!existsSync16(promptPath)) {
|
|
6920
8222
|
return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
|
|
6921
8223
|
Working directory: ${ctx.cwd}
|
|
6922
8224
|
Package manager: ${ctx.packageManager}
|
|
@@ -6942,7 +8244,7 @@ Fallback sequence (when neither version bump nor override can be applied):
|
|
|
6942
8244
|
|
|
6943
8245
|
Always respect dryRun and policy constraints.`;
|
|
6944
8246
|
}
|
|
6945
|
-
const template =
|
|
8247
|
+
const template = readFileSync17(promptPath, "utf8");
|
|
6946
8248
|
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(
|
|
6947
8249
|
"{{directDependenciesOnly}}",
|
|
6948
8250
|
String(ctx.constraints.directDependenciesOnly ?? false)
|
|
@@ -6963,6 +8265,9 @@ function createProgressEmitter(options) {
|
|
|
6963
8265
|
};
|
|
6964
8266
|
}
|
|
6965
8267
|
|
|
8268
|
+
// src/remediation/pipeline-runtime/tool-wrappers.ts
|
|
8269
|
+
import { existsSync as existsSync17 } from "fs";
|
|
8270
|
+
|
|
6966
8271
|
// src/remediation/tools/lookup-cve.ts
|
|
6967
8272
|
import { z as z9 } from "zod";
|
|
6968
8273
|
|
|
@@ -7249,7 +8554,7 @@ var lookupCveTool = defineTool({
|
|
|
7249
8554
|
|
|
7250
8555
|
// src/remediation/tools/check-version-match.ts
|
|
7251
8556
|
import { z as z10 } from "zod";
|
|
7252
|
-
import
|
|
8557
|
+
import semver8 from "semver";
|
|
7253
8558
|
var affectedPackageSchema = z10.object({
|
|
7254
8559
|
name: z10.string(),
|
|
7255
8560
|
ecosystem: z10.literal("npm"),
|
|
@@ -7278,10 +8583,10 @@ var checkVersionMatchTool = defineTool({
|
|
|
7278
8583
|
(p) => p.name === affected.name
|
|
7279
8584
|
);
|
|
7280
8585
|
for (const installed of matches) {
|
|
7281
|
-
if (!
|
|
8586
|
+
if (!semver8.valid(installed.version)) continue;
|
|
7282
8587
|
let isVulnerable = false;
|
|
7283
8588
|
try {
|
|
7284
|
-
isVulnerable =
|
|
8589
|
+
isVulnerable = semver8.satisfies(installed.version, affected.vulnerableRange, {
|
|
7285
8590
|
includePrerelease: false
|
|
7286
8591
|
});
|
|
7287
8592
|
} catch {
|
|
@@ -7301,6 +8606,7 @@ var checkVersionMatchTool = defineTool({
|
|
|
7301
8606
|
|
|
7302
8607
|
// src/remediation/tools/find-fixed-version.ts
|
|
7303
8608
|
import { z as z11 } from "zod";
|
|
8609
|
+
import semver9 from "semver";
|
|
7304
8610
|
var findFixedVersionTool = defineTool({
|
|
7305
8611
|
description: "Query the npm registry to find the safest published upgrade version for a package that is >= the first patched version. Prefer patch upgrades first, then minor, and only fall back to major when no same-major fix exists.",
|
|
7306
8612
|
parameters: z11.object({
|
|
@@ -7332,9 +8638,11 @@ var findFixedVersionTool = defineTool({
|
|
|
7332
8638
|
message: `No safe upgrade version found for "${packageName}". The patch-file path will be needed.`
|
|
7333
8639
|
};
|
|
7334
8640
|
}
|
|
7335
|
-
const
|
|
7336
|
-
const
|
|
7337
|
-
const isMajorBump =
|
|
8641
|
+
const parsedInstalled = semver9.parse(installedVersion) ?? semver9.coerce(installedVersion);
|
|
8642
|
+
const parsedSafe = semver9.parse(safeVersion) ?? semver9.coerce(safeVersion);
|
|
8643
|
+
const isMajorBump = Boolean(
|
|
8644
|
+
parsedInstalled && parsedSafe && parsedSafe.major > parsedInstalled.major
|
|
8645
|
+
);
|
|
7338
8646
|
return {
|
|
7339
8647
|
safeVersion,
|
|
7340
8648
|
upgradeLevel,
|
|
@@ -7423,6 +8731,10 @@ function createRuntimeToolsForRun(options) {
|
|
|
7423
8731
|
workspace: constraints.workspace,
|
|
7424
8732
|
typecheck: input.typecheck ?? options.typecheck ?? constraints.typecheck,
|
|
7425
8733
|
breakingChangeRefactor: input.breakingChangeRefactor ?? options.breakingChangeRefactor ?? constraints.enableBreakingChangeRefactor,
|
|
8734
|
+
proofOfFix: input.proofOfFix ?? options.proofOfFix,
|
|
8735
|
+
requireProofOfFix: input.requireProofOfFix ?? options.requireProofOfFix ?? constraints.requireProofOfFix,
|
|
8736
|
+
reproductionTest: input.reproductionTest ?? options.reproductionTest,
|
|
8737
|
+
patchesDir: input.patchesDir ?? options.patchesDir,
|
|
7426
8738
|
dryRun: preview ? true : input.dryRun
|
|
7427
8739
|
})
|
|
7428
8740
|
};
|
|
@@ -7435,6 +8747,10 @@ function createRuntimeToolsForRun(options) {
|
|
|
7435
8747
|
installPreferOffline: constraints.installPreferOffline,
|
|
7436
8748
|
enforceFrozenLockfile: constraints.enforceFrozenLockfile,
|
|
7437
8749
|
workspace: constraints.workspace,
|
|
8750
|
+
proofOfFix: input.proofOfFix ?? options.proofOfFix,
|
|
8751
|
+
requireProofOfFix: input.requireProofOfFix ?? options.requireProofOfFix ?? constraints.requireProofOfFix,
|
|
8752
|
+
reproductionTest: input.reproductionTest ?? options.reproductionTest,
|
|
8753
|
+
patchesDir: input.patchesDir ?? options.patchesDir,
|
|
7438
8754
|
dryRun: preview ? true : input.dryRun
|
|
7439
8755
|
})
|
|
7440
8756
|
};
|
|
@@ -7449,6 +8765,8 @@ function createRuntimeToolsForRun(options) {
|
|
|
7449
8765
|
workspace: constraints.workspace,
|
|
7450
8766
|
proofOfFix: input.proofOfFix ?? options.proofOfFix,
|
|
7451
8767
|
requireProofOfFix: input.requireProofOfFix ?? options.requireProofOfFix ?? constraints.requireProofOfFix,
|
|
8768
|
+
reproductionTest: input.reproductionTest ?? options.reproductionTest,
|
|
8769
|
+
patchesDir: input.patchesDir ?? options.patchesDir,
|
|
7452
8770
|
dryRun: preview ? true : input.dryRun
|
|
7453
8771
|
})
|
|
7454
8772
|
};
|
|
@@ -7460,15 +8778,50 @@ function createRuntimeToolsForRun(options) {
|
|
|
7460
8778
|
constraints
|
|
7461
8779
|
});
|
|
7462
8780
|
if (options.offlineIntelligence) {
|
|
7463
|
-
|
|
8781
|
+
let snapshot = null;
|
|
8782
|
+
if (options.intelligenceSnapshotPath && existsSync17(options.intelligenceSnapshotPath)) {
|
|
8783
|
+
try {
|
|
8784
|
+
snapshot = loadIntelligenceSnapshot(options.intelligenceSnapshotPath);
|
|
8785
|
+
} catch (err) {
|
|
8786
|
+
const _ignored = err;
|
|
8787
|
+
snapshot = null;
|
|
8788
|
+
}
|
|
8789
|
+
}
|
|
7464
8790
|
tools["lookup-cve"] = {
|
|
7465
8791
|
...tools["lookup-cve"],
|
|
7466
|
-
execute: async (_input) =>
|
|
7467
|
-
cveId
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
8792
|
+
execute: async (_input) => {
|
|
8793
|
+
const cveId = (_input.cveId ?? "unknown").toUpperCase();
|
|
8794
|
+
if (snapshot) {
|
|
8795
|
+
const details = lookupSnapshotCve(snapshot, cveId);
|
|
8796
|
+
if (details) {
|
|
8797
|
+
return {
|
|
8798
|
+
success: true,
|
|
8799
|
+
found: true,
|
|
8800
|
+
cveId,
|
|
8801
|
+
offlineMode: true,
|
|
8802
|
+
data: details,
|
|
8803
|
+
message: `Offline mode: intelligence lookup resolved from snapshot for ${cveId}.`
|
|
8804
|
+
};
|
|
8805
|
+
}
|
|
8806
|
+
return {
|
|
8807
|
+
success: false,
|
|
8808
|
+
found: false,
|
|
8809
|
+
cveId,
|
|
8810
|
+
offlineMode: true,
|
|
8811
|
+
error: `Offline mode: ${cveId} not found in intelligence snapshot.`,
|
|
8812
|
+
message: `Offline mode: ${cveId} not found in intelligence snapshot.`
|
|
8813
|
+
};
|
|
8814
|
+
}
|
|
8815
|
+
const snapshotErrorMsg = options.intelligenceSnapshotPath ? `Offline mode: intelligence snapshot file not found or invalid at ${options.intelligenceSnapshotPath}.` : `Offline mode: intelligence lookup skipped (no snapshot provided).`;
|
|
8816
|
+
return {
|
|
8817
|
+
success: false,
|
|
8818
|
+
found: false,
|
|
8819
|
+
cveId,
|
|
8820
|
+
offlineMode: true,
|
|
8821
|
+
error: snapshotErrorMsg,
|
|
8822
|
+
message: snapshotErrorMsg
|
|
8823
|
+
};
|
|
8824
|
+
}
|
|
7472
8825
|
};
|
|
7473
8826
|
}
|
|
7474
8827
|
return tools;
|
|
@@ -7603,7 +8956,7 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
7603
8956
|
}
|
|
7604
8957
|
|
|
7605
8958
|
// src/api/change-request/index.ts
|
|
7606
|
-
import { execa as
|
|
8959
|
+
import { execa as execa9 } from "execa";
|
|
7607
8960
|
import { randomBytes } from "crypto";
|
|
7608
8961
|
function sanitizeBranchToken(value) {
|
|
7609
8962
|
return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
@@ -7686,14 +9039,14 @@ function groupReports(reports, grouping) {
|
|
|
7686
9039
|
return [reports];
|
|
7687
9040
|
}
|
|
7688
9041
|
async function detectCurrentBranch(cwd) {
|
|
7689
|
-
const result = await
|
|
9042
|
+
const result = await execa9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
7690
9043
|
cwd,
|
|
7691
9044
|
stdio: "pipe"
|
|
7692
9045
|
});
|
|
7693
9046
|
return result.stdout.trim();
|
|
7694
9047
|
}
|
|
7695
9048
|
async function ensureRepoHasChanges(cwd) {
|
|
7696
|
-
const diff = await
|
|
9049
|
+
const diff = await execa9("git", ["status", "--porcelain"], {
|
|
7697
9050
|
cwd,
|
|
7698
9051
|
stdio: "pipe"
|
|
7699
9052
|
});
|
|
@@ -7703,7 +9056,59 @@ function toBranchName(prefix, cveIds) {
|
|
|
7703
9056
|
const token = sanitizeBranchToken(cveIds.join("-") || "remediation");
|
|
7704
9057
|
return `${sanitizeBranchToken(prefix)}/${token}-${randomBytes(6).toString("hex")}`;
|
|
7705
9058
|
}
|
|
9059
|
+
async function detectRepoFromRemote(cwd) {
|
|
9060
|
+
const result = await execa9("git", ["config", "--get", "remote.origin.url"], {
|
|
9061
|
+
cwd,
|
|
9062
|
+
stdio: "pipe"
|
|
9063
|
+
}).catch(() => ({ stdout: "" }));
|
|
9064
|
+
const url = result.stdout.trim();
|
|
9065
|
+
const match = url.match(/[:/]([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
9066
|
+
if (match) {
|
|
9067
|
+
return `${match[1]}/${match[2]}`;
|
|
9068
|
+
}
|
|
9069
|
+
return void 0;
|
|
9070
|
+
}
|
|
9071
|
+
async function ensureRepoHasStagedChanges(cwd) {
|
|
9072
|
+
const result = await execa9("git", ["diff", "--cached", "--name-only"], {
|
|
9073
|
+
cwd,
|
|
9074
|
+
stdio: "pipe"
|
|
9075
|
+
}).catch(() => ({ stdout: "" }));
|
|
9076
|
+
return result.stdout.trim().length > 0;
|
|
9077
|
+
}
|
|
7706
9078
|
async function createGitHubRequest(params) {
|
|
9079
|
+
const token = params.token ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
|
|
9080
|
+
let repository = params.repository;
|
|
9081
|
+
if (!repository) {
|
|
9082
|
+
repository = await detectRepoFromRemote(params.cwd);
|
|
9083
|
+
}
|
|
9084
|
+
if (token && repository) {
|
|
9085
|
+
const [owner, repo] = repository.split("/");
|
|
9086
|
+
const baseUrl = params.githubApiUrl?.replace(/\/+$/, "") || "https://api.github.com";
|
|
9087
|
+
const endpoint = `${baseUrl}/repos/${owner}/${repo}/pulls`;
|
|
9088
|
+
const response = await fetch(endpoint, {
|
|
9089
|
+
method: "POST",
|
|
9090
|
+
headers: {
|
|
9091
|
+
authorization: `Bearer ${token}`,
|
|
9092
|
+
accept: "application/vnd.github+json",
|
|
9093
|
+
"content-type": "application/json",
|
|
9094
|
+
"user-agent": "autoremediator",
|
|
9095
|
+
"x-github-api-version": "2022-11-28"
|
|
9096
|
+
},
|
|
9097
|
+
body: JSON.stringify({
|
|
9098
|
+
title: params.title,
|
|
9099
|
+
head: params.head,
|
|
9100
|
+
base: params.base,
|
|
9101
|
+
body: params.body,
|
|
9102
|
+
draft: Boolean(params.draft)
|
|
9103
|
+
})
|
|
9104
|
+
});
|
|
9105
|
+
if (!response.ok) {
|
|
9106
|
+
const errText = await response.text().catch(() => "");
|
|
9107
|
+
throw new Error(`GitHub API create pull request failed (${response.status}): ${errText}`);
|
|
9108
|
+
}
|
|
9109
|
+
const data = await response.json();
|
|
9110
|
+
return data.html_url || void 0;
|
|
9111
|
+
}
|
|
7707
9112
|
const args = [
|
|
7708
9113
|
"pr",
|
|
7709
9114
|
"create",
|
|
@@ -7722,7 +9127,7 @@ async function createGitHubRequest(params) {
|
|
|
7722
9127
|
if (params.draft) {
|
|
7723
9128
|
args.push("--draft");
|
|
7724
9129
|
}
|
|
7725
|
-
const result = await
|
|
9130
|
+
const result = await execa9("gh", args, {
|
|
7726
9131
|
cwd: params.cwd,
|
|
7727
9132
|
stdio: "pipe"
|
|
7728
9133
|
});
|
|
@@ -7747,7 +9152,7 @@ async function createGitLabRequest(params) {
|
|
|
7747
9152
|
if (params.draft) {
|
|
7748
9153
|
args.push("--draft");
|
|
7749
9154
|
}
|
|
7750
|
-
const result = await
|
|
9155
|
+
const result = await execa9("glab", args, {
|
|
7751
9156
|
cwd: params.cwd,
|
|
7752
9157
|
stdio: "pipe"
|
|
7753
9158
|
});
|
|
@@ -7762,7 +9167,7 @@ async function createChangeRequestsForReports(params) {
|
|
|
7762
9167
|
return [];
|
|
7763
9168
|
}
|
|
7764
9169
|
const results = [];
|
|
7765
|
-
const
|
|
9170
|
+
const token = options.token ?? (options.tokenEnvVar ? process.env[options.tokenEnvVar] : void 0) ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
|
|
7766
9171
|
const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd).catch(() => "main");
|
|
7767
9172
|
const branchPrefix = options.branchPrefix ?? "autoremediator";
|
|
7768
9173
|
const pushRemote = options.pushRemote ?? "origin";
|
|
@@ -7780,6 +9185,7 @@ async function createChangeRequestsForReports(params) {
|
|
|
7780
9185
|
const body = bodyFooter ? `${plan.body}
|
|
7781
9186
|
|
|
7782
9187
|
${bodyFooter}` : plan.body;
|
|
9188
|
+
const hasChanges = await ensureRepoHasChanges(cwd).catch(() => false);
|
|
7783
9189
|
if (!hasChanges) {
|
|
7784
9190
|
results.push({
|
|
7785
9191
|
provider,
|
|
@@ -7796,11 +9202,49 @@ ${bodyFooter}` : plan.body;
|
|
|
7796
9202
|
continue;
|
|
7797
9203
|
}
|
|
7798
9204
|
const branchName = toBranchName(branchPrefix, plan.cveIds);
|
|
9205
|
+
let branchCreated = false;
|
|
7799
9206
|
try {
|
|
7800
|
-
await
|
|
7801
|
-
|
|
7802
|
-
await
|
|
7803
|
-
|
|
9207
|
+
await execa9("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
|
|
9208
|
+
branchCreated = true;
|
|
9209
|
+
await execa9("git", ["add", "-A"], { cwd, stdio: "pipe" });
|
|
9210
|
+
const hasStaged = await ensureRepoHasStagedChanges(cwd);
|
|
9211
|
+
if (!hasStaged) {
|
|
9212
|
+
results.push({
|
|
9213
|
+
provider,
|
|
9214
|
+
grouping,
|
|
9215
|
+
repository: options.repository,
|
|
9216
|
+
branchName: "",
|
|
9217
|
+
title,
|
|
9218
|
+
body,
|
|
9219
|
+
created: false,
|
|
9220
|
+
cveIds: plan.cveIds,
|
|
9221
|
+
packageNames: plan.packageNames,
|
|
9222
|
+
error: "No repository changes to include in change request."
|
|
9223
|
+
});
|
|
9224
|
+
continue;
|
|
9225
|
+
}
|
|
9226
|
+
await execa9(
|
|
9227
|
+
"git",
|
|
9228
|
+
[
|
|
9229
|
+
"-c",
|
|
9230
|
+
"user.name=autoremediator[bot]",
|
|
9231
|
+
"-c",
|
|
9232
|
+
"user.email=41898282+github-actions[bot]@users.noreply.github.com",
|
|
9233
|
+
"commit",
|
|
9234
|
+
"-m",
|
|
9235
|
+
title
|
|
9236
|
+
],
|
|
9237
|
+
{ cwd, stdio: "pipe" }
|
|
9238
|
+
);
|
|
9239
|
+
const pushArgs = token ? [
|
|
9240
|
+
"-c",
|
|
9241
|
+
`http.extraheader=AUTHORIZATION: basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`,
|
|
9242
|
+
"push",
|
|
9243
|
+
"-u",
|
|
9244
|
+
pushRemote,
|
|
9245
|
+
branchName
|
|
9246
|
+
] : ["push", "-u", pushRemote, branchName];
|
|
9247
|
+
await execa9("git", pushArgs, { cwd, stdio: "pipe" });
|
|
7804
9248
|
const url = provider === "github" ? await createGitHubRequest({
|
|
7805
9249
|
cwd,
|
|
7806
9250
|
repository: options.repository,
|
|
@@ -7808,7 +9252,9 @@ ${bodyFooter}` : plan.body;
|
|
|
7808
9252
|
base: baseBranch,
|
|
7809
9253
|
title,
|
|
7810
9254
|
body,
|
|
7811
|
-
draft: options.draft
|
|
9255
|
+
draft: options.draft,
|
|
9256
|
+
token,
|
|
9257
|
+
githubApiUrl: options.githubApiUrl
|
|
7812
9258
|
}) : await createGitLabRequest({
|
|
7813
9259
|
cwd,
|
|
7814
9260
|
repository: options.repository,
|
|
@@ -7831,10 +9277,6 @@ ${bodyFooter}` : plan.body;
|
|
|
7831
9277
|
cveIds: plan.cveIds,
|
|
7832
9278
|
packageNames: plan.packageNames
|
|
7833
9279
|
});
|
|
7834
|
-
if (groups.length > 1) {
|
|
7835
|
-
await execa10("git", ["checkout", baseBranch], { cwd, stdio: "pipe" }).catch(() => {
|
|
7836
|
-
});
|
|
7837
|
-
}
|
|
7838
9280
|
} catch (error) {
|
|
7839
9281
|
results.push({
|
|
7840
9282
|
provider,
|
|
@@ -7849,6 +9291,11 @@ ${bodyFooter}` : plan.body;
|
|
|
7849
9291
|
packageNames: plan.packageNames,
|
|
7850
9292
|
error: error instanceof Error ? error.message : String(error)
|
|
7851
9293
|
});
|
|
9294
|
+
} finally {
|
|
9295
|
+
if (branchCreated) {
|
|
9296
|
+
await execa9("git", ["checkout", baseBranch], { cwd, stdio: "pipe" }).catch(() => {
|
|
9297
|
+
});
|
|
9298
|
+
}
|
|
7852
9299
|
}
|
|
7853
9300
|
}
|
|
7854
9301
|
return results;
|
|
@@ -7888,8 +9335,8 @@ function resolveConstraints(options, cwd) {
|
|
|
7888
9335
|
}
|
|
7889
9336
|
|
|
7890
9337
|
// src/platform/evidence.ts
|
|
7891
|
-
import { mkdirSync as
|
|
7892
|
-
import { join as
|
|
9338
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync9 } from "fs";
|
|
9339
|
+
import { join as join23 } from "path";
|
|
7893
9340
|
function createEvidenceLog(cwd, cveIds, context = {}) {
|
|
7894
9341
|
return {
|
|
7895
9342
|
runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -7921,10 +9368,10 @@ function finalizeEvidence(log) {
|
|
|
7921
9368
|
return log;
|
|
7922
9369
|
}
|
|
7923
9370
|
function writeEvidenceLog(cwd, log) {
|
|
7924
|
-
const dir =
|
|
7925
|
-
|
|
7926
|
-
const filePath =
|
|
7927
|
-
|
|
9371
|
+
const dir = join23(cwd, ".autoremediator", "evidence");
|
|
9372
|
+
mkdirSync5(dir, { recursive: true });
|
|
9373
|
+
const filePath = join23(dir, `${log.runId}.json`);
|
|
9374
|
+
writeFileSync9(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
|
|
7928
9375
|
return filePath;
|
|
7929
9376
|
}
|
|
7930
9377
|
|
|
@@ -8166,12 +9613,12 @@ async function planRemediation(cveId, options = {}) {
|
|
|
8166
9613
|
}
|
|
8167
9614
|
|
|
8168
9615
|
// src/scanner/parse-input.ts
|
|
8169
|
-
import { extname, resolve as
|
|
8170
|
-
import { readFileSync as
|
|
8171
|
-
import { execa as
|
|
9616
|
+
import { extname, resolve as resolve4 } from "path";
|
|
9617
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
9618
|
+
import { execa as execa10 } from "execa";
|
|
8172
9619
|
|
|
8173
9620
|
// src/scanner/adapters/npm-audit.ts
|
|
8174
|
-
import { readFileSync as
|
|
9621
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
8175
9622
|
var CVE_REGEX = /CVE-\d{4}-\d{1,7}/gi;
|
|
8176
9623
|
var GHSA_REGEX = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
|
|
8177
9624
|
function normalizeSeverity(raw) {
|
|
@@ -8207,12 +9654,12 @@ function parseNpmAuditJsonFromString(content) {
|
|
|
8207
9654
|
return findings;
|
|
8208
9655
|
}
|
|
8209
9656
|
function parseNpmAuditJsonFile(filePath) {
|
|
8210
|
-
const content =
|
|
9657
|
+
const content = readFileSync18(filePath, "utf8");
|
|
8211
9658
|
return parseNpmAuditJsonFromString(content);
|
|
8212
9659
|
}
|
|
8213
9660
|
|
|
8214
9661
|
// src/scanner/adapters/yarn-audit.ts
|
|
8215
|
-
import { readFileSync as
|
|
9662
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
8216
9663
|
var CVE_REGEX2 = /CVE-\d{4}-\d{1,7}/gi;
|
|
8217
9664
|
var GHSA_REGEX2 = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
|
|
8218
9665
|
function normalizeSeverity2(raw) {
|
|
@@ -8257,12 +9704,12 @@ function parseYarnAuditJsonFromString(content) {
|
|
|
8257
9704
|
return findings;
|
|
8258
9705
|
}
|
|
8259
9706
|
function parseYarnAuditJsonFile(filePath) {
|
|
8260
|
-
const content =
|
|
9707
|
+
const content = readFileSync19(filePath, "utf8");
|
|
8261
9708
|
return parseYarnAuditJsonFromString(content);
|
|
8262
9709
|
}
|
|
8263
9710
|
|
|
8264
9711
|
// src/scanner/adapters/sarif.ts
|
|
8265
|
-
import { readFileSync as
|
|
9712
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
8266
9713
|
var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
|
|
8267
9714
|
function extractPackageName(result) {
|
|
8268
9715
|
const pkg = result.properties?.["packageName"];
|
|
@@ -8300,7 +9747,7 @@ function parseSarifFromString(content) {
|
|
|
8300
9747
|
return findings;
|
|
8301
9748
|
}
|
|
8302
9749
|
function parseSarifFile(filePath) {
|
|
8303
|
-
const content =
|
|
9750
|
+
const content = readFileSync20(filePath, "utf8");
|
|
8304
9751
|
return parseSarifFromString(content);
|
|
8305
9752
|
}
|
|
8306
9753
|
|
|
@@ -8309,7 +9756,7 @@ function parseScanInput(filePath, format) {
|
|
|
8309
9756
|
if (filePath.includes("\0")) {
|
|
8310
9757
|
throw new Error("Invalid scan input path: path contains null bytes");
|
|
8311
9758
|
}
|
|
8312
|
-
const resolvedPath =
|
|
9759
|
+
const resolvedPath = resolve4(filePath);
|
|
8313
9760
|
const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
|
|
8314
9761
|
if (resolved === "npm-audit") {
|
|
8315
9762
|
return parseNpmAuditJsonFile(resolvedPath);
|
|
@@ -8331,7 +9778,7 @@ async function parseScanInputFromAudit(params) {
|
|
|
8331
9778
|
}
|
|
8332
9779
|
const command = resolveAuditCommand(pm, { workspace: params.workspace });
|
|
8333
9780
|
const [cmd, ...args] = command;
|
|
8334
|
-
const result = await
|
|
9781
|
+
const result = await execa10(cmd, args, {
|
|
8335
9782
|
cwd: params.cwd,
|
|
8336
9783
|
stdio: "pipe",
|
|
8337
9784
|
reject: false
|
|
@@ -8385,7 +9832,7 @@ function inferFormat(filePath) {
|
|
|
8385
9832
|
const ext = extname(filePath).toLowerCase();
|
|
8386
9833
|
if (ext === ".sarif") return "sarif";
|
|
8387
9834
|
try {
|
|
8388
|
-
const content =
|
|
9835
|
+
const content = readFileSync21(filePath, "utf8");
|
|
8389
9836
|
const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
|
|
8390
9837
|
if (firstLine) {
|
|
8391
9838
|
const parsed = JSON.parse(firstLine);
|
|
@@ -8421,9 +9868,6 @@ async function executeScanRemediations(params) {
|
|
|
8421
9868
|
source: params.provenance.source,
|
|
8422
9869
|
constraints: params.constraints
|
|
8423
9870
|
});
|
|
8424
|
-
report.results = report.results.filter(
|
|
8425
|
-
(result) => isPackageAllowed(params.policy, result.packageName)
|
|
8426
|
-
);
|
|
8427
9871
|
for (const result of report.results) {
|
|
8428
9872
|
if (result.strategy === "patch-file") {
|
|
8429
9873
|
patchCount += 1;
|
|
@@ -8796,14 +10240,14 @@ async function remediatePortfolio(targets, options = {}) {
|
|
|
8796
10240
|
}
|
|
8797
10241
|
|
|
8798
10242
|
// src/api/update-outdated/index.ts
|
|
8799
|
-
import { join as
|
|
8800
|
-
import { readFileSync as
|
|
8801
|
-
import { execa as
|
|
10243
|
+
import { join as join24 } from "path";
|
|
10244
|
+
import { readFileSync as readFileSync22, writeFileSync as writeFileSync10 } from "fs";
|
|
10245
|
+
import { execa as execa11 } from "execa";
|
|
8802
10246
|
async function applyBump(params) {
|
|
8803
|
-
const pkgPath =
|
|
10247
|
+
const pkgPath = join24(params.cwd, "package.json");
|
|
8804
10248
|
let pkgJson;
|
|
8805
10249
|
try {
|
|
8806
|
-
pkgJson = JSON.parse(
|
|
10250
|
+
pkgJson = JSON.parse(readFileSync22(pkgPath, "utf8"));
|
|
8807
10251
|
} catch {
|
|
8808
10252
|
return {
|
|
8809
10253
|
applied: false,
|
|
@@ -8825,13 +10269,13 @@ async function applyBump(params) {
|
|
|
8825
10269
|
const newRange = `${prefix}${params.toVersion}`;
|
|
8826
10270
|
return withRepoLock(params.cwd, async () => {
|
|
8827
10271
|
pkgJson[depField][params.packageName] = newRange;
|
|
8828
|
-
|
|
10272
|
+
writeFileSync10(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
8829
10273
|
try {
|
|
8830
10274
|
const [installCmd, ...installArgs] = params.installCommand;
|
|
8831
|
-
await
|
|
10275
|
+
await execa11(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
|
|
8832
10276
|
} catch (err) {
|
|
8833
10277
|
pkgJson[depField][params.packageName] = currentRange;
|
|
8834
|
-
|
|
10278
|
+
writeFileSync10(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
8835
10279
|
const message = err instanceof Error ? err.message : String(err);
|
|
8836
10280
|
return {
|
|
8837
10281
|
applied: false,
|
|
@@ -8841,13 +10285,13 @@ async function applyBump(params) {
|
|
|
8841
10285
|
if (params.runTests) {
|
|
8842
10286
|
try {
|
|
8843
10287
|
const [testCmd, ...testArgs] = params.testCommand;
|
|
8844
|
-
await
|
|
10288
|
+
await execa11(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
|
|
8845
10289
|
} catch (err) {
|
|
8846
10290
|
pkgJson[depField][params.packageName] = currentRange;
|
|
8847
|
-
|
|
10291
|
+
writeFileSync10(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
8848
10292
|
try {
|
|
8849
10293
|
const [rollbackCmd, ...rollbackArgs] = params.installCommand;
|
|
8850
|
-
await
|
|
10294
|
+
await execa11(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
|
|
8851
10295
|
} catch {
|
|
8852
10296
|
}
|
|
8853
10297
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -9049,19 +10493,19 @@ async function checkReachability(options) {
|
|
|
9049
10493
|
}
|
|
9050
10494
|
|
|
9051
10495
|
// src/scanner/delta-scan.ts
|
|
9052
|
-
import { existsSync as
|
|
9053
|
-
import { join as
|
|
9054
|
-
import { execa as
|
|
9055
|
-
import
|
|
10496
|
+
import { existsSync as existsSync18, readFileSync as readFileSync23 } from "fs";
|
|
10497
|
+
import { join as join25 } from "path";
|
|
10498
|
+
import { execa as execa12 } from "execa";
|
|
10499
|
+
import semver11 from "semver";
|
|
9056
10500
|
|
|
9057
10501
|
// src/scanner/lockfile-diff.ts
|
|
9058
10502
|
import YAML2 from "yaml";
|
|
9059
|
-
import
|
|
10503
|
+
import semver10 from "semver";
|
|
9060
10504
|
function parseLockfileDependencies(filename, content) {
|
|
9061
10505
|
const result = /* @__PURE__ */ new Map();
|
|
9062
10506
|
const add = (name, ver) => {
|
|
9063
10507
|
if (!name || !ver) return;
|
|
9064
|
-
const cleanVer =
|
|
10508
|
+
const cleanVer = semver10.clean(ver) || semver10.coerce(ver)?.version || ver;
|
|
9065
10509
|
let set = result.get(name);
|
|
9066
10510
|
if (!set) {
|
|
9067
10511
|
set = /* @__PURE__ */ new Set();
|
|
@@ -9181,9 +10625,9 @@ function computeLockfileDiff(filename, oldContent, newContent) {
|
|
|
9181
10625
|
});
|
|
9182
10626
|
} else {
|
|
9183
10627
|
const [firstOld] = oldVers;
|
|
9184
|
-
const cleanOld =
|
|
9185
|
-
const cleanNew =
|
|
9186
|
-
const isUpgrade = cleanOld && cleanNew &&
|
|
10628
|
+
const cleanOld = semver10.clean(firstOld) || semver10.coerce(firstOld)?.version;
|
|
10629
|
+
const cleanNew = semver10.clean(nVer) || semver10.coerce(nVer)?.version;
|
|
10630
|
+
const isUpgrade = cleanOld && cleanNew && semver10.valid(cleanOld) && semver10.valid(cleanNew) ? semver10.gt(cleanNew, cleanOld) : true;
|
|
9187
10631
|
changes.push({
|
|
9188
10632
|
packageName: pkg,
|
|
9189
10633
|
changeType: isUpgrade ? "upgraded" : "downgraded",
|
|
@@ -9220,7 +10664,7 @@ var DEP_SECTIONS = [
|
|
|
9220
10664
|
async function getChangedManifestPaths(cwd, baseRef) {
|
|
9221
10665
|
const manifests = /* @__PURE__ */ new Set();
|
|
9222
10666
|
try {
|
|
9223
|
-
const { stdout: diffFiles } = await
|
|
10667
|
+
const { stdout: diffFiles } = await execa12(
|
|
9224
10668
|
"git",
|
|
9225
10669
|
["diff", "--name-only", baseRef, "--", "package.json", "**/package.json"],
|
|
9226
10670
|
{ cwd, reject: false }
|
|
@@ -9231,7 +10675,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
|
|
|
9231
10675
|
manifests.add(trimmed);
|
|
9232
10676
|
}
|
|
9233
10677
|
}
|
|
9234
|
-
const { stdout: untrackedFiles } = await
|
|
10678
|
+
const { stdout: untrackedFiles } = await execa12(
|
|
9235
10679
|
"git",
|
|
9236
10680
|
["ls-files", "--others", "--exclude-standard", "--", "package.json", "**/package.json"],
|
|
9237
10681
|
{ cwd, reject: false }
|
|
@@ -9243,7 +10687,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
|
|
|
9243
10687
|
}
|
|
9244
10688
|
}
|
|
9245
10689
|
} catch {
|
|
9246
|
-
if (
|
|
10690
|
+
if (existsSync18(join25(cwd, "package.json"))) {
|
|
9247
10691
|
manifests.add("package.json");
|
|
9248
10692
|
}
|
|
9249
10693
|
}
|
|
@@ -9251,7 +10695,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
|
|
|
9251
10695
|
}
|
|
9252
10696
|
async function getOldManifestContent(cwd, baseRef, relPath) {
|
|
9253
10697
|
try {
|
|
9254
|
-
const { stdout } = await
|
|
10698
|
+
const { stdout } = await execa12("git", ["show", `${baseRef}:${relPath}`], {
|
|
9255
10699
|
cwd,
|
|
9256
10700
|
reject: false
|
|
9257
10701
|
});
|
|
@@ -9262,10 +10706,10 @@ async function getOldManifestContent(cwd, baseRef, relPath) {
|
|
|
9262
10706
|
}
|
|
9263
10707
|
}
|
|
9264
10708
|
function getNewManifestContent(cwd, relPath) {
|
|
9265
|
-
const fullPath =
|
|
9266
|
-
if (!
|
|
10709
|
+
const fullPath = join25(cwd, relPath);
|
|
10710
|
+
if (!existsSync18(fullPath)) return null;
|
|
9267
10711
|
try {
|
|
9268
|
-
return JSON.parse(
|
|
10712
|
+
return JSON.parse(readFileSync23(fullPath, "utf8"));
|
|
9269
10713
|
} catch {
|
|
9270
10714
|
return null;
|
|
9271
10715
|
}
|
|
@@ -9286,9 +10730,9 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
|
|
|
9286
10730
|
manifestPath: relPath
|
|
9287
10731
|
});
|
|
9288
10732
|
} else if (oldVer !== newVer) {
|
|
9289
|
-
const cleanOld =
|
|
9290
|
-
const cleanNew =
|
|
9291
|
-
const isUpgrade = cleanOld && cleanNew &&
|
|
10733
|
+
const cleanOld = semver11.clean(oldVer) || semver11.coerce(oldVer)?.version;
|
|
10734
|
+
const cleanNew = semver11.clean(newVer) || semver11.coerce(newVer)?.version;
|
|
10735
|
+
const isUpgrade = cleanOld && cleanNew && semver11.valid(cleanOld) && semver11.valid(cleanNew) ? semver11.gt(cleanNew, cleanOld) : true;
|
|
9292
10736
|
changes.push({
|
|
9293
10737
|
packageName: pkgName,
|
|
9294
10738
|
changeType: isUpgrade ? "upgraded" : "downgraded",
|
|
@@ -9324,7 +10768,7 @@ var LOCKFILE_PATTERNS = [
|
|
|
9324
10768
|
async function getChangedLockfilePaths(cwd, baseRef) {
|
|
9325
10769
|
const lockfiles = /* @__PURE__ */ new Set();
|
|
9326
10770
|
try {
|
|
9327
|
-
const { stdout: diffFiles } = await
|
|
10771
|
+
const { stdout: diffFiles } = await execa12(
|
|
9328
10772
|
"git",
|
|
9329
10773
|
["diff", "--name-only", baseRef, "--", ...LOCKFILE_PATTERNS],
|
|
9330
10774
|
{ cwd, reject: false }
|
|
@@ -9335,7 +10779,7 @@ async function getChangedLockfilePaths(cwd, baseRef) {
|
|
|
9335
10779
|
lockfiles.add(trimmed);
|
|
9336
10780
|
}
|
|
9337
10781
|
}
|
|
9338
|
-
const { stdout: untrackedFiles } = await
|
|
10782
|
+
const { stdout: untrackedFiles } = await execa12(
|
|
9339
10783
|
"git",
|
|
9340
10784
|
["ls-files", "--others", "--exclude-standard", "--", ...LOCKFILE_PATTERNS],
|
|
9341
10785
|
{ cwd, reject: false }
|
|
@@ -9352,7 +10796,7 @@ async function getChangedLockfilePaths(cwd, baseRef) {
|
|
|
9352
10796
|
}
|
|
9353
10797
|
async function getFileContentAtRef(cwd, baseRef, relPath) {
|
|
9354
10798
|
try {
|
|
9355
|
-
const { stdout } = await
|
|
10799
|
+
const { stdout } = await execa12("git", ["show", `${baseRef}:${relPath}`], {
|
|
9356
10800
|
cwd,
|
|
9357
10801
|
reject: false
|
|
9358
10802
|
});
|
|
@@ -9379,8 +10823,8 @@ async function scanDelta(options = {}) {
|
|
|
9379
10823
|
}
|
|
9380
10824
|
for (const lockfileRel of changedLockfiles) {
|
|
9381
10825
|
const oldContent = await getFileContentAtRef(cwd, baseRef, lockfileRel);
|
|
9382
|
-
const fullPath =
|
|
9383
|
-
const newContent =
|
|
10826
|
+
const fullPath = join25(cwd, lockfileRel);
|
|
10827
|
+
const newContent = existsSync18(fullPath) ? readFileSync23(fullPath, "utf8") : null;
|
|
9384
10828
|
const lockDiffs = computeLockfileDiff(lockfileRel, oldContent, newContent);
|
|
9385
10829
|
for (const change of lockDiffs) {
|
|
9386
10830
|
if (!directPkgs.has(change.packageName)) {
|
|
@@ -9395,7 +10839,7 @@ async function scanDelta(options = {}) {
|
|
|
9395
10839
|
const resolvedFindings = [];
|
|
9396
10840
|
for (const change of dependencyChanges) {
|
|
9397
10841
|
if (change.changeType === "added" || change.changeType === "upgraded" || change.changeType === "downgraded") {
|
|
9398
|
-
const cleanVer =
|
|
10842
|
+
const cleanVer = semver11.coerce(change.newVersion)?.version ?? change.newVersion;
|
|
9399
10843
|
const evaluation = await evaluatePackage(change.packageName, {
|
|
9400
10844
|
version: cleanVer,
|
|
9401
10845
|
enrichIntelligence: true
|
|
@@ -9415,12 +10859,12 @@ async function scanDelta(options = {}) {
|
|
|
9415
10859
|
}
|
|
9416
10860
|
}
|
|
9417
10861
|
if (change.oldVersion && (change.changeType === "upgraded" || change.changeType === "removed")) {
|
|
9418
|
-
const cleanOldVer =
|
|
10862
|
+
const cleanOldVer = semver11.coerce(change.oldVersion)?.version ?? change.oldVersion;
|
|
9419
10863
|
const oldEval = await evaluatePackage(change.packageName, {
|
|
9420
10864
|
version: cleanOldVer,
|
|
9421
10865
|
enrichIntelligence: true
|
|
9422
10866
|
});
|
|
9423
|
-
const cleanNewVer = change.newVersion ?
|
|
10867
|
+
const cleanNewVer = change.newVersion ? semver11.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
|
|
9424
10868
|
const newEval = cleanNewVer ? await evaluatePackage(change.packageName, {
|
|
9425
10869
|
version: cleanNewVer,
|
|
9426
10870
|
enrichIntelligence: false
|
|
@@ -9472,7 +10916,145 @@ async function scanDelta(options = {}) {
|
|
|
9472
10916
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9473
10917
|
|
|
9474
10918
|
// src/platform/jobs.ts
|
|
9475
|
-
var
|
|
10919
|
+
var BoundedJobRegistry = class extends Map {
|
|
10920
|
+
maxCapacity;
|
|
10921
|
+
ttlMs;
|
|
10922
|
+
constructor(options = {}) {
|
|
10923
|
+
super();
|
|
10924
|
+
this.maxCapacity = options.maxCapacity ?? 1e3;
|
|
10925
|
+
this.ttlMs = options.ttlMs ?? 24 * 60 * 60 * 1e3;
|
|
10926
|
+
}
|
|
10927
|
+
getMaxCapacity() {
|
|
10928
|
+
return this.maxCapacity;
|
|
10929
|
+
}
|
|
10930
|
+
setMaxCapacity(capacity) {
|
|
10931
|
+
if (capacity <= 0) {
|
|
10932
|
+
throw new Error(`maxCapacity must be greater than 0, received ${capacity}`);
|
|
10933
|
+
}
|
|
10934
|
+
this.maxCapacity = capacity;
|
|
10935
|
+
this.enforceCapacity();
|
|
10936
|
+
}
|
|
10937
|
+
getTtl() {
|
|
10938
|
+
return this.ttlMs;
|
|
10939
|
+
}
|
|
10940
|
+
setTtl(ttlMs) {
|
|
10941
|
+
if (ttlMs <= 0) {
|
|
10942
|
+
throw new Error(`ttlMs must be greater than 0, received ${ttlMs}`);
|
|
10943
|
+
}
|
|
10944
|
+
this.ttlMs = ttlMs;
|
|
10945
|
+
}
|
|
10946
|
+
isExpired(job, now = Date.now()) {
|
|
10947
|
+
const timestampStr = job.completedAt ?? job.submittedAt;
|
|
10948
|
+
const timeMs = new Date(timestampStr).getTime();
|
|
10949
|
+
if (isNaN(timeMs)) return false;
|
|
10950
|
+
return now - timeMs > this.ttlMs;
|
|
10951
|
+
}
|
|
10952
|
+
evictExpired(now = Date.now()) {
|
|
10953
|
+
let count = 0;
|
|
10954
|
+
for (const [id, job] of super.entries()) {
|
|
10955
|
+
if (this.isExpired(job, now)) {
|
|
10956
|
+
super.delete(id);
|
|
10957
|
+
count++;
|
|
10958
|
+
}
|
|
10959
|
+
}
|
|
10960
|
+
return count;
|
|
10961
|
+
}
|
|
10962
|
+
prune(now = Date.now()) {
|
|
10963
|
+
return this.evictExpired(now);
|
|
10964
|
+
}
|
|
10965
|
+
get(jobId) {
|
|
10966
|
+
const job = super.get(jobId);
|
|
10967
|
+
if (!job) return void 0;
|
|
10968
|
+
if (this.isExpired(job)) {
|
|
10969
|
+
super.delete(jobId);
|
|
10970
|
+
return void 0;
|
|
10971
|
+
}
|
|
10972
|
+
return job;
|
|
10973
|
+
}
|
|
10974
|
+
has(jobId) {
|
|
10975
|
+
const job = super.get(jobId);
|
|
10976
|
+
if (!job) return false;
|
|
10977
|
+
if (this.isExpired(job)) {
|
|
10978
|
+
super.delete(jobId);
|
|
10979
|
+
return false;
|
|
10980
|
+
}
|
|
10981
|
+
return true;
|
|
10982
|
+
}
|
|
10983
|
+
set(jobId, job) {
|
|
10984
|
+
if (super.has(jobId)) {
|
|
10985
|
+
super.set(jobId, job);
|
|
10986
|
+
return this;
|
|
10987
|
+
}
|
|
10988
|
+
this.evictExpired();
|
|
10989
|
+
if (this.size >= this.maxCapacity) {
|
|
10990
|
+
this.evictOneCandidate();
|
|
10991
|
+
}
|
|
10992
|
+
super.set(jobId, job);
|
|
10993
|
+
return this;
|
|
10994
|
+
}
|
|
10995
|
+
evictOneCandidate() {
|
|
10996
|
+
let oldestTerminalId;
|
|
10997
|
+
let oldestTerminalTime = Infinity;
|
|
10998
|
+
let oldestOverallId;
|
|
10999
|
+
let oldestOverallTime = Infinity;
|
|
11000
|
+
for (const [id, job] of super.entries()) {
|
|
11001
|
+
const submittedMs = new Date(job.submittedAt).getTime() || 0;
|
|
11002
|
+
if (submittedMs < oldestOverallTime) {
|
|
11003
|
+
oldestOverallTime = submittedMs;
|
|
11004
|
+
oldestOverallId = id;
|
|
11005
|
+
}
|
|
11006
|
+
if (job.status === "done" || job.status === "failed") {
|
|
11007
|
+
const terminalMs = new Date(job.completedAt ?? job.submittedAt).getTime() || 0;
|
|
11008
|
+
if (terminalMs < oldestTerminalTime) {
|
|
11009
|
+
oldestTerminalTime = terminalMs;
|
|
11010
|
+
oldestTerminalId = id;
|
|
11011
|
+
}
|
|
11012
|
+
}
|
|
11013
|
+
}
|
|
11014
|
+
const targetId = oldestTerminalId ?? oldestOverallId;
|
|
11015
|
+
if (targetId) {
|
|
11016
|
+
super.delete(targetId);
|
|
11017
|
+
}
|
|
11018
|
+
}
|
|
11019
|
+
enforceCapacity() {
|
|
11020
|
+
this.evictExpired();
|
|
11021
|
+
while (this.size > this.maxCapacity) {
|
|
11022
|
+
this.evictOneCandidate();
|
|
11023
|
+
}
|
|
11024
|
+
}
|
|
11025
|
+
stats() {
|
|
11026
|
+
let pending = 0;
|
|
11027
|
+
let running = 0;
|
|
11028
|
+
let done = 0;
|
|
11029
|
+
let failed = 0;
|
|
11030
|
+
for (const job of super.values()) {
|
|
11031
|
+
switch (job.status) {
|
|
11032
|
+
case "pending":
|
|
11033
|
+
pending++;
|
|
11034
|
+
break;
|
|
11035
|
+
case "running":
|
|
11036
|
+
running++;
|
|
11037
|
+
break;
|
|
11038
|
+
case "done":
|
|
11039
|
+
done++;
|
|
11040
|
+
break;
|
|
11041
|
+
case "failed":
|
|
11042
|
+
failed++;
|
|
11043
|
+
break;
|
|
11044
|
+
}
|
|
11045
|
+
}
|
|
11046
|
+
return {
|
|
11047
|
+
size: this.size,
|
|
11048
|
+
maxCapacity: this.maxCapacity,
|
|
11049
|
+
ttlMs: this.ttlMs,
|
|
11050
|
+
pending,
|
|
11051
|
+
running,
|
|
11052
|
+
done,
|
|
11053
|
+
failed
|
|
11054
|
+
};
|
|
11055
|
+
}
|
|
11056
|
+
};
|
|
11057
|
+
var jobRegistry = new BoundedJobRegistry();
|
|
9476
11058
|
|
|
9477
11059
|
// src/api/jobs/index.ts
|
|
9478
11060
|
function createJob() {
|