autoremediator 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/dist/{chunk-DQKT2CUG.js → chunk-URM53GSJ.js} +388 -133
- package/dist/chunk-URM53GSJ.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +51 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +42 -2
- package/dist/index.js +3 -1
- package/dist/mcp/server.d.ts +277 -0
- package/dist/mcp/server.js +115 -15
- package/dist/mcp/server.js.map +1 -1
- package/dist/openapi/server.d.ts +400 -1
- package/dist/openapi/server.js +192 -50
- package/dist/openapi/server.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-DQKT2CUG.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/remediation/pipeline.ts
|
|
2
2
|
import { generateText as generateText2 } from "ai";
|
|
3
3
|
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
4
|
-
import { join as
|
|
4
|
+
import { join as join8 } from "path";
|
|
5
5
|
import semver4 from "semver";
|
|
6
6
|
|
|
7
7
|
// src/platform/config.ts
|
|
@@ -574,7 +574,7 @@ var findFixedVersionTool = tool4({
|
|
|
574
574
|
// src/remediation/tools/apply-version-bump.ts
|
|
575
575
|
import { tool as tool5 } from "ai";
|
|
576
576
|
import { z as z5 } from "zod";
|
|
577
|
-
import { join as
|
|
577
|
+
import { join as join5 } from "path";
|
|
578
578
|
import { readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
579
579
|
import { execa as execa2 } from "execa";
|
|
580
580
|
import semver3 from "semver";
|
|
@@ -585,7 +585,11 @@ import { join as join3 } from "path";
|
|
|
585
585
|
var DEFAULT_POLICY = {
|
|
586
586
|
allowMajorBumps: false,
|
|
587
587
|
denyPackages: [],
|
|
588
|
-
allowPackages: []
|
|
588
|
+
allowPackages: [],
|
|
589
|
+
constraints: {
|
|
590
|
+
directDependenciesOnly: false,
|
|
591
|
+
preferVersionBump: false
|
|
592
|
+
}
|
|
589
593
|
};
|
|
590
594
|
function loadPolicy(cwd, explicitPath) {
|
|
591
595
|
const candidate = explicitPath ?? join3(cwd, ".autoremediator.json");
|
|
@@ -595,7 +599,11 @@ function loadPolicy(cwd, explicitPath) {
|
|
|
595
599
|
return {
|
|
596
600
|
allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
|
|
597
601
|
denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
|
|
598
|
-
allowPackages: parsed.allowPackages ?? DEFAULT_POLICY.allowPackages
|
|
602
|
+
allowPackages: parsed.allowPackages ?? DEFAULT_POLICY.allowPackages,
|
|
603
|
+
constraints: {
|
|
604
|
+
directDependenciesOnly: parsed.constraints?.directDependenciesOnly ?? DEFAULT_POLICY.constraints?.directDependenciesOnly ?? false,
|
|
605
|
+
preferVersionBump: parsed.constraints?.preferVersionBump ?? DEFAULT_POLICY.constraints?.preferVersionBump ?? false
|
|
606
|
+
}
|
|
599
607
|
};
|
|
600
608
|
} catch {
|
|
601
609
|
return DEFAULT_POLICY;
|
|
@@ -609,6 +617,45 @@ function isPackageAllowed(policy, packageName) {
|
|
|
609
617
|
return true;
|
|
610
618
|
}
|
|
611
619
|
|
|
620
|
+
// src/platform/repo-lock.ts
|
|
621
|
+
import { mkdir, rm } from "fs/promises";
|
|
622
|
+
import { join as join4 } from "path";
|
|
623
|
+
async function sleep(ms) {
|
|
624
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
625
|
+
}
|
|
626
|
+
async function acquireRepoLock(cwd, options = {}) {
|
|
627
|
+
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
628
|
+
const retryDelayMs = options.retryDelayMs ?? 125;
|
|
629
|
+
const lockRoot = join4(cwd, ".autoremediator", "locks");
|
|
630
|
+
const lockPath = join4(cwd, ".autoremediator", "locks", "remediation.lock");
|
|
631
|
+
const startedAt = Date.now();
|
|
632
|
+
await mkdir(lockRoot, { recursive: true });
|
|
633
|
+
while (true) {
|
|
634
|
+
try {
|
|
635
|
+
await mkdir(lockPath, { recursive: false });
|
|
636
|
+
return {
|
|
637
|
+
lockPath,
|
|
638
|
+
release: async () => {
|
|
639
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
} catch {
|
|
643
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
644
|
+
throw new Error(`Timed out waiting for repository lock at ${lockPath}.`);
|
|
645
|
+
}
|
|
646
|
+
await sleep(retryDelayMs);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
async function withRepoLock(cwd, fn, options) {
|
|
651
|
+
const lock = await acquireRepoLock(cwd, options);
|
|
652
|
+
try {
|
|
653
|
+
return await fn();
|
|
654
|
+
} finally {
|
|
655
|
+
await lock.release();
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
612
659
|
// src/remediation/tools/apply-version-bump.ts
|
|
613
660
|
var applyVersionBumpTool = tool5({
|
|
614
661
|
description: "Update package.json to use the safe version of a vulnerable package and run the project's package manager install. In dry-run mode, only reports what would change.",
|
|
@@ -634,7 +681,7 @@ var applyVersionBumpTool = tool5({
|
|
|
634
681
|
}) => {
|
|
635
682
|
const pm = packageManager ?? detectPackageManager(cwd);
|
|
636
683
|
const commands = getPackageManagerCommands(pm);
|
|
637
|
-
const pkgPath =
|
|
684
|
+
const pkgPath = join5(cwd, "package.json");
|
|
638
685
|
const policy = loadPolicy(cwd, policyPath);
|
|
639
686
|
if (!isPackageAllowed(policy, packageName)) {
|
|
640
687
|
return {
|
|
@@ -702,46 +749,18 @@ var applyVersionBumpTool = tool5({
|
|
|
702
749
|
message: `[DRY RUN] Would update ${depField}.${packageName}: "${currentRange}" \u2192 "${newRange}", then run ${installCmd}${skipTests ? "" : ` and ${testCmd}`}.`
|
|
703
750
|
};
|
|
704
751
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
try {
|
|
708
|
-
const [installCmd, ...installArgs] = commands.installPreferOffline;
|
|
709
|
-
await execa2(installCmd, installArgs, {
|
|
710
|
-
cwd,
|
|
711
|
-
stdio: "pipe"
|
|
712
|
-
});
|
|
713
|
-
} catch (err) {
|
|
714
|
-
pkgJson[depField][packageName] = currentRange;
|
|
752
|
+
return withRepoLock(cwd, async () => {
|
|
753
|
+
pkgJson[depField][packageName] = newRange;
|
|
715
754
|
writeFileSync(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
716
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
717
|
-
return {
|
|
718
|
-
packageName,
|
|
719
|
-
strategy: "version-bump",
|
|
720
|
-
fromVersion,
|
|
721
|
-
toVersion,
|
|
722
|
-
applied: false,
|
|
723
|
-
dryRun: false,
|
|
724
|
-
message: `${commands.installPreferOffline.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
if (!skipTests) {
|
|
728
755
|
try {
|
|
729
|
-
const [
|
|
730
|
-
await execa2(
|
|
756
|
+
const [installCmd, ...installArgs] = commands.installPreferOffline;
|
|
757
|
+
await execa2(installCmd, installArgs, {
|
|
731
758
|
cwd,
|
|
732
759
|
stdio: "pipe"
|
|
733
760
|
});
|
|
734
761
|
} catch (err) {
|
|
735
762
|
pkgJson[depField][packageName] = currentRange;
|
|
736
763
|
writeFileSync(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
737
|
-
try {
|
|
738
|
-
const [rollbackCmd, ...rollbackArgs] = commands.installPreferOffline;
|
|
739
|
-
await execa2(rollbackCmd, rollbackArgs, {
|
|
740
|
-
cwd,
|
|
741
|
-
stdio: "pipe"
|
|
742
|
-
});
|
|
743
|
-
} catch {
|
|
744
|
-
}
|
|
745
764
|
const message = err instanceof Error ? err.message : String(err);
|
|
746
765
|
return {
|
|
747
766
|
packageName,
|
|
@@ -750,27 +769,57 @@ var applyVersionBumpTool = tool5({
|
|
|
750
769
|
toVersion,
|
|
751
770
|
applied: false,
|
|
752
771
|
dryRun: false,
|
|
753
|
-
message: `${commands.
|
|
772
|
+
message: `${commands.installPreferOffline.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
|
|
754
773
|
};
|
|
755
774
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
775
|
+
if (!skipTests) {
|
|
776
|
+
try {
|
|
777
|
+
const [testCmd, ...testArgs] = commands.test;
|
|
778
|
+
await execa2(testCmd, testArgs, {
|
|
779
|
+
cwd,
|
|
780
|
+
stdio: "pipe"
|
|
781
|
+
});
|
|
782
|
+
} catch (err) {
|
|
783
|
+
pkgJson[depField][packageName] = currentRange;
|
|
784
|
+
writeFileSync(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
785
|
+
try {
|
|
786
|
+
const [rollbackCmd, ...rollbackArgs] = commands.installPreferOffline;
|
|
787
|
+
await execa2(rollbackCmd, rollbackArgs, {
|
|
788
|
+
cwd,
|
|
789
|
+
stdio: "pipe"
|
|
790
|
+
});
|
|
791
|
+
} catch {
|
|
792
|
+
}
|
|
793
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
794
|
+
return {
|
|
795
|
+
packageName,
|
|
796
|
+
strategy: "version-bump",
|
|
797
|
+
fromVersion,
|
|
798
|
+
toVersion,
|
|
799
|
+
applied: false,
|
|
800
|
+
dryRun: false,
|
|
801
|
+
message: `${commands.test.join(" ")} failed after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return {
|
|
806
|
+
packageName,
|
|
807
|
+
strategy: "version-bump",
|
|
808
|
+
fromVersion,
|
|
809
|
+
toVersion,
|
|
810
|
+
applied: true,
|
|
811
|
+
dryRun: false,
|
|
812
|
+
message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion}, ran ${commands.installPreferOffline.join(" ")}${skipTests ? "" : `, and passed ${commands.test.join(" ")}`}.`
|
|
813
|
+
};
|
|
814
|
+
});
|
|
766
815
|
}
|
|
767
816
|
});
|
|
768
817
|
|
|
769
818
|
// src/remediation/tools/fetch-package-source.ts
|
|
770
819
|
import { tool as tool6 } from "ai";
|
|
771
820
|
import { z as z6 } from "zod";
|
|
772
|
-
import { mkdir, readdir, readFile, rm } from "fs/promises";
|
|
773
|
-
import { join as
|
|
821
|
+
import { mkdir as mkdir2, readdir, readFile, rm as rm2 } from "fs/promises";
|
|
822
|
+
import { join as join6 } from "path";
|
|
774
823
|
import { execa as execa3 } from "execa";
|
|
775
824
|
var fetchPackageSourceTool = tool6({
|
|
776
825
|
description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
|
|
@@ -787,23 +836,23 @@ var fetchPackageSourceTool = tool6({
|
|
|
787
836
|
filePatterns
|
|
788
837
|
}) => {
|
|
789
838
|
const tempBaseDir = `/tmp/autoremediator-pkg-${Date.now()}`;
|
|
790
|
-
const extractDir =
|
|
839
|
+
const extractDir = join6(tempBaseDir, "out");
|
|
791
840
|
try {
|
|
792
841
|
const npmUrl = `https://registry.npmjs.org/${packageName}/-/${packageName.split("/").pop()}-${version}.tgz`;
|
|
793
|
-
await
|
|
794
|
-
const tarballPath =
|
|
842
|
+
await mkdir2(tempBaseDir, { recursive: true });
|
|
843
|
+
const tarballPath = join6(tempBaseDir, "package.tgz");
|
|
795
844
|
await execa3("curl", ["-L", "-o", tarballPath, npmUrl]);
|
|
796
|
-
await
|
|
845
|
+
await mkdir2(extractDir, { recursive: true });
|
|
797
846
|
await execa3("tar", ["-xzf", tarballPath, "-C", extractDir]);
|
|
798
847
|
const extractedContents = await readdir(extractDir);
|
|
799
|
-
const packageRootDir = extractedContents.includes("package") ?
|
|
848
|
+
const packageRootDir = extractedContents.includes("package") ? join6(extractDir, "package") : extractDir;
|
|
800
849
|
const sourceCode = {};
|
|
801
850
|
async function walkDir(dir, relativeBase) {
|
|
802
851
|
try {
|
|
803
852
|
const files = await readdir(dir, { withFileTypes: true });
|
|
804
853
|
for (const file of files) {
|
|
805
|
-
const fullPath =
|
|
806
|
-
const relPath =
|
|
854
|
+
const fullPath = join6(dir, file.name);
|
|
855
|
+
const relPath = join6(relativeBase, file.name);
|
|
807
856
|
if (file.isDirectory()) {
|
|
808
857
|
if (![
|
|
809
858
|
"node_modules",
|
|
@@ -860,7 +909,7 @@ var fetchPackageSourceTool = tool6({
|
|
|
860
909
|
error: `Failed to fetch and extract package ${packageName}@${version}: ${message}`
|
|
861
910
|
};
|
|
862
911
|
} finally {
|
|
863
|
-
await
|
|
912
|
+
await rm2(tempBaseDir, { recursive: true, force: true });
|
|
864
913
|
}
|
|
865
914
|
}
|
|
866
915
|
});
|
|
@@ -1076,9 +1125,9 @@ function generateUnifiedDiff(original, fixed, filePath) {
|
|
|
1076
1125
|
import { tool as tool8 } from "ai";
|
|
1077
1126
|
import { z as z8 } from "zod";
|
|
1078
1127
|
import { existsSync as existsSync3 } from "fs";
|
|
1079
|
-
import { mkdir as
|
|
1128
|
+
import { mkdir as mkdir3, mkdtemp, readFile as readFile2, rm as rm3, writeFile } from "fs/promises";
|
|
1080
1129
|
import { tmpdir } from "os";
|
|
1081
|
-
import { join as
|
|
1130
|
+
import { join as join7 } from "path";
|
|
1082
1131
|
import { execa as execa4 } from "execa";
|
|
1083
1132
|
var applyPatchFileTool = tool8({
|
|
1084
1133
|
description: "Write generated patch file and apply it using package-manager-native patch flow when available, falling back to patch-package when needed.",
|
|
@@ -1126,7 +1175,7 @@ var applyPatchFileTool = tool8({
|
|
|
1126
1175
|
};
|
|
1127
1176
|
}
|
|
1128
1177
|
const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
|
|
1129
|
-
const patchFilePath =
|
|
1178
|
+
const patchFilePath = join7(cwd, patchesDir, patchFileName);
|
|
1130
1179
|
if (dryRun) {
|
|
1131
1180
|
return {
|
|
1132
1181
|
success: true,
|
|
@@ -1139,66 +1188,68 @@ var applyPatchFileTool = tool8({
|
|
|
1139
1188
|
patchPath: patchFilePath
|
|
1140
1189
|
};
|
|
1141
1190
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
cwd,
|
|
1149
|
-
|
|
1150
|
-
vulnerableVersion,
|
|
1151
|
-
patchContent: selectedPatch,
|
|
1152
|
-
patchMode
|
|
1153
|
-
});
|
|
1154
|
-
if (!applyResult.success) {
|
|
1155
|
-
return {
|
|
1156
|
-
success: false,
|
|
1191
|
+
return withRepoLock(cwd, async () => {
|
|
1192
|
+
const patchesDirPath = join7(cwd, patchesDir);
|
|
1193
|
+
await mkdir3(patchesDirPath, { recursive: true });
|
|
1194
|
+
await writeFile(patchFilePath, selectedPatch, "utf8");
|
|
1195
|
+
let validationResult;
|
|
1196
|
+
const patchMode = await resolvePatchMode(pm, cwd);
|
|
1197
|
+
const applyResult = patchMode === "patch-package" ? await configurePatchPackagePostinstall(cwd, pm) : await applyNativePatch({
|
|
1198
|
+
cwd,
|
|
1157
1199
|
packageName,
|
|
1158
1200
|
vulnerableVersion,
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
patchPath: patchFilePath,
|
|
1164
|
-
patchMode,
|
|
1165
|
-
postinstallConfigured: patchMode === "patch-package" ? false : void 0,
|
|
1166
|
-
error: applyResult.error
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
if (validateWithTests) {
|
|
1170
|
-
validationResult = await validatePatchWithTests(cwd, pm);
|
|
1171
|
-
if (!validationResult.passed) {
|
|
1172
|
-
const validationError = "Patch validation failed after apply; patch marked unresolved.";
|
|
1201
|
+
patchContent: selectedPatch,
|
|
1202
|
+
patchMode
|
|
1203
|
+
});
|
|
1204
|
+
if (!applyResult.success) {
|
|
1173
1205
|
return {
|
|
1174
1206
|
success: false,
|
|
1175
1207
|
packageName,
|
|
1176
1208
|
vulnerableVersion,
|
|
1177
1209
|
applied: false,
|
|
1178
1210
|
dryRun: false,
|
|
1179
|
-
message:
|
|
1211
|
+
message: applyResult.error,
|
|
1180
1212
|
patchFilePath,
|
|
1181
1213
|
patchPath: patchFilePath,
|
|
1182
1214
|
patchMode,
|
|
1183
|
-
postinstallConfigured: patchMode === "patch-package",
|
|
1184
|
-
|
|
1185
|
-
error: validationError
|
|
1215
|
+
postinstallConfigured: patchMode === "patch-package" ? false : void 0,
|
|
1216
|
+
error: applyResult.error
|
|
1186
1217
|
};
|
|
1187
1218
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1219
|
+
if (validateWithTests) {
|
|
1220
|
+
validationResult = await validatePatchWithTests(cwd, pm);
|
|
1221
|
+
if (!validationResult.passed) {
|
|
1222
|
+
const validationError = "Patch validation failed after apply; patch marked unresolved.";
|
|
1223
|
+
return {
|
|
1224
|
+
success: false,
|
|
1225
|
+
packageName,
|
|
1226
|
+
vulnerableVersion,
|
|
1227
|
+
applied: false,
|
|
1228
|
+
dryRun: false,
|
|
1229
|
+
message: validationError,
|
|
1230
|
+
patchFilePath,
|
|
1231
|
+
patchPath: patchFilePath,
|
|
1232
|
+
patchMode,
|
|
1233
|
+
postinstallConfigured: patchMode === "patch-package",
|
|
1234
|
+
validation: validationResult,
|
|
1235
|
+
error: validationError
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
return {
|
|
1240
|
+
success: true,
|
|
1241
|
+
packageName,
|
|
1242
|
+
vulnerableVersion,
|
|
1243
|
+
applied: true,
|
|
1244
|
+
dryRun: false,
|
|
1245
|
+
message: `Patch applied successfully for ${packageName}@${vulnerableVersion}.`,
|
|
1246
|
+
patchFilePath,
|
|
1247
|
+
patchPath: patchFilePath,
|
|
1248
|
+
patchMode,
|
|
1249
|
+
postinstallConfigured: patchMode === "patch-package",
|
|
1250
|
+
validation: validationResult
|
|
1251
|
+
};
|
|
1252
|
+
});
|
|
1202
1253
|
} catch (err) {
|
|
1203
1254
|
const message = err instanceof Error ? err.message : String(err);
|
|
1204
1255
|
return {
|
|
@@ -1233,7 +1284,7 @@ function buildPatchFileName(packageName, vulnerableVersion) {
|
|
|
1233
1284
|
return `${safeName}+${vulnerableVersion}.patch`;
|
|
1234
1285
|
}
|
|
1235
1286
|
async function configurePatchPackagePostinstall(cwd, packageManager) {
|
|
1236
|
-
const pkgJsonPath =
|
|
1287
|
+
const pkgJsonPath = join7(cwd, "package.json");
|
|
1237
1288
|
let pkgJson;
|
|
1238
1289
|
try {
|
|
1239
1290
|
pkgJson = JSON.parse(await readFile2(pkgJsonPath, "utf8"));
|
|
@@ -1297,8 +1348,8 @@ ${createResult.stderr}`);
|
|
|
1297
1348
|
error: `Could not determine native patch directory for ${packageSpec}.`
|
|
1298
1349
|
};
|
|
1299
1350
|
}
|
|
1300
|
-
const tempPatchDir = await mkdtemp(
|
|
1301
|
-
const tempPatchFile =
|
|
1351
|
+
const tempPatchDir = await mkdtemp(join7(tmpdir(), "autoremediator-native-patch-"));
|
|
1352
|
+
const tempPatchFile = join7(tempPatchDir, "change.patch");
|
|
1302
1353
|
try {
|
|
1303
1354
|
await writeFile(tempPatchFile, patchContent, "utf8");
|
|
1304
1355
|
await execa4("patch", ["-p1", "-i", tempPatchFile], {
|
|
@@ -1317,7 +1368,7 @@ ${createResult.stderr}`);
|
|
|
1317
1368
|
error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
|
|
1318
1369
|
};
|
|
1319
1370
|
} finally {
|
|
1320
|
-
await
|
|
1371
|
+
await rm3(tempPatchDir, { recursive: true, force: true });
|
|
1321
1372
|
}
|
|
1322
1373
|
return { success: true };
|
|
1323
1374
|
}
|
|
@@ -1389,7 +1440,8 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
1389
1440
|
}
|
|
1390
1441
|
const cwd = options.cwd ?? process.cwd();
|
|
1391
1442
|
const packageManager = options.packageManager ?? detectPackageManager(cwd);
|
|
1392
|
-
const
|
|
1443
|
+
const preview = options.preview ?? false;
|
|
1444
|
+
const dryRun = (options.dryRun ?? false) || preview;
|
|
1393
1445
|
const skipTests = options.skipTests ?? true;
|
|
1394
1446
|
const policyPath = options.policyPath ?? "";
|
|
1395
1447
|
const patchesDir = options.patchesDir || "./patches";
|
|
@@ -1408,6 +1460,14 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
1408
1460
|
const vulnerablePackages = [];
|
|
1409
1461
|
let cveDetails = null;
|
|
1410
1462
|
let agentSteps = 0;
|
|
1463
|
+
const applyVersionBumpToolForRun = preview ? {
|
|
1464
|
+
...applyVersionBumpTool,
|
|
1465
|
+
execute: async (input) => applyVersionBumpTool.execute({ ...input, dryRun: true })
|
|
1466
|
+
} : applyVersionBumpTool;
|
|
1467
|
+
const applyPatchFileToolForRun = preview ? {
|
|
1468
|
+
...applyPatchFileTool,
|
|
1469
|
+
execute: async (input) => applyPatchFileTool.execute({ ...input, dryRun: true })
|
|
1470
|
+
} : applyPatchFileTool;
|
|
1411
1471
|
const result = await generateText2({
|
|
1412
1472
|
model,
|
|
1413
1473
|
system: systemPrompt,
|
|
@@ -1417,10 +1477,10 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
1417
1477
|
"check-inventory": checkInventoryTool,
|
|
1418
1478
|
"check-version-match": checkVersionMatchTool,
|
|
1419
1479
|
"find-fixed-version": findFixedVersionTool,
|
|
1420
|
-
"apply-version-bump":
|
|
1480
|
+
"apply-version-bump": applyVersionBumpToolForRun,
|
|
1421
1481
|
"fetch-package-source": fetchPackageSourceTool,
|
|
1422
1482
|
"generate-patch": generatePatchTool,
|
|
1423
|
-
"apply-patch-file":
|
|
1483
|
+
"apply-patch-file": applyPatchFileToolForRun
|
|
1424
1484
|
},
|
|
1425
1485
|
maxSteps: 25,
|
|
1426
1486
|
onStepFinish(stepResult) {
|
|
@@ -1463,13 +1523,19 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
1463
1523
|
vulnerablePackages,
|
|
1464
1524
|
results: collectedResults,
|
|
1465
1525
|
agentSteps,
|
|
1466
|
-
summary: result.text
|
|
1526
|
+
summary: result.text,
|
|
1527
|
+
correlation: {
|
|
1528
|
+
requestId: options.requestId,
|
|
1529
|
+
sessionId: options.sessionId,
|
|
1530
|
+
parentRunId: options.parentRunId
|
|
1531
|
+
}
|
|
1467
1532
|
};
|
|
1468
1533
|
}
|
|
1469
1534
|
async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
1470
1535
|
const cwd = options.cwd ?? process.cwd();
|
|
1471
1536
|
const packageManager = options.packageManager ?? detectPackageManager(cwd);
|
|
1472
|
-
const
|
|
1537
|
+
const preview = options.preview ?? false;
|
|
1538
|
+
const dryRun = (options.dryRun ?? false) || preview;
|
|
1473
1539
|
const skipTests = options.skipTests ?? true;
|
|
1474
1540
|
const policyPath = options.policyPath ?? "";
|
|
1475
1541
|
const collectedResults = [];
|
|
@@ -1489,7 +1555,12 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
1489
1555
|
vulnerablePackages,
|
|
1490
1556
|
results: collectedResults,
|
|
1491
1557
|
agentSteps,
|
|
1492
|
-
summary: `Local mode failed at lookup-cve: ${normalizedId} not found in OSV or GitHub advisory data
|
|
1558
|
+
summary: `Local mode failed at lookup-cve: ${normalizedId} not found in OSV or GitHub advisory data.`,
|
|
1559
|
+
correlation: {
|
|
1560
|
+
requestId: options.requestId,
|
|
1561
|
+
sessionId: options.sessionId,
|
|
1562
|
+
parentRunId: options.parentRunId
|
|
1563
|
+
}
|
|
1493
1564
|
};
|
|
1494
1565
|
}
|
|
1495
1566
|
cveDetails = osvDetails ?? {
|
|
@@ -1510,7 +1581,12 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
1510
1581
|
vulnerablePackages,
|
|
1511
1582
|
results: collectedResults,
|
|
1512
1583
|
agentSteps,
|
|
1513
|
-
summary: `Local mode lookup succeeded but no npm affected packages were found for ${normalizedId}
|
|
1584
|
+
summary: `Local mode lookup succeeded but no npm affected packages were found for ${normalizedId}.`,
|
|
1585
|
+
correlation: {
|
|
1586
|
+
requestId: options.requestId,
|
|
1587
|
+
sessionId: options.sessionId,
|
|
1588
|
+
parentRunId: options.parentRunId
|
|
1589
|
+
}
|
|
1514
1590
|
};
|
|
1515
1591
|
}
|
|
1516
1592
|
const inventory = await checkInventoryTool.execute({ cwd, packageManager });
|
|
@@ -1522,7 +1598,12 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
1522
1598
|
vulnerablePackages,
|
|
1523
1599
|
results: collectedResults,
|
|
1524
1600
|
agentSteps,
|
|
1525
|
-
summary: `Local mode failed at check-inventory: ${inventory.error}
|
|
1601
|
+
summary: `Local mode failed at check-inventory: ${inventory.error}`,
|
|
1602
|
+
correlation: {
|
|
1603
|
+
requestId: options.requestId,
|
|
1604
|
+
sessionId: options.sessionId,
|
|
1605
|
+
parentRunId: options.parentRunId
|
|
1606
|
+
}
|
|
1526
1607
|
};
|
|
1527
1608
|
}
|
|
1528
1609
|
const installedPackages = inventory.packages ?? [];
|
|
@@ -1612,11 +1693,16 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
1612
1693
|
vulnerablePackages,
|
|
1613
1694
|
results: collectedResults,
|
|
1614
1695
|
agentSteps,
|
|
1615
|
-
summary: `Local mode completed: vulnerable=${vulnerablePackages.length}, applied=${appliedCount}, dryRun=${dryRunCount}, unresolved=${unresolvedCount}
|
|
1696
|
+
summary: `Local mode completed: vulnerable=${vulnerablePackages.length}, applied=${appliedCount}, dryRun=${dryRunCount}, unresolved=${unresolvedCount}`,
|
|
1697
|
+
correlation: {
|
|
1698
|
+
requestId: options.requestId,
|
|
1699
|
+
sessionId: options.sessionId,
|
|
1700
|
+
parentRunId: options.parentRunId
|
|
1701
|
+
}
|
|
1616
1702
|
};
|
|
1617
1703
|
}
|
|
1618
1704
|
function loadOrchestrationPrompt(ctx) {
|
|
1619
|
-
const promptPath =
|
|
1705
|
+
const promptPath = join8(process.cwd(), ".github", "instructions", "orchestration.instructions.md");
|
|
1620
1706
|
if (!existsSync4(promptPath)) {
|
|
1621
1707
|
return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
|
|
1622
1708
|
Working directory: ${ctx.cwd}
|
|
@@ -1810,10 +1896,16 @@ function uniqueCveIds(findings) {
|
|
|
1810
1896
|
|
|
1811
1897
|
// src/platform/evidence.ts
|
|
1812
1898
|
import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1813
|
-
import { join as
|
|
1814
|
-
function createEvidenceLog(cwd, cveIds) {
|
|
1899
|
+
import { join as join9 } from "path";
|
|
1900
|
+
function createEvidenceLog(cwd, cveIds, context = {}) {
|
|
1815
1901
|
return {
|
|
1816
|
-
runId: `${Date.now()}`,
|
|
1902
|
+
runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1903
|
+
requestId: context.requestId,
|
|
1904
|
+
sessionId: context.sessionId,
|
|
1905
|
+
parentRunId: context.parentRunId,
|
|
1906
|
+
actor: context.actor,
|
|
1907
|
+
source: context.source,
|
|
1908
|
+
idempotencyKey: context.idempotencyKey,
|
|
1817
1909
|
cveIds,
|
|
1818
1910
|
cwd,
|
|
1819
1911
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1834,21 +1926,163 @@ function finalizeEvidence(log) {
|
|
|
1834
1926
|
return log;
|
|
1835
1927
|
}
|
|
1836
1928
|
function writeEvidenceLog(cwd, log) {
|
|
1837
|
-
const dir =
|
|
1929
|
+
const dir = join9(cwd, ".autoremediator", "evidence");
|
|
1838
1930
|
mkdirSync(dir, { recursive: true });
|
|
1839
|
-
const filePath =
|
|
1931
|
+
const filePath = join9(dir, `${log.runId}.json`);
|
|
1840
1932
|
writeFileSync2(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
|
|
1841
1933
|
return filePath;
|
|
1842
1934
|
}
|
|
1843
1935
|
|
|
1936
|
+
// src/platform/idempotency.ts
|
|
1937
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
|
|
1938
|
+
import { join as join10 } from "path";
|
|
1939
|
+
var DEFAULT_INDEX = {
|
|
1940
|
+
schemaVersion: "1.0",
|
|
1941
|
+
entries: {}
|
|
1942
|
+
};
|
|
1943
|
+
function indexFilePath(cwd) {
|
|
1944
|
+
return join10(cwd, ".autoremediator", "state", "idempotency.json");
|
|
1945
|
+
}
|
|
1946
|
+
function entryKey(idempotencyKey, cveId) {
|
|
1947
|
+
return `${idempotencyKey}::${cveId.toUpperCase()}`;
|
|
1948
|
+
}
|
|
1949
|
+
function loadIndex(cwd) {
|
|
1950
|
+
const filePath = indexFilePath(cwd);
|
|
1951
|
+
if (!existsSync5(filePath)) return DEFAULT_INDEX;
|
|
1952
|
+
try {
|
|
1953
|
+
const parsed = JSON.parse(readFileSync9(filePath, "utf8"));
|
|
1954
|
+
if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
|
|
1955
|
+
return parsed;
|
|
1956
|
+
}
|
|
1957
|
+
return DEFAULT_INDEX;
|
|
1958
|
+
} catch {
|
|
1959
|
+
return DEFAULT_INDEX;
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
function saveIndex(cwd, index) {
|
|
1963
|
+
const filePath = indexFilePath(cwd);
|
|
1964
|
+
mkdirSync2(join10(cwd, ".autoremediator", "state"), { recursive: true });
|
|
1965
|
+
writeFileSync3(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
|
|
1966
|
+
}
|
|
1967
|
+
function readIdempotentReport(cwd, idempotencyKey, cveId) {
|
|
1968
|
+
const index = loadIndex(cwd);
|
|
1969
|
+
const key = entryKey(idempotencyKey, cveId);
|
|
1970
|
+
return index.entries[key]?.report;
|
|
1971
|
+
}
|
|
1972
|
+
function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
|
|
1973
|
+
const index = loadIndex(cwd);
|
|
1974
|
+
const key = entryKey(idempotencyKey, cveId);
|
|
1975
|
+
index.entries[key] = {
|
|
1976
|
+
key: idempotencyKey,
|
|
1977
|
+
cveId: cveId.toUpperCase(),
|
|
1978
|
+
report,
|
|
1979
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1980
|
+
};
|
|
1981
|
+
saveIndex(cwd, index);
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1844
1984
|
// src/api.ts
|
|
1985
|
+
function buildRequestId() {
|
|
1986
|
+
return `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1987
|
+
}
|
|
1988
|
+
function resolveCorrelationContext(options) {
|
|
1989
|
+
return {
|
|
1990
|
+
requestId: options.requestId ?? buildRequestId(),
|
|
1991
|
+
sessionId: options.sessionId,
|
|
1992
|
+
parentRunId: options.parentRunId
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
function resolveProvenanceContext(options) {
|
|
1996
|
+
return {
|
|
1997
|
+
actor: options.actor,
|
|
1998
|
+
source: options.source ?? "sdk"
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
function resolveConstraints(options, cwd) {
|
|
2002
|
+
const policy = loadPolicy(cwd, options.policyPath);
|
|
2003
|
+
return {
|
|
2004
|
+
directDependenciesOnly: options.constraints?.directDependenciesOnly ?? policy.constraints?.directDependenciesOnly ?? false,
|
|
2005
|
+
preferVersionBump: options.constraints?.preferVersionBump ?? policy.constraints?.preferVersionBump ?? false
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
function enforceConstraints(report, constraints) {
|
|
2009
|
+
const indirectPackages = new Set(
|
|
2010
|
+
report.vulnerablePackages.filter((vp) => vp.installed.type === "indirect").map((vp) => vp.installed.name)
|
|
2011
|
+
);
|
|
2012
|
+
const nextResults = report.results.map((result) => {
|
|
2013
|
+
if (constraints.directDependenciesOnly && indirectPackages.has(result.packageName)) {
|
|
2014
|
+
return {
|
|
2015
|
+
...result,
|
|
2016
|
+
strategy: "none",
|
|
2017
|
+
applied: false,
|
|
2018
|
+
message: `Constraint blocked remediation for indirect dependency "${result.packageName}".`
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
if (constraints.preferVersionBump && result.strategy === "patch-file") {
|
|
2022
|
+
return {
|
|
2023
|
+
...result,
|
|
2024
|
+
strategy: "none",
|
|
2025
|
+
applied: false,
|
|
2026
|
+
message: `Constraint prefers version-bump and rejected patch-file remediation for "${result.packageName}".`
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
return result;
|
|
2030
|
+
});
|
|
2031
|
+
return {
|
|
2032
|
+
...report,
|
|
2033
|
+
results: nextResults,
|
|
2034
|
+
constraints
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
1845
2037
|
async function remediate(cveId, options = {}) {
|
|
1846
2038
|
if (!/^CVE-\d{4}-\d+$/i.test(cveId)) {
|
|
1847
2039
|
throw new Error(
|
|
1848
2040
|
`Invalid CVE ID: "${cveId}". Expected format: CVE-YYYY-NNNNN (e.g. CVE-2021-23337).`
|
|
1849
2041
|
);
|
|
1850
2042
|
}
|
|
1851
|
-
|
|
2043
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2044
|
+
const constraints = resolveConstraints(options, cwd);
|
|
2045
|
+
const provenance = resolveProvenanceContext(options);
|
|
2046
|
+
const correlation = resolveCorrelationContext(options);
|
|
2047
|
+
if (options.resume && options.idempotencyKey) {
|
|
2048
|
+
const cached = readIdempotentReport(cwd, options.idempotencyKey, cveId.toUpperCase());
|
|
2049
|
+
if (cached) {
|
|
2050
|
+
return {
|
|
2051
|
+
...cached,
|
|
2052
|
+
summary: `${cached.summary} (resumed from idempotency cache)`,
|
|
2053
|
+
correlation,
|
|
2054
|
+
provenance,
|
|
2055
|
+
constraints,
|
|
2056
|
+
resumedFromCache: true
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
const report = await runRemediationPipeline(cveId.toUpperCase(), {
|
|
2061
|
+
...options,
|
|
2062
|
+
...correlation,
|
|
2063
|
+
constraints
|
|
2064
|
+
});
|
|
2065
|
+
const constrainedReport = enforceConstraints(report, constraints);
|
|
2066
|
+
const finalReport = {
|
|
2067
|
+
...constrainedReport,
|
|
2068
|
+
correlation,
|
|
2069
|
+
provenance,
|
|
2070
|
+
constraints,
|
|
2071
|
+
resumedFromCache: false
|
|
2072
|
+
};
|
|
2073
|
+
if (options.idempotencyKey && !options.dryRun && !options.preview) {
|
|
2074
|
+
storeIdempotentReport(cwd, options.idempotencyKey, cveId.toUpperCase(), finalReport);
|
|
2075
|
+
}
|
|
2076
|
+
return {
|
|
2077
|
+
...finalReport
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
async function planRemediation(cveId, options = {}) {
|
|
2081
|
+
return remediate(cveId, {
|
|
2082
|
+
...options,
|
|
2083
|
+
preview: true,
|
|
2084
|
+
dryRun: true
|
|
2085
|
+
});
|
|
1852
2086
|
}
|
|
1853
2087
|
async function remediateFromScan(inputPath, options = {}) {
|
|
1854
2088
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -1857,7 +2091,15 @@ async function remediateFromScan(inputPath, options = {}) {
|
|
|
1857
2091
|
const findings = parseScanInput(inputPath, format);
|
|
1858
2092
|
const cveIds = uniqueCveIds(findings);
|
|
1859
2093
|
const policy = loadPolicy(cwd, options.policyPath);
|
|
1860
|
-
const
|
|
2094
|
+
const correlation = resolveCorrelationContext(options);
|
|
2095
|
+
const provenance = resolveProvenanceContext(options);
|
|
2096
|
+
const constraints = resolveConstraints(options, cwd);
|
|
2097
|
+
const evidence = createEvidenceLog(cwd, cveIds, {
|
|
2098
|
+
...correlation,
|
|
2099
|
+
actor: provenance.actor,
|
|
2100
|
+
source: provenance.source,
|
|
2101
|
+
idempotencyKey: options.idempotencyKey
|
|
2102
|
+
});
|
|
1861
2103
|
addEvidenceStep(evidence, "scan.parse", { inputPath, format }, { findingCount: findings.length, cveCount: cveIds.length });
|
|
1862
2104
|
const reports = [];
|
|
1863
2105
|
const errors = [];
|
|
@@ -1868,7 +2110,11 @@ async function remediateFromScan(inputPath, options = {}) {
|
|
|
1868
2110
|
addEvidenceStep(evidence, "remediate.start", { cveId });
|
|
1869
2111
|
const report = await remediate(cveId, {
|
|
1870
2112
|
...options,
|
|
1871
|
-
patchesDir
|
|
2113
|
+
patchesDir,
|
|
2114
|
+
...correlation,
|
|
2115
|
+
actor: provenance.actor,
|
|
2116
|
+
source: provenance.source,
|
|
2117
|
+
constraints
|
|
1872
2118
|
});
|
|
1873
2119
|
report.results = report.results.filter((r) => isPackageAllowed(policy, r.packageName));
|
|
1874
2120
|
for (const result of report.results) {
|
|
@@ -1923,7 +2169,11 @@ async function remediateFromScan(inputPath, options = {}) {
|
|
|
1923
2169
|
evidenceFile,
|
|
1924
2170
|
patchFileCount,
|
|
1925
2171
|
patchValidationFailures: patchValidationFailures.length > 0 ? patchValidationFailures : void 0,
|
|
1926
|
-
patchStorageDir: patchFileCount > 0 ? patchesDir : void 0
|
|
2172
|
+
patchStorageDir: patchFileCount > 0 ? patchesDir : void 0,
|
|
2173
|
+
correlation,
|
|
2174
|
+
provenance,
|
|
2175
|
+
constraints,
|
|
2176
|
+
idempotencyKey: options.idempotencyKey
|
|
1927
2177
|
};
|
|
1928
2178
|
}
|
|
1929
2179
|
function toCiSummary(report) {
|
|
@@ -1943,7 +2193,11 @@ function toCiSummary(report) {
|
|
|
1943
2193
|
evidenceFile: report.evidenceFile,
|
|
1944
2194
|
patchFileCount: report.patchFileCount || 0,
|
|
1945
2195
|
patchValidationFailures: report.patchValidationFailures,
|
|
1946
|
-
patchStorageDir: report.patchStorageDir
|
|
2196
|
+
patchStorageDir: report.patchStorageDir,
|
|
2197
|
+
correlation: report.correlation,
|
|
2198
|
+
provenance: report.provenance,
|
|
2199
|
+
constraints: report.constraints,
|
|
2200
|
+
idempotencyKey: report.idempotencyKey
|
|
1947
2201
|
};
|
|
1948
2202
|
}
|
|
1949
2203
|
function ciExitCode(summary) {
|
|
@@ -1953,8 +2207,9 @@ function ciExitCode(summary) {
|
|
|
1953
2207
|
export {
|
|
1954
2208
|
runRemediationPipeline,
|
|
1955
2209
|
remediate,
|
|
2210
|
+
planRemediation,
|
|
1956
2211
|
remediateFromScan,
|
|
1957
2212
|
toCiSummary,
|
|
1958
2213
|
ciExitCode
|
|
1959
2214
|
};
|
|
1960
|
-
//# sourceMappingURL=chunk-
|
|
2215
|
+
//# sourceMappingURL=chunk-URM53GSJ.js.map
|