autoremediator 0.7.0 → 0.8.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 -15
- package/dist/{chunk-MUFP2DQX.js → chunk-5S4Y3WVZ.js} +822 -146
- package/dist/chunk-5S4Y3WVZ.js.map +1 -0
- package/dist/cli.js +135 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +9 -3
- package/dist/mcp/server.d.ts +75 -2
- package/dist/mcp/server.js +72 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/openapi/server.d.ts +301 -4
- package/dist/openapi/server.js +211 -5
- package/dist/openapi/server.js.map +1 -1
- package/dist/{options-schema-DfLBOsPI.d.ts → options-schema-DHXBY2qm.d.ts} +8 -2
- package/dist/{remediate-from-scan-C-E7gqxF.d.ts → remediate-from-scan-BmFPbhp2.d.ts} +121 -4
- package/llms.txt +30 -7
- package/package.json +6 -2
- package/dist/chunk-MUFP2DQX.js.map +0 -1
|
@@ -1,57 +1,139 @@
|
|
|
1
1
|
// src/remediation/pipeline.ts
|
|
2
2
|
import { generateText as generateText2 } from "ai";
|
|
3
3
|
|
|
4
|
+
// src/platform/policy.ts
|
|
5
|
+
import { existsSync, readFileSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
var DEFAULT_POLICY = {
|
|
8
|
+
allowMajorBumps: false,
|
|
9
|
+
denyPackages: [],
|
|
10
|
+
allowPackages: [],
|
|
11
|
+
constraints: {
|
|
12
|
+
directDependenciesOnly: false,
|
|
13
|
+
preferVersionBump: false
|
|
14
|
+
},
|
|
15
|
+
modelDefaults: {},
|
|
16
|
+
providerSafetyProfile: "relaxed",
|
|
17
|
+
requireConsensusForHighRisk: false,
|
|
18
|
+
dynamicModelRouting: false,
|
|
19
|
+
dynamicRoutingThresholdChars: 18e3
|
|
20
|
+
};
|
|
21
|
+
function loadPolicy(cwd, explicitPath) {
|
|
22
|
+
const candidate = explicitPath ?? join(cwd, ".autoremediator.json");
|
|
23
|
+
if (!existsSync(candidate)) return DEFAULT_POLICY;
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
|
|
26
|
+
return {
|
|
27
|
+
allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
|
|
28
|
+
denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
|
|
29
|
+
allowPackages: parsed.allowPackages ?? DEFAULT_POLICY.allowPackages,
|
|
30
|
+
constraints: {
|
|
31
|
+
directDependenciesOnly: parsed.constraints?.directDependenciesOnly ?? DEFAULT_POLICY.constraints?.directDependenciesOnly ?? false,
|
|
32
|
+
preferVersionBump: parsed.constraints?.preferVersionBump ?? DEFAULT_POLICY.constraints?.preferVersionBump ?? false
|
|
33
|
+
},
|
|
34
|
+
modelDefaults: {
|
|
35
|
+
remote: parsed.modelDefaults?.remote ?? DEFAULT_POLICY.modelDefaults?.remote,
|
|
36
|
+
local: parsed.modelDefaults?.local ?? DEFAULT_POLICY.modelDefaults?.local
|
|
37
|
+
},
|
|
38
|
+
providerSafetyProfile: parsed.providerSafetyProfile ?? DEFAULT_POLICY.providerSafetyProfile,
|
|
39
|
+
requireConsensusForHighRisk: parsed.requireConsensusForHighRisk ?? DEFAULT_POLICY.requireConsensusForHighRisk,
|
|
40
|
+
dynamicModelRouting: parsed.dynamicModelRouting ?? DEFAULT_POLICY.dynamicModelRouting,
|
|
41
|
+
dynamicRoutingThresholdChars: parsed.dynamicRoutingThresholdChars ?? DEFAULT_POLICY.dynamicRoutingThresholdChars
|
|
42
|
+
};
|
|
43
|
+
} catch {
|
|
44
|
+
return DEFAULT_POLICY;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function isPackageAllowed(policy, packageName) {
|
|
48
|
+
if (policy.denyPackages.includes(packageName)) return false;
|
|
49
|
+
if (policy.allowPackages.length > 0 && !policy.allowPackages.includes(packageName)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
4
55
|
// src/platform/config.ts
|
|
5
56
|
function resolveProvider(options = {}) {
|
|
6
|
-
const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "
|
|
7
|
-
if (raw !== "
|
|
57
|
+
const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "remote";
|
|
58
|
+
if (raw !== "remote" && raw !== "local") {
|
|
8
59
|
throw new Error(
|
|
9
|
-
`Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "
|
|
60
|
+
`Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
|
|
10
61
|
);
|
|
11
62
|
}
|
|
12
63
|
return raw;
|
|
13
64
|
}
|
|
14
|
-
function resolveModelName(provider, options = {}) {
|
|
65
|
+
function resolveModelName(provider, options = {}, routing = {}) {
|
|
15
66
|
if (options.model) return options.model;
|
|
16
67
|
if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
|
|
68
|
+
const cwd = options.cwd ?? process.cwd();
|
|
69
|
+
const policy = loadPolicy(cwd, options.policy);
|
|
70
|
+
const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
|
|
71
|
+
if (providerEnvModel) return providerEnvModel;
|
|
72
|
+
const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
|
|
73
|
+
if (policyPinnedModel) return policyPinnedModel;
|
|
17
74
|
const defaults = {
|
|
18
|
-
|
|
19
|
-
anthropic: "claude-sonnet-4-5",
|
|
75
|
+
remote: "remote-default",
|
|
20
76
|
local: "local"
|
|
21
77
|
};
|
|
78
|
+
const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
|
|
79
|
+
const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
|
|
80
|
+
if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
|
|
81
|
+
return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
|
|
82
|
+
}
|
|
22
83
|
return defaults[provider];
|
|
23
84
|
}
|
|
24
|
-
async function
|
|
85
|
+
async function loadRemoteFactory() {
|
|
86
|
+
const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
|
|
87
|
+
const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
|
|
88
|
+
if (!moduleName) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
"AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const loaded = await import(moduleName);
|
|
94
|
+
const factory = loaded[exportName];
|
|
95
|
+
if (typeof factory !== "function") {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Remote client factory "${exportName}" was not found in module "${moduleName}".`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return factory;
|
|
101
|
+
}
|
|
102
|
+
async function createModel(options = {}, routing = {}) {
|
|
25
103
|
const provider = resolveProvider(options);
|
|
26
104
|
if (provider === "local") {
|
|
27
105
|
throw new Error(
|
|
28
106
|
"Local provider does not create a language model. Use the deterministic pipeline path instead."
|
|
29
107
|
);
|
|
30
108
|
}
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"OPENAI_API_KEY environment variable is required when using the openai provider."
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
40
|
-
const openai = createOpenAI({ apiKey });
|
|
41
|
-
return openai(modelName);
|
|
42
|
-
}
|
|
43
|
-
if (provider === "anthropic") {
|
|
44
|
-
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
45
|
-
if (!apiKey) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
"ANTHROPIC_API_KEY environment variable is required when using the anthropic provider."
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
const { createAnthropic } = await import("@ai-sdk/anthropic");
|
|
51
|
-
const anthropic = createAnthropic({ apiKey });
|
|
52
|
-
return anthropic(modelName);
|
|
109
|
+
const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
|
|
110
|
+
if (!apiKey) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
"AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
|
|
113
|
+
);
|
|
53
114
|
}
|
|
54
|
-
|
|
115
|
+
const modelName = resolveModelName(provider, options, routing);
|
|
116
|
+
const createRemoteClient = await loadRemoteFactory();
|
|
117
|
+
const remoteClient = createRemoteClient({ apiKey });
|
|
118
|
+
return remoteClient(modelName);
|
|
119
|
+
}
|
|
120
|
+
function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed") {
|
|
121
|
+
void provider;
|
|
122
|
+
const relaxed = {
|
|
123
|
+
remote: 0.7,
|
|
124
|
+
local: 0.7
|
|
125
|
+
};
|
|
126
|
+
const strict = {
|
|
127
|
+
remote: 0.85,
|
|
128
|
+
local: 0.85
|
|
129
|
+
};
|
|
130
|
+
return safetyProfile === "strict" ? strict[provider] : relaxed[provider];
|
|
131
|
+
}
|
|
132
|
+
function estimateModelCostUsd(params) {
|
|
133
|
+
const inputTokens = params.promptChars / 4;
|
|
134
|
+
const outputTokens = params.completionChars / 4;
|
|
135
|
+
const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
|
|
136
|
+
return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
|
|
55
137
|
}
|
|
56
138
|
function getNvdConfig() {
|
|
57
139
|
return {
|
|
@@ -76,11 +158,11 @@ function getIntelligenceSourceConfig() {
|
|
|
76
158
|
}
|
|
77
159
|
|
|
78
160
|
// src/platform/package-manager.ts
|
|
79
|
-
import { existsSync } from "fs";
|
|
80
|
-
import { join } from "path";
|
|
161
|
+
import { existsSync as existsSync2 } from "fs";
|
|
162
|
+
import { join as join2 } from "path";
|
|
81
163
|
function detectPackageManager(cwd) {
|
|
82
|
-
if (
|
|
83
|
-
if (
|
|
164
|
+
if (existsSync2(join2(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
165
|
+
if (existsSync2(join2(cwd, "yarn.lock"))) return "yarn";
|
|
84
166
|
return "npm";
|
|
85
167
|
}
|
|
86
168
|
function getPackageManagerCommands(pm) {
|
|
@@ -167,49 +249,11 @@ import { readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
|
167
249
|
import { execa } from "execa";
|
|
168
250
|
import semver from "semver";
|
|
169
251
|
|
|
170
|
-
// src/platform/policy.ts
|
|
171
|
-
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
172
|
-
import { join as join2 } from "path";
|
|
173
|
-
var DEFAULT_POLICY = {
|
|
174
|
-
allowMajorBumps: false,
|
|
175
|
-
denyPackages: [],
|
|
176
|
-
allowPackages: [],
|
|
177
|
-
constraints: {
|
|
178
|
-
directDependenciesOnly: false,
|
|
179
|
-
preferVersionBump: false
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
function loadPolicy(cwd, explicitPath) {
|
|
183
|
-
const candidate = explicitPath ?? join2(cwd, ".autoremediator.json");
|
|
184
|
-
if (!existsSync2(candidate)) return DEFAULT_POLICY;
|
|
185
|
-
try {
|
|
186
|
-
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
|
|
187
|
-
return {
|
|
188
|
-
allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
|
|
189
|
-
denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
|
|
190
|
-
allowPackages: parsed.allowPackages ?? DEFAULT_POLICY.allowPackages,
|
|
191
|
-
constraints: {
|
|
192
|
-
directDependenciesOnly: parsed.constraints?.directDependenciesOnly ?? DEFAULT_POLICY.constraints?.directDependenciesOnly ?? false,
|
|
193
|
-
preferVersionBump: parsed.constraints?.preferVersionBump ?? DEFAULT_POLICY.constraints?.preferVersionBump ?? false
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
} catch {
|
|
197
|
-
return DEFAULT_POLICY;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
function isPackageAllowed(policy, packageName) {
|
|
201
|
-
if (policy.denyPackages.includes(packageName)) return false;
|
|
202
|
-
if (policy.allowPackages.length > 0 && !policy.allowPackages.includes(packageName)) {
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
252
|
// src/platform/repo-lock.ts
|
|
209
253
|
import { mkdir, rm } from "fs/promises";
|
|
210
254
|
import { join as join3 } from "path";
|
|
211
255
|
async function sleep(ms) {
|
|
212
|
-
await new Promise((
|
|
256
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
213
257
|
}
|
|
214
258
|
async function acquireRepoLock(cwd, options = {}) {
|
|
215
259
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
@@ -661,6 +705,9 @@ var applyPatchFileTool = tool3({
|
|
|
661
705
|
packageName: z3.string().min(1).describe("The npm package name"),
|
|
662
706
|
vulnerableVersion: z3.string().describe("The vulnerable version string"),
|
|
663
707
|
patchContent: z3.string().min(10).optional().describe("Unified diff patch content from generate-patch"),
|
|
708
|
+
cveId: z3.string().optional().describe("Optional CVE ID associated with this patch artifact"),
|
|
709
|
+
confidence: z3.number().min(0).max(1).optional().describe("Optional patch confidence score from generate-patch"),
|
|
710
|
+
riskLevel: z3.enum(["low", "medium", "high"]).optional().describe("Optional risk level from generate-patch"),
|
|
664
711
|
patches: z3.array(
|
|
665
712
|
z3.object({
|
|
666
713
|
filePath: z3.string().min(1),
|
|
@@ -679,16 +726,22 @@ var applyPatchFileTool = tool3({
|
|
|
679
726
|
packageName,
|
|
680
727
|
vulnerableVersion,
|
|
681
728
|
patchContent,
|
|
729
|
+
cveId,
|
|
730
|
+
confidence,
|
|
682
731
|
patches,
|
|
683
732
|
patchesDir,
|
|
684
733
|
cwd,
|
|
685
734
|
packageManager,
|
|
735
|
+
riskLevel,
|
|
686
736
|
validateWithTests,
|
|
687
737
|
dryRun
|
|
688
738
|
}) => {
|
|
689
739
|
try {
|
|
690
740
|
const pm = packageManager ?? detectPackageManager(cwd);
|
|
691
741
|
const selectedPatch = patchContent ?? patches?.[0]?.unifiedDiff;
|
|
742
|
+
const patchFiles = extractPatchedFiles(selectedPatch ?? "");
|
|
743
|
+
const hunkCount = countPatchHunks(selectedPatch ?? "");
|
|
744
|
+
const validationPhases = [];
|
|
692
745
|
if (!selectedPatch) {
|
|
693
746
|
return {
|
|
694
747
|
success: false,
|
|
@@ -701,6 +754,12 @@ var applyPatchFileTool = tool3({
|
|
|
701
754
|
};
|
|
702
755
|
}
|
|
703
756
|
const patchValidation = validatePatchDiff(selectedPatch);
|
|
757
|
+
validationPhases.push({
|
|
758
|
+
phase: "diff-format",
|
|
759
|
+
passed: patchValidation.valid,
|
|
760
|
+
error: patchValidation.valid ? void 0 : patchValidation.error,
|
|
761
|
+
message: patchValidation.valid ? "Patch content is a valid unified diff." : void 0
|
|
762
|
+
});
|
|
704
763
|
if (!patchValidation.valid) {
|
|
705
764
|
return {
|
|
706
765
|
success: false,
|
|
@@ -709,11 +768,32 @@ var applyPatchFileTool = tool3({
|
|
|
709
768
|
applied: false,
|
|
710
769
|
dryRun,
|
|
711
770
|
message: patchValidation.error ?? "Patch content is not a valid unified diff.",
|
|
771
|
+
validationPhases,
|
|
712
772
|
error: patchValidation.error ?? "Patch content is not a valid unified diff."
|
|
713
773
|
};
|
|
714
774
|
}
|
|
715
775
|
const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
|
|
716
776
|
const patchFilePath = join7(cwd, patchesDir, patchFileName);
|
|
777
|
+
const manifestFilePath = `${patchFilePath}.json`;
|
|
778
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
779
|
+
const baseArtifact = {
|
|
780
|
+
schemaVersion: "1.0",
|
|
781
|
+
cveId,
|
|
782
|
+
packageName,
|
|
783
|
+
vulnerableVersion,
|
|
784
|
+
patchFilePath,
|
|
785
|
+
manifestFilePath,
|
|
786
|
+
patchFileName,
|
|
787
|
+
patchesDir,
|
|
788
|
+
confidence,
|
|
789
|
+
riskLevel,
|
|
790
|
+
generatedAt,
|
|
791
|
+
files: patchFiles,
|
|
792
|
+
hunkCount,
|
|
793
|
+
applied: false,
|
|
794
|
+
dryRun,
|
|
795
|
+
validationPhases
|
|
796
|
+
};
|
|
717
797
|
if (dryRun) {
|
|
718
798
|
return {
|
|
719
799
|
success: true,
|
|
@@ -723,7 +803,10 @@ var applyPatchFileTool = tool3({
|
|
|
723
803
|
dryRun: true,
|
|
724
804
|
message: `[DRY RUN] Would write and configure patch at ${patchFilePath}.`,
|
|
725
805
|
patchFilePath,
|
|
726
|
-
|
|
806
|
+
manifestFilePath,
|
|
807
|
+
patchPath: patchFilePath,
|
|
808
|
+
patchArtifact: baseArtifact,
|
|
809
|
+
validationPhases
|
|
727
810
|
};
|
|
728
811
|
}
|
|
729
812
|
return withRepoLock(cwd, async () => {
|
|
@@ -731,21 +814,41 @@ var applyPatchFileTool = tool3({
|
|
|
731
814
|
const patchesDirPath = join7(cwd, patchesDir);
|
|
732
815
|
await mkdir2(patchesDirPath, { recursive: true });
|
|
733
816
|
await writeFile(patchFilePath, selectedPatch, "utf8");
|
|
817
|
+
validationPhases.push({
|
|
818
|
+
phase: "patch-write",
|
|
819
|
+
passed: true,
|
|
820
|
+
message: `Patch file written to ${patchFilePath}.`
|
|
821
|
+
});
|
|
734
822
|
let validationResult;
|
|
735
823
|
const patchMode = await resolvePatchMode(pm, cwd);
|
|
736
824
|
const commands = getPackageManagerCommands(pm);
|
|
825
|
+
const artifact = {
|
|
826
|
+
...baseArtifact,
|
|
827
|
+
patchMode,
|
|
828
|
+
validationPhases
|
|
829
|
+
};
|
|
830
|
+
await writePatchManifest(manifestFilePath, artifact);
|
|
831
|
+
validationPhases.push({
|
|
832
|
+
phase: "manifest-write",
|
|
833
|
+
passed: true,
|
|
834
|
+
message: `Patch manifest written to ${manifestFilePath}.`
|
|
835
|
+
});
|
|
836
|
+
artifact.validationPhases = validationPhases;
|
|
837
|
+
await writePatchManifest(manifestFilePath, artifact);
|
|
737
838
|
const applyResult = patchMode === "patch-package" ? await configurePatchPackagePostinstall(cwd, pm) : await applyNativePatch({
|
|
738
839
|
cwd,
|
|
739
840
|
packageName,
|
|
740
841
|
vulnerableVersion,
|
|
741
842
|
patchContent: selectedPatch,
|
|
742
|
-
patchMode
|
|
843
|
+
patchMode,
|
|
844
|
+
validationPhases
|
|
743
845
|
});
|
|
744
846
|
if (!applyResult.success) {
|
|
745
847
|
await cleanupPatchArtifacts({
|
|
746
848
|
cwd,
|
|
747
849
|
packageManager: pm,
|
|
748
850
|
patchFilePath,
|
|
851
|
+
manifestFilePath,
|
|
749
852
|
patchMode,
|
|
750
853
|
packageJsonSnapshot,
|
|
751
854
|
rerunInstall: patchMode === "patch-package"
|
|
@@ -758,12 +861,24 @@ var applyPatchFileTool = tool3({
|
|
|
758
861
|
dryRun: false,
|
|
759
862
|
message: applyResult.error,
|
|
760
863
|
patchFilePath,
|
|
864
|
+
manifestFilePath,
|
|
761
865
|
patchPath: patchFilePath,
|
|
762
866
|
patchMode,
|
|
763
867
|
postinstallConfigured: patchMode === "patch-package" ? false : void 0,
|
|
868
|
+
patchArtifact: {
|
|
869
|
+
...artifact,
|
|
870
|
+
applied: false,
|
|
871
|
+
validationPhases
|
|
872
|
+
},
|
|
873
|
+
validationPhases,
|
|
764
874
|
error: applyResult.error
|
|
765
875
|
};
|
|
766
876
|
}
|
|
877
|
+
validationPhases.push({
|
|
878
|
+
phase: "apply",
|
|
879
|
+
passed: true,
|
|
880
|
+
message: `Patch applied using ${patchMode}.`
|
|
881
|
+
});
|
|
767
882
|
if (patchMode === "patch-package") {
|
|
768
883
|
try {
|
|
769
884
|
const [installCmd, ...installArgs] = commands.installPreferOffline;
|
|
@@ -771,16 +886,27 @@ var applyPatchFileTool = tool3({
|
|
|
771
886
|
cwd,
|
|
772
887
|
stdio: "pipe"
|
|
773
888
|
});
|
|
889
|
+
validationPhases.push({
|
|
890
|
+
phase: "install",
|
|
891
|
+
passed: true,
|
|
892
|
+
message: `${commands.installPreferOffline.join(" ")} completed successfully.`
|
|
893
|
+
});
|
|
774
894
|
} catch (err) {
|
|
775
895
|
await cleanupPatchArtifacts({
|
|
776
896
|
cwd,
|
|
777
897
|
packageManager: pm,
|
|
778
898
|
patchFilePath,
|
|
899
|
+
manifestFilePath,
|
|
779
900
|
patchMode,
|
|
780
901
|
packageJsonSnapshot,
|
|
781
902
|
rerunInstall: true
|
|
782
903
|
});
|
|
783
904
|
const error = err instanceof Error ? err.message : String(err);
|
|
905
|
+
validationPhases.push({
|
|
906
|
+
phase: "install",
|
|
907
|
+
passed: false,
|
|
908
|
+
error
|
|
909
|
+
});
|
|
784
910
|
return {
|
|
785
911
|
success: false,
|
|
786
912
|
packageName,
|
|
@@ -789,9 +915,16 @@ var applyPatchFileTool = tool3({
|
|
|
789
915
|
dryRun: false,
|
|
790
916
|
message: `Failed to apply patch-package workflow for ${packageName}@${vulnerableVersion}: ${error}`,
|
|
791
917
|
patchFilePath,
|
|
918
|
+
manifestFilePath,
|
|
792
919
|
patchPath: patchFilePath,
|
|
793
920
|
patchMode,
|
|
794
921
|
postinstallConfigured: false,
|
|
922
|
+
patchArtifact: {
|
|
923
|
+
...artifact,
|
|
924
|
+
applied: false,
|
|
925
|
+
validationPhases
|
|
926
|
+
},
|
|
927
|
+
validationPhases,
|
|
795
928
|
error: `Failed to apply patch-package workflow for ${packageName}@${vulnerableVersion}: ${error}`
|
|
796
929
|
};
|
|
797
930
|
}
|
|
@@ -803,11 +936,17 @@ var applyPatchFileTool = tool3({
|
|
|
803
936
|
cwd,
|
|
804
937
|
packageManager: pm,
|
|
805
938
|
patchFilePath,
|
|
939
|
+
manifestFilePath,
|
|
806
940
|
patchMode,
|
|
807
941
|
packageJsonSnapshot,
|
|
808
942
|
rerunInstall: patchMode === "patch-package"
|
|
809
943
|
});
|
|
810
944
|
const validationError = "Patch validation failed after apply; patch marked unresolved.";
|
|
945
|
+
validationPhases.push({
|
|
946
|
+
phase: "test",
|
|
947
|
+
passed: false,
|
|
948
|
+
error: validationResult.error
|
|
949
|
+
});
|
|
811
950
|
return {
|
|
812
951
|
success: false,
|
|
813
952
|
packageName,
|
|
@@ -816,14 +955,33 @@ var applyPatchFileTool = tool3({
|
|
|
816
955
|
dryRun: false,
|
|
817
956
|
message: validationError,
|
|
818
957
|
patchFilePath,
|
|
958
|
+
manifestFilePath,
|
|
819
959
|
patchPath: patchFilePath,
|
|
820
960
|
patchMode,
|
|
821
961
|
postinstallConfigured: false,
|
|
962
|
+
patchArtifact: {
|
|
963
|
+
...artifact,
|
|
964
|
+
applied: false,
|
|
965
|
+
validationPhases
|
|
966
|
+
},
|
|
822
967
|
validation: validationResult,
|
|
968
|
+
validationPhases,
|
|
823
969
|
error: validationError
|
|
824
970
|
};
|
|
825
971
|
}
|
|
972
|
+
validationPhases.push({
|
|
973
|
+
phase: "test",
|
|
974
|
+
passed: true,
|
|
975
|
+
message: "Patch validation tests passed."
|
|
976
|
+
});
|
|
826
977
|
}
|
|
978
|
+
const finalArtifact = {
|
|
979
|
+
...artifact,
|
|
980
|
+
applied: true,
|
|
981
|
+
dryRun: false,
|
|
982
|
+
validationPhases
|
|
983
|
+
};
|
|
984
|
+
await writePatchManifest(manifestFilePath, finalArtifact);
|
|
827
985
|
return {
|
|
828
986
|
success: true,
|
|
829
987
|
packageName,
|
|
@@ -832,10 +990,13 @@ var applyPatchFileTool = tool3({
|
|
|
832
990
|
dryRun: false,
|
|
833
991
|
message: `Patch applied successfully for ${packageName}@${vulnerableVersion}.`,
|
|
834
992
|
patchFilePath,
|
|
993
|
+
manifestFilePath,
|
|
835
994
|
patchPath: patchFilePath,
|
|
836
995
|
patchMode,
|
|
837
996
|
postinstallConfigured: patchMode === "patch-package",
|
|
838
|
-
|
|
997
|
+
patchArtifact: finalArtifact,
|
|
998
|
+
validation: validationResult,
|
|
999
|
+
validationPhases
|
|
839
1000
|
};
|
|
840
1001
|
});
|
|
841
1002
|
} catch (err) {
|
|
@@ -926,8 +1087,11 @@ async function capturePackageJsonSnapshot(cwd) {
|
|
|
926
1087
|
}
|
|
927
1088
|
}
|
|
928
1089
|
async function cleanupPatchArtifacts(params) {
|
|
929
|
-
const { cwd, packageManager, patchFilePath, patchMode, packageJsonSnapshot, rerunInstall } = params;
|
|
1090
|
+
const { cwd, packageManager, patchFilePath, manifestFilePath, patchMode, packageJsonSnapshot, rerunInstall } = params;
|
|
930
1091
|
await rm2(patchFilePath, { force: true }).catch(() => void 0);
|
|
1092
|
+
if (manifestFilePath) {
|
|
1093
|
+
await rm2(manifestFilePath, { force: true }).catch(() => void 0);
|
|
1094
|
+
}
|
|
931
1095
|
if (patchMode === "patch-package" && packageJsonSnapshot) {
|
|
932
1096
|
await writeFile(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(() => void 0);
|
|
933
1097
|
}
|
|
@@ -943,7 +1107,7 @@ async function cleanupPatchArtifacts(params) {
|
|
|
943
1107
|
}
|
|
944
1108
|
}
|
|
945
1109
|
async function applyNativePatch(params) {
|
|
946
|
-
const { cwd, packageName, vulnerableVersion, patchContent, patchMode } = params;
|
|
1110
|
+
const { cwd, packageName, vulnerableVersion, patchContent, patchMode, validationPhases } = params;
|
|
947
1111
|
const packageSpec = `${packageName}@${vulnerableVersion}`;
|
|
948
1112
|
const createCommand = patchMode === "native-pnpm" ? "pnpm" : "yarn";
|
|
949
1113
|
const createArgs = ["patch", packageSpec];
|
|
@@ -956,12 +1120,22 @@ async function applyNativePatch(params) {
|
|
|
956
1120
|
patchDir = extractPatchDirectory(`${createResult.stdout}
|
|
957
1121
|
${createResult.stderr}`);
|
|
958
1122
|
} catch (err) {
|
|
1123
|
+
validationPhases.push({
|
|
1124
|
+
phase: "apply",
|
|
1125
|
+
passed: false,
|
|
1126
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1127
|
+
});
|
|
959
1128
|
return {
|
|
960
1129
|
success: false,
|
|
961
1130
|
error: `Failed to create native patch workspace for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
|
|
962
1131
|
};
|
|
963
1132
|
}
|
|
964
1133
|
if (!patchDir) {
|
|
1134
|
+
validationPhases.push({
|
|
1135
|
+
phase: "apply",
|
|
1136
|
+
passed: false,
|
|
1137
|
+
error: `Could not determine native patch directory for ${packageSpec}.`
|
|
1138
|
+
});
|
|
965
1139
|
return {
|
|
966
1140
|
success: false,
|
|
967
1141
|
error: `Could not determine native patch directory for ${packageSpec}.`
|
|
@@ -982,6 +1156,11 @@ ${createResult.stderr}`);
|
|
|
982
1156
|
stdio: "pipe"
|
|
983
1157
|
});
|
|
984
1158
|
} catch (err) {
|
|
1159
|
+
validationPhases.push({
|
|
1160
|
+
phase: "apply",
|
|
1161
|
+
passed: false,
|
|
1162
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1163
|
+
});
|
|
985
1164
|
return {
|
|
986
1165
|
success: false,
|
|
987
1166
|
error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -991,6 +1170,19 @@ ${createResult.stderr}`);
|
|
|
991
1170
|
}
|
|
992
1171
|
return { success: true };
|
|
993
1172
|
}
|
|
1173
|
+
async function writePatchManifest(manifestFilePath, artifact) {
|
|
1174
|
+
await writeFile(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
|
|
1175
|
+
}
|
|
1176
|
+
function extractPatchedFiles(patchContent) {
|
|
1177
|
+
return Array.from(
|
|
1178
|
+
new Set(
|
|
1179
|
+
patchContent.split(/\r?\n/).filter((line) => line.startsWith("+++ b/")).map((line) => line.slice("+++ b/".length))
|
|
1180
|
+
)
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
function countPatchHunks(patchContent) {
|
|
1184
|
+
return patchContent.split(/\r?\n/).filter((line) => line.startsWith("@@ ")).length;
|
|
1185
|
+
}
|
|
994
1186
|
function extractPatchDirectory(output) {
|
|
995
1187
|
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
996
1188
|
for (const line of lines) {
|
|
@@ -1640,7 +1832,15 @@ var generatePatchTool = tool6({
|
|
|
1640
1832
|
"Map of file paths to source code contents from fetch-package-source"
|
|
1641
1833
|
),
|
|
1642
1834
|
vulnerabilityCategory: z6.enum(["redos", "code-injection", "path-traversal", "unknown"]).optional().default("unknown").describe("Category of the vulnerability for better context"),
|
|
1643
|
-
dryRun: z6.boolean().optional().default(false).describe("If true, return analysis without generating patches")
|
|
1835
|
+
dryRun: z6.boolean().optional().default(false).describe("If true, return analysis without generating patches"),
|
|
1836
|
+
llmProvider: z6.enum(["remote", "local"]).optional().describe("Optional provider override for patch generation"),
|
|
1837
|
+
model: z6.string().optional().describe("Optional model override for patch generation"),
|
|
1838
|
+
policy: z6.string().optional().describe("Optional policy file path for model default resolution"),
|
|
1839
|
+
cwd: z6.string().optional().describe("Optional working directory for policy/model resolution"),
|
|
1840
|
+
providerSafetyProfile: z6.enum(["strict", "relaxed"]).optional().describe("Confidence threshold profile for patch acceptance"),
|
|
1841
|
+
dynamicModelRouting: z6.boolean().optional().describe("Enable dynamic model routing by input size"),
|
|
1842
|
+
dynamicRoutingThresholdChars: z6.number().int().positive().optional().describe("Threshold for dynamic model routing"),
|
|
1843
|
+
modelPersonality: z6.enum(["analytical", "pragmatic", "balanced"]).optional().describe("Prompt personality for patch-generation guidance")
|
|
1644
1844
|
}),
|
|
1645
1845
|
execute: async ({
|
|
1646
1846
|
packageName,
|
|
@@ -1649,27 +1849,49 @@ var generatePatchTool = tool6({
|
|
|
1649
1849
|
cveSummary,
|
|
1650
1850
|
sourceFiles,
|
|
1651
1851
|
vulnerabilityCategory,
|
|
1652
|
-
dryRun
|
|
1852
|
+
dryRun,
|
|
1853
|
+
llmProvider,
|
|
1854
|
+
model,
|
|
1855
|
+
policy,
|
|
1856
|
+
cwd,
|
|
1857
|
+
providerSafetyProfile,
|
|
1858
|
+
dynamicModelRouting,
|
|
1859
|
+
dynamicRoutingThresholdChars,
|
|
1860
|
+
modelPersonality
|
|
1653
1861
|
}) => {
|
|
1654
1862
|
try {
|
|
1655
1863
|
const resolvedSourceFiles = sourceFiles;
|
|
1864
|
+
const effectiveOptions = {
|
|
1865
|
+
llmProvider,
|
|
1866
|
+
model,
|
|
1867
|
+
policy,
|
|
1868
|
+
cwd,
|
|
1869
|
+
providerSafetyProfile,
|
|
1870
|
+
dynamicModelRouting,
|
|
1871
|
+
dynamicRoutingThresholdChars,
|
|
1872
|
+
modelPersonality
|
|
1873
|
+
};
|
|
1874
|
+
const provider = resolveProvider(effectiveOptions);
|
|
1656
1875
|
if (Object.keys(resolvedSourceFiles).length === 0) {
|
|
1657
1876
|
return {
|
|
1658
1877
|
success: false,
|
|
1878
|
+
llmProvider: provider,
|
|
1659
1879
|
llmModel: "unknown",
|
|
1660
1880
|
confidence: 0,
|
|
1661
1881
|
riskLevel: "high",
|
|
1662
1882
|
error: "No source files were provided. Call fetch-package-source first and pass sourceFiles."
|
|
1663
1883
|
};
|
|
1664
1884
|
}
|
|
1665
|
-
const
|
|
1666
|
-
const
|
|
1885
|
+
const inputChars = JSON.stringify(resolvedSourceFiles).length + cveSummary.length;
|
|
1886
|
+
const modelInstance = await createModel(effectiveOptions, { inputChars });
|
|
1887
|
+
const modelName = modelInstance.modelId || "unknown-model";
|
|
1667
1888
|
const sourceContext = Object.entries(resolvedSourceFiles).map(([filePath, content]) => `
|
|
1668
1889
|
### File: ${filePath}
|
|
1669
1890
|
\`\`\`typescript
|
|
1670
1891
|
${content}
|
|
1671
1892
|
\`\`\``).join("\n");
|
|
1672
1893
|
const vulnerabilityContext = VULNERABILITY_DESCRIPTIONS[vulnerabilityCategory] || VULNERABILITY_DESCRIPTIONS.unknown;
|
|
1894
|
+
const personalityDirective = modelPersonality === "analytical" ? "Provide concise analysis with explicit risk tradeoffs." : modelPersonality === "pragmatic" ? "Prioritize minimal, safe changes with low operational risk." : "Balance analytical explanation with practical remediation.";
|
|
1673
1895
|
const prompt = `You are a security expert tasked with analyzing a CVE vulnerability and generating a secure patch.
|
|
1674
1896
|
|
|
1675
1897
|
## CVE Information
|
|
@@ -1683,6 +1905,9 @@ ${cveSummary}
|
|
|
1683
1905
|
## Vulnerability Type Context
|
|
1684
1906
|
${vulnerabilityContext}
|
|
1685
1907
|
|
|
1908
|
+
## Model Behavior
|
|
1909
|
+
${personalityDirective}
|
|
1910
|
+
|
|
1686
1911
|
## Vulnerable Source Code
|
|
1687
1912
|
${sourceContext}
|
|
1688
1913
|
|
|
@@ -1710,12 +1935,14 @@ Important:
|
|
|
1710
1935
|
- riskLevel: "low", "medium", or "high" - assess the risk of the proposed fix breaking functionality
|
|
1711
1936
|
- fixedCode: must contain the COMPLETE file contents (not just diffs), with the vulnerability addressed
|
|
1712
1937
|
- Only include files that need modification`;
|
|
1938
|
+
const started = Date.now();
|
|
1713
1939
|
const { text } = await generateText({
|
|
1714
|
-
model,
|
|
1940
|
+
model: modelInstance,
|
|
1715
1941
|
prompt,
|
|
1716
1942
|
temperature: 0.3
|
|
1717
1943
|
// Lower temperature for more consistent code generation
|
|
1718
1944
|
});
|
|
1945
|
+
const latencyMs = Date.now() - started;
|
|
1719
1946
|
let analysis;
|
|
1720
1947
|
try {
|
|
1721
1948
|
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
@@ -1726,25 +1953,42 @@ Important:
|
|
|
1726
1953
|
} catch (err) {
|
|
1727
1954
|
return {
|
|
1728
1955
|
success: false,
|
|
1956
|
+
llmProvider: provider,
|
|
1729
1957
|
llmModel: modelName,
|
|
1730
1958
|
confidence: 0,
|
|
1731
1959
|
riskLevel: "high",
|
|
1960
|
+
latencyMs,
|
|
1732
1961
|
error: `Failed to parse LLM response: ${err instanceof Error ? err.message : "unknown error"}`
|
|
1733
1962
|
};
|
|
1734
1963
|
}
|
|
1735
1964
|
if (!analysis.analysis || !analysis.fixedCode || typeof analysis.confidence !== "number" || !["low", "medium", "high"].includes(analysis.riskLevel)) {
|
|
1736
1965
|
return {
|
|
1737
1966
|
success: false,
|
|
1967
|
+
llmProvider: provider,
|
|
1738
1968
|
llmModel: modelName,
|
|
1739
1969
|
confidence: 0,
|
|
1740
1970
|
riskLevel: "high",
|
|
1971
|
+
latencyMs,
|
|
1741
1972
|
error: "LLM response missing required fields (analysis, fixedCode, confidence, riskLevel)"
|
|
1742
1973
|
};
|
|
1743
1974
|
}
|
|
1975
|
+
const confidenceThreshold = getPatchConfidenceThreshold(
|
|
1976
|
+
provider,
|
|
1977
|
+
providerSafetyProfile ?? "relaxed"
|
|
1978
|
+
);
|
|
1979
|
+
const estimatedCostUsd = estimateModelCostUsd({
|
|
1980
|
+
provider,
|
|
1981
|
+
promptChars: prompt.length,
|
|
1982
|
+
completionChars: text.length
|
|
1983
|
+
});
|
|
1744
1984
|
if (dryRun) {
|
|
1745
1985
|
return {
|
|
1746
1986
|
success: true,
|
|
1987
|
+
llmProvider: provider,
|
|
1747
1988
|
llmModel: modelName,
|
|
1989
|
+
latencyMs,
|
|
1990
|
+
estimatedCostUsd,
|
|
1991
|
+
confidenceThreshold,
|
|
1748
1992
|
confidence: analysis.confidence,
|
|
1749
1993
|
riskLevel: analysis.riskLevel
|
|
1750
1994
|
};
|
|
@@ -1772,7 +2016,11 @@ Important:
|
|
|
1772
2016
|
if (patches.length === 0) {
|
|
1773
2017
|
return {
|
|
1774
2018
|
success: false,
|
|
2019
|
+
llmProvider: provider,
|
|
1775
2020
|
llmModel: modelName,
|
|
2021
|
+
latencyMs,
|
|
2022
|
+
estimatedCostUsd,
|
|
2023
|
+
confidenceThreshold,
|
|
1776
2024
|
confidence: analysis.confidence,
|
|
1777
2025
|
riskLevel: analysis.riskLevel,
|
|
1778
2026
|
error: "No valid patches could be generated from LLM response"
|
|
@@ -1782,7 +2030,11 @@ Important:
|
|
|
1782
2030
|
success: true,
|
|
1783
2031
|
patches,
|
|
1784
2032
|
patchContent: patches[0]?.unifiedDiff,
|
|
2033
|
+
llmProvider: provider,
|
|
1785
2034
|
llmModel: modelName,
|
|
2035
|
+
latencyMs,
|
|
2036
|
+
estimatedCostUsd,
|
|
2037
|
+
confidenceThreshold,
|
|
1786
2038
|
confidence: analysis.confidence,
|
|
1787
2039
|
riskLevel: analysis.riskLevel
|
|
1788
2040
|
};
|
|
@@ -1790,6 +2042,7 @@ Important:
|
|
|
1790
2042
|
const message = err instanceof Error ? err.message : String(err);
|
|
1791
2043
|
return {
|
|
1792
2044
|
success: false,
|
|
2045
|
+
llmProvider: "local",
|
|
1793
2046
|
llmModel: "unknown",
|
|
1794
2047
|
confidence: 0,
|
|
1795
2048
|
riskLevel: "high",
|
|
@@ -1827,12 +2080,17 @@ function generateUnifiedDiff(original, fixed, filePath) {
|
|
|
1827
2080
|
}
|
|
1828
2081
|
|
|
1829
2082
|
// src/remediation/local/fallback.ts
|
|
2083
|
+
function resolvePatchProvider(provider) {
|
|
2084
|
+
void provider;
|
|
2085
|
+
return "remote";
|
|
2086
|
+
}
|
|
1830
2087
|
function shouldAttemptPatchFallback(result, preferVersionBump) {
|
|
1831
2088
|
if (preferVersionBump) return false;
|
|
1832
2089
|
if (result.applied || result.dryRun) return false;
|
|
1833
2090
|
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 === "indirect-dependency";
|
|
1834
2091
|
}
|
|
1835
2092
|
async function tryLocalPatchFallback(params) {
|
|
2093
|
+
const usage = [];
|
|
1836
2094
|
let steps = 0;
|
|
1837
2095
|
const sourceResult = await fetchPackageSourceTool.execute({
|
|
1838
2096
|
packageName: params.packageName,
|
|
@@ -1842,17 +2100,20 @@ async function tryLocalPatchFallback(params) {
|
|
|
1842
2100
|
if (!sourceResult?.success || !sourceResult.sourceFiles) {
|
|
1843
2101
|
return {
|
|
1844
2102
|
steps,
|
|
2103
|
+
usage,
|
|
1845
2104
|
result: {
|
|
1846
2105
|
packageName: params.packageName,
|
|
1847
2106
|
strategy: "none",
|
|
1848
2107
|
fromVersion: params.vulnerableVersion,
|
|
1849
2108
|
applied: false,
|
|
1850
2109
|
dryRun: params.dryRun,
|
|
2110
|
+
dependencyScope: params.dependencyScope,
|
|
1851
2111
|
unresolvedReason: "source-fetch-failed",
|
|
1852
2112
|
message: sourceResult?.error ?? `Failed to fetch source for ${params.packageName}@${params.vulnerableVersion}.`
|
|
1853
2113
|
}
|
|
1854
2114
|
};
|
|
1855
2115
|
}
|
|
2116
|
+
const primaryProvider = resolvePatchProvider(params.llmProvider);
|
|
1856
2117
|
const patchResult = await generatePatchTool.execute({
|
|
1857
2118
|
packageName: params.packageName,
|
|
1858
2119
|
vulnerableVersion: params.vulnerableVersion,
|
|
@@ -1860,7 +2121,15 @@ async function tryLocalPatchFallback(params) {
|
|
|
1860
2121
|
cveSummary: params.cveSummary,
|
|
1861
2122
|
sourceFiles: sourceResult.sourceFiles,
|
|
1862
2123
|
vulnerabilityCategory: "unknown",
|
|
1863
|
-
dryRun: params.dryRun
|
|
2124
|
+
dryRun: params.dryRun,
|
|
2125
|
+
llmProvider: primaryProvider,
|
|
2126
|
+
model: params.model,
|
|
2127
|
+
policy: params.policy,
|
|
2128
|
+
cwd: params.cwd,
|
|
2129
|
+
modelPersonality: params.modelPersonality,
|
|
2130
|
+
providerSafetyProfile: params.providerSafetyProfile,
|
|
2131
|
+
dynamicModelRouting: params.dynamicModelRouting,
|
|
2132
|
+
dynamicRoutingThresholdChars: params.dynamicRoutingThresholdChars
|
|
1864
2133
|
});
|
|
1865
2134
|
steps += 1;
|
|
1866
2135
|
if (!patchResult?.success) {
|
|
@@ -1868,58 +2137,121 @@ async function tryLocalPatchFallback(params) {
|
|
|
1868
2137
|
const unresolvedReason = error.includes("API_KEY") || error.includes("does not create a language model") ? "requires-llm-fallback" : "patch-generation-failed";
|
|
1869
2138
|
return {
|
|
1870
2139
|
steps,
|
|
2140
|
+
usage,
|
|
1871
2141
|
result: {
|
|
1872
2142
|
packageName: params.packageName,
|
|
1873
2143
|
strategy: "none",
|
|
1874
2144
|
fromVersion: params.vulnerableVersion,
|
|
1875
2145
|
applied: false,
|
|
1876
2146
|
dryRun: params.dryRun,
|
|
2147
|
+
dependencyScope: params.dependencyScope,
|
|
1877
2148
|
unresolvedReason,
|
|
1878
2149
|
message: error
|
|
1879
2150
|
}
|
|
1880
2151
|
};
|
|
1881
2152
|
}
|
|
1882
|
-
|
|
2153
|
+
const effectiveProvider = patchResult.llmProvider ?? primaryProvider;
|
|
2154
|
+
const confidenceThreshold = patchResult.confidenceThreshold ?? getPatchConfidenceThreshold(effectiveProvider, params.providerSafetyProfile ?? "relaxed");
|
|
2155
|
+
if (typeof patchResult.confidence === "number" && patchResult.confidence < confidenceThreshold) {
|
|
1883
2156
|
return {
|
|
1884
2157
|
steps,
|
|
2158
|
+
usage,
|
|
1885
2159
|
result: {
|
|
1886
2160
|
packageName: params.packageName,
|
|
1887
2161
|
strategy: "none",
|
|
1888
2162
|
fromVersion: params.vulnerableVersion,
|
|
1889
2163
|
applied: false,
|
|
1890
2164
|
dryRun: params.dryRun,
|
|
2165
|
+
dependencyScope: params.dependencyScope,
|
|
2166
|
+
confidence: patchResult.confidence,
|
|
2167
|
+
riskLevel: patchResult.riskLevel,
|
|
1891
2168
|
unresolvedReason: "patch-confidence-too-low",
|
|
1892
|
-
message: `Patch confidence ${patchResult.confidence.toFixed(2)} is below threshold
|
|
2169
|
+
message: `Patch confidence ${patchResult.confidence.toFixed(2)} is below threshold ${confidenceThreshold.toFixed(2)}.`
|
|
1893
2170
|
}
|
|
1894
2171
|
};
|
|
1895
2172
|
}
|
|
2173
|
+
if (params.requireConsensusForHighRisk && patchResult.riskLevel === "high" && !params.dryRun) {
|
|
2174
|
+
const consensus = await generatePatchTool.execute({
|
|
2175
|
+
packageName: params.packageName,
|
|
2176
|
+
vulnerableVersion: params.vulnerableVersion,
|
|
2177
|
+
cveId: params.cveId,
|
|
2178
|
+
cveSummary: params.cveSummary,
|
|
2179
|
+
sourceFiles: sourceResult.sourceFiles,
|
|
2180
|
+
vulnerabilityCategory: "unknown",
|
|
2181
|
+
dryRun: false,
|
|
2182
|
+
llmProvider: "remote",
|
|
2183
|
+
policy: params.policy,
|
|
2184
|
+
cwd: params.cwd,
|
|
2185
|
+
modelPersonality: params.modelPersonality,
|
|
2186
|
+
providerSafetyProfile: params.providerSafetyProfile,
|
|
2187
|
+
dynamicModelRouting: params.dynamicModelRouting,
|
|
2188
|
+
dynamicRoutingThresholdChars: params.dynamicRoutingThresholdChars
|
|
2189
|
+
});
|
|
2190
|
+
steps += 1;
|
|
2191
|
+
const primaryDiff = patchResult.patches?.[0]?.unifiedDiff;
|
|
2192
|
+
const secondaryDiff = consensus.patches?.[0]?.unifiedDiff;
|
|
2193
|
+
if (!consensus.success || !primaryDiff || !secondaryDiff || primaryDiff !== secondaryDiff) {
|
|
2194
|
+
return {
|
|
2195
|
+
steps,
|
|
2196
|
+
usage,
|
|
2197
|
+
result: {
|
|
2198
|
+
packageName: params.packageName,
|
|
2199
|
+
strategy: "none",
|
|
2200
|
+
fromVersion: params.vulnerableVersion,
|
|
2201
|
+
applied: false,
|
|
2202
|
+
dryRun: params.dryRun,
|
|
2203
|
+
dependencyScope: params.dependencyScope,
|
|
2204
|
+
unresolvedReason: "consensus-failed",
|
|
2205
|
+
message: consensus.error ?? "High-risk patch did not pass consensus verification."
|
|
2206
|
+
}
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
1896
2210
|
const applyResult = await applyPatchFileTool.execute({
|
|
1897
2211
|
packageName: params.packageName,
|
|
1898
2212
|
vulnerableVersion: params.vulnerableVersion,
|
|
2213
|
+
cveId: params.cveId,
|
|
2214
|
+
confidence: patchResult.confidence,
|
|
1899
2215
|
patchContent: patchResult.patchContent,
|
|
1900
2216
|
patches: patchResult.patches,
|
|
1901
2217
|
patchesDir: params.patchesDir,
|
|
1902
2218
|
cwd: params.cwd,
|
|
1903
2219
|
packageManager: params.packageManager,
|
|
2220
|
+
riskLevel: patchResult.riskLevel,
|
|
1904
2221
|
validateWithTests: params.runTests,
|
|
1905
2222
|
dryRun: params.dryRun
|
|
1906
2223
|
});
|
|
1907
2224
|
steps += 1;
|
|
2225
|
+
if (patchResult.llmProvider && patchResult.llmModel) {
|
|
2226
|
+
usage.push({
|
|
2227
|
+
purpose: "patch-generation",
|
|
2228
|
+
provider: patchResult.llmProvider,
|
|
2229
|
+
model: patchResult.llmModel,
|
|
2230
|
+
latencyMs: patchResult.latencyMs,
|
|
2231
|
+
estimatedCostUsd: patchResult.estimatedCostUsd
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
1908
2234
|
return {
|
|
1909
2235
|
steps,
|
|
2236
|
+
usage,
|
|
1910
2237
|
result: {
|
|
1911
2238
|
packageName: params.packageName,
|
|
1912
2239
|
strategy: "patch-file",
|
|
1913
2240
|
fromVersion: params.vulnerableVersion,
|
|
1914
2241
|
patchFilePath: applyResult.patchFilePath ?? applyResult.patchPath,
|
|
2242
|
+
patchArtifact: applyResult.patchArtifact,
|
|
1915
2243
|
applied: Boolean(applyResult.applied),
|
|
1916
2244
|
dryRun: Boolean(applyResult.dryRun),
|
|
2245
|
+
dependencyScope: params.dependencyScope,
|
|
2246
|
+
confidence: patchResult.confidence,
|
|
2247
|
+
riskLevel: patchResult.riskLevel,
|
|
1917
2248
|
unresolvedReason: !Boolean(applyResult.applied) && !Boolean(applyResult.dryRun) ? applyResult.validation?.passed === false ? "patch-validation-failed" : "patch-apply-failed" : void 0,
|
|
1918
2249
|
message: applyResult.message ?? applyResult.error ?? "Patch-file strategy finished.",
|
|
1919
2250
|
validation: typeof applyResult.validation?.passed === "boolean" ? {
|
|
1920
2251
|
passed: applyResult.validation.passed,
|
|
1921
2252
|
error: applyResult.validation.error
|
|
1922
|
-
} : void 0
|
|
2253
|
+
} : void 0,
|
|
2254
|
+
validationPhases: applyResult.validationPhases
|
|
1923
2255
|
}
|
|
1924
2256
|
};
|
|
1925
2257
|
}
|
|
@@ -1934,7 +2266,14 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
1934
2266
|
const policy = options.policy ?? "";
|
|
1935
2267
|
const patchesDir = options.patchesDir || "./patches";
|
|
1936
2268
|
const constraints = options.constraints ?? {};
|
|
2269
|
+
const loadedPolicy = loadPolicy(cwd, options.policy);
|
|
2270
|
+
const llmProvider = resolveProvider(options);
|
|
2271
|
+
const providerSafetyProfile = options.providerSafetyProfile ?? loadedPolicy.providerSafetyProfile ?? "relaxed";
|
|
2272
|
+
const requireConsensusForHighRisk = options.requireConsensusForHighRisk ?? loadedPolicy.requireConsensusForHighRisk ?? false;
|
|
2273
|
+
const dynamicModelRouting = options.dynamicModelRouting ?? loadedPolicy.dynamicModelRouting ?? false;
|
|
2274
|
+
const dynamicRoutingThresholdChars = options.dynamicRoutingThresholdChars ?? loadedPolicy.dynamicRoutingThresholdChars;
|
|
1937
2275
|
const collectedResults = [];
|
|
2276
|
+
const llmUsage = [];
|
|
1938
2277
|
const vulnerablePackages = [];
|
|
1939
2278
|
let cveDetails = null;
|
|
1940
2279
|
let agentSteps = 0;
|
|
@@ -2043,15 +2382,30 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
2043
2382
|
vulnerableVersion: vulnerable.installed.version,
|
|
2044
2383
|
cveId: normalizedId,
|
|
2045
2384
|
cveSummary: cveDetails?.summary ?? normalizedId,
|
|
2385
|
+
dependencyScope: vulnerable.installed.type === "direct" ? "direct" : "transitive",
|
|
2046
2386
|
dryRun,
|
|
2047
2387
|
runTests,
|
|
2048
|
-
patchesDir
|
|
2388
|
+
patchesDir,
|
|
2389
|
+
llmProvider,
|
|
2390
|
+
model: options.model,
|
|
2391
|
+
policy: options.policy,
|
|
2392
|
+
modelPersonality: options.modelPersonality,
|
|
2393
|
+
providerSafetyProfile,
|
|
2394
|
+
requireConsensusForHighRisk,
|
|
2395
|
+
dynamicModelRouting,
|
|
2396
|
+
dynamicRoutingThresholdChars
|
|
2049
2397
|
});
|
|
2050
2398
|
agentSteps += fallback.steps;
|
|
2051
2399
|
collectedResults.push(fallback.result);
|
|
2400
|
+
if (fallback.usage) {
|
|
2401
|
+
llmUsage.push(...fallback.usage);
|
|
2402
|
+
}
|
|
2052
2403
|
continue;
|
|
2053
2404
|
}
|
|
2054
|
-
collectedResults.push(
|
|
2405
|
+
collectedResults.push({
|
|
2406
|
+
...primary.result,
|
|
2407
|
+
dependencyScope: vulnerable.installed.type === "direct" ? "direct" : "transitive"
|
|
2408
|
+
});
|
|
2055
2409
|
}
|
|
2056
2410
|
const appliedCount = collectedResults.filter((result) => result.applied).length;
|
|
2057
2411
|
const unresolvedCount = collectedResults.filter((result) => !result.applied && !result.dryRun).length;
|
|
@@ -2063,6 +2417,7 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
2063
2417
|
results: collectedResults,
|
|
2064
2418
|
agentSteps,
|
|
2065
2419
|
summary: `Local mode completed: vulnerable=${vulnerablePackages.length}, applied=${appliedCount}, dryRun=${dryRunCount}, unresolved=${unresolvedCount}`,
|
|
2420
|
+
llmUsage: llmUsage.length > 0 ? llmUsage : void 0,
|
|
2066
2421
|
correlation: {
|
|
2067
2422
|
requestId: options.requestId,
|
|
2068
2423
|
sessionId: options.sessionId,
|
|
@@ -2071,6 +2426,50 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
|
|
|
2071
2426
|
};
|
|
2072
2427
|
}
|
|
2073
2428
|
|
|
2429
|
+
// src/remediation/orchestration-prompt.ts
|
|
2430
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
2431
|
+
import { join as join10 } from "path";
|
|
2432
|
+
function buildProviderAddendum(provider, personality = "balanced") {
|
|
2433
|
+
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.";
|
|
2434
|
+
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.";
|
|
2435
|
+
return `
|
|
2436
|
+
Provider profile:
|
|
2437
|
+
- Provider: ${provider}
|
|
2438
|
+
- ${providerDirective}
|
|
2439
|
+
- ${personalityDirective}`;
|
|
2440
|
+
}
|
|
2441
|
+
function loadOrchestrationPrompt(ctx) {
|
|
2442
|
+
const promptPath = join10(process.cwd(), ".github", "instructions", "orchestration.instructions.md");
|
|
2443
|
+
if (!existsSync5(promptPath)) {
|
|
2444
|
+
return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
|
|
2445
|
+
Working directory: ${ctx.cwd}
|
|
2446
|
+
Package manager: ${ctx.packageManager}
|
|
2447
|
+
Dry run: ${ctx.dryRun}
|
|
2448
|
+
Run tests: ${ctx.runTests}
|
|
2449
|
+
Policy: ${ctx.policy || "undefined"}
|
|
2450
|
+
Patches dir: ${ctx.patchesDir}
|
|
2451
|
+
Direct dependencies only: ${String(ctx.constraints.directDependenciesOnly ?? false)}
|
|
2452
|
+
Prefer version bump: ${String(ctx.constraints.preferVersionBump ?? false)}
|
|
2453
|
+
|
|
2454
|
+
Required sequence:
|
|
2455
|
+
1. lookup-cve
|
|
2456
|
+
2. check-inventory
|
|
2457
|
+
3. check-version-match
|
|
2458
|
+
4. find-fixed-version
|
|
2459
|
+
5. apply-version-bump
|
|
2460
|
+
6. apply-package-override
|
|
2461
|
+
|
|
2462
|
+
Fallback sequence (when neither version bump nor override can be applied):
|
|
2463
|
+
1. fetch-package-source
|
|
2464
|
+
2. generate-patch
|
|
2465
|
+
3. apply-patch-file
|
|
2466
|
+
|
|
2467
|
+
Always respect dryRun and policy constraints.`;
|
|
2468
|
+
}
|
|
2469
|
+
const template = readFileSync6(promptPath, "utf8");
|
|
2470
|
+
return template.replaceAll("{{cveId}}", ctx.cveId).replaceAll("{{cwd}}", ctx.cwd).replaceAll("{{packageManager}}", ctx.packageManager).replaceAll("{{dryRun}}", String(ctx.dryRun)).replaceAll("{{runTests}}", String(ctx.runTests)).replaceAll("{{policy}}", ctx.policy || "undefined").replaceAll("{{patchesDir}}", ctx.patchesDir).replaceAll("{{directDependenciesOnly}}", String(ctx.constraints.directDependenciesOnly ?? false)).replaceAll("{{preferVersionBump}}", String(ctx.constraints.preferVersionBump ?? false)) + buildProviderAddendum(ctx.llmProvider, ctx.modelPersonality);
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2074
2473
|
// src/remediation/tools/lookup-cve.ts
|
|
2075
2474
|
import { tool as tool7 } from "ai";
|
|
2076
2475
|
import { z as z7 } from "zod";
|
|
@@ -2544,47 +2943,23 @@ function buildRuntimeTools(ctx) {
|
|
|
2544
2943
|
return tools;
|
|
2545
2944
|
}
|
|
2546
2945
|
|
|
2547
|
-
// src/remediation/orchestration-prompt.ts
|
|
2548
|
-
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
2549
|
-
import { join as join10 } from "path";
|
|
2550
|
-
function loadOrchestrationPrompt(ctx) {
|
|
2551
|
-
const promptPath = join10(process.cwd(), ".github", "instructions", "orchestration.instructions.md");
|
|
2552
|
-
if (!existsSync5(promptPath)) {
|
|
2553
|
-
return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
|
|
2554
|
-
Working directory: ${ctx.cwd}
|
|
2555
|
-
Package manager: ${ctx.packageManager}
|
|
2556
|
-
Dry run: ${ctx.dryRun}
|
|
2557
|
-
Run tests: ${ctx.runTests}
|
|
2558
|
-
Policy: ${ctx.policy || "undefined"}
|
|
2559
|
-
Patches dir: ${ctx.patchesDir}
|
|
2560
|
-
Direct dependencies only: ${String(ctx.constraints.directDependenciesOnly ?? false)}
|
|
2561
|
-
Prefer version bump: ${String(ctx.constraints.preferVersionBump ?? false)}
|
|
2562
|
-
|
|
2563
|
-
Required sequence:
|
|
2564
|
-
1. lookup-cve
|
|
2565
|
-
2. check-inventory
|
|
2566
|
-
3. check-version-match
|
|
2567
|
-
4. find-fixed-version
|
|
2568
|
-
5. apply-version-bump
|
|
2569
|
-
6. apply-package-override
|
|
2570
|
-
|
|
2571
|
-
Fallback sequence (when neither version bump nor override can be applied):
|
|
2572
|
-
1. fetch-package-source
|
|
2573
|
-
2. generate-patch
|
|
2574
|
-
3. apply-patch-file
|
|
2575
|
-
|
|
2576
|
-
Always respect dryRun and policy constraints.`;
|
|
2577
|
-
}
|
|
2578
|
-
const template = readFileSync6(promptPath, "utf8");
|
|
2579
|
-
return template.replaceAll("{{cveId}}", ctx.cveId).replaceAll("{{cwd}}", ctx.cwd).replaceAll("{{packageManager}}", ctx.packageManager).replaceAll("{{dryRun}}", String(ctx.dryRun)).replaceAll("{{runTests}}", String(ctx.runTests)).replaceAll("{{policy}}", ctx.policy || "undefined").replaceAll("{{patchesDir}}", ctx.patchesDir).replaceAll("{{directDependenciesOnly}}", String(ctx.constraints.directDependenciesOnly ?? false)).replaceAll("{{preferVersionBump}}", String(ctx.constraints.preferVersionBump ?? false));
|
|
2580
|
-
}
|
|
2581
|
-
|
|
2582
2946
|
// src/remediation/pipeline.ts
|
|
2583
2947
|
async function runRemediationPipeline(cveId, options = {}) {
|
|
2584
2948
|
const provider = resolveProvider(options);
|
|
2585
2949
|
if (provider === "local") {
|
|
2586
2950
|
return runLocalRemediationPipeline(cveId, options);
|
|
2587
2951
|
}
|
|
2952
|
+
const emitProgress = (stage, detail, extra) => {
|
|
2953
|
+
if (!options.onProgress) return;
|
|
2954
|
+
options.onProgress({
|
|
2955
|
+
stage,
|
|
2956
|
+
detail,
|
|
2957
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2958
|
+
provider: extra?.provider,
|
|
2959
|
+
model: extra?.model
|
|
2960
|
+
});
|
|
2961
|
+
};
|
|
2962
|
+
emitProgress("pipeline-start", `Starting remediation for ${cveId}.`, { provider });
|
|
2588
2963
|
const cwd = options.cwd ?? process.cwd();
|
|
2589
2964
|
const packageManager = options.packageManager ?? detectPackageManager(cwd);
|
|
2590
2965
|
const preview = options.preview ?? false;
|
|
@@ -2593,10 +2968,15 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
2593
2968
|
const policy = options.policy ?? "";
|
|
2594
2969
|
const patchesDir = options.patchesDir || "./patches";
|
|
2595
2970
|
const constraints = options.constraints ?? {};
|
|
2596
|
-
const
|
|
2971
|
+
const prompt = `Patch vulnerable dependencies affected by ${cveId} in the project at: ${cwd}. Package manager: ${packageManager}.`;
|
|
2972
|
+
const model = await createModel(options, { inputChars: prompt.length });
|
|
2973
|
+
const modelName = model.modelId ?? "unknown-model";
|
|
2974
|
+
emitProgress("model-selected", `Selected model ${modelName}.`, { provider, model: modelName });
|
|
2597
2975
|
const systemPrompt = loadOrchestrationPrompt({
|
|
2598
2976
|
cveId,
|
|
2599
2977
|
cwd,
|
|
2978
|
+
llmProvider: provider,
|
|
2979
|
+
modelPersonality: options.modelPersonality,
|
|
2600
2980
|
dryRun,
|
|
2601
2981
|
runTests,
|
|
2602
2982
|
policy,
|
|
@@ -2604,11 +2984,15 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
2604
2984
|
packageManager,
|
|
2605
2985
|
constraints
|
|
2606
2986
|
});
|
|
2607
|
-
const prompt = `Patch vulnerable dependencies affected by ${cveId} in the project at: ${cwd}. Package manager: ${packageManager}.`;
|
|
2608
2987
|
const collectedResults = [];
|
|
2609
2988
|
const vulnerablePackages = [];
|
|
2610
2989
|
let cveDetails = null;
|
|
2611
2990
|
let agentSteps = 0;
|
|
2991
|
+
function getDependencyScope(packageName) {
|
|
2992
|
+
const match = vulnerablePackages.find((pkg) => pkg.installed.name === packageName);
|
|
2993
|
+
if (!match) return void 0;
|
|
2994
|
+
return match.installed.type === "direct" ? "direct" : "transitive";
|
|
2995
|
+
}
|
|
2612
2996
|
const applyVersionBumpToolForRun = preview ? {
|
|
2613
2997
|
...applyVersionBumpTool,
|
|
2614
2998
|
execute: async (input) => applyVersionBumpTool.execute({ ...input, dryRun: true })
|
|
@@ -2627,6 +3011,7 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
2627
3011
|
applyPatchFileToolForRun,
|
|
2628
3012
|
constraints
|
|
2629
3013
|
});
|
|
3014
|
+
const started = Date.now();
|
|
2630
3015
|
const result = await generateText2({
|
|
2631
3016
|
model,
|
|
2632
3017
|
system: systemPrompt,
|
|
@@ -2645,10 +3030,18 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
2645
3030
|
vulnerablePackages.push(...toolResult.vulnerablePackages);
|
|
2646
3031
|
}
|
|
2647
3032
|
if (tr.toolName === "apply-version-bump") {
|
|
2648
|
-
|
|
3033
|
+
const typed = toolResult;
|
|
3034
|
+
collectedResults.push({
|
|
3035
|
+
...typed,
|
|
3036
|
+
dependencyScope: typed.packageName ? getDependencyScope(typed.packageName) : typed.dependencyScope
|
|
3037
|
+
});
|
|
2649
3038
|
}
|
|
2650
3039
|
if (tr.toolName === "apply-package-override") {
|
|
2651
|
-
|
|
3040
|
+
const typed = toolResult;
|
|
3041
|
+
collectedResults.push({
|
|
3042
|
+
...typed,
|
|
3043
|
+
dependencyScope: typed.packageName ? getDependencyScope(typed.packageName) : typed.dependencyScope
|
|
3044
|
+
});
|
|
2652
3045
|
}
|
|
2653
3046
|
if (tr.toolName === "apply-patch-file" && toolResult) {
|
|
2654
3047
|
const validation = toolResult.validation;
|
|
@@ -2658,19 +3051,47 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
2658
3051
|
strategy: "patch-file",
|
|
2659
3052
|
fromVersion: typeof toolResult.vulnerableVersion === "string" ? toolResult.vulnerableVersion : "unknown",
|
|
2660
3053
|
patchFilePath: typeof toolResult.patchFilePath === "string" ? toolResult.patchFilePath : typeof toolResult.patchPath === "string" ? toolResult.patchPath : void 0,
|
|
3054
|
+
patchArtifact: typeof toolResult.patchArtifact === "object" && toolResult.patchArtifact !== null ? toolResult.patchArtifact : void 0,
|
|
2661
3055
|
applied: Boolean(toolResult.applied),
|
|
2662
3056
|
dryRun: Boolean(toolResult.dryRun),
|
|
3057
|
+
dependencyScope: typeof toolResult.packageName === "string" ? getDependencyScope(toolResult.packageName) : void 0,
|
|
3058
|
+
confidence: typeof toolResult.patchArtifact === "object" && toolResult.patchArtifact !== null && typeof toolResult.patchArtifact.confidence === "number" ? toolResult.patchArtifact.confidence : void 0,
|
|
3059
|
+
riskLevel: typeof toolResult.patchArtifact === "object" && toolResult.patchArtifact !== null && typeof toolResult.patchArtifact.riskLevel === "string" ? toolResult.patchArtifact.riskLevel : void 0,
|
|
2663
3060
|
unresolvedReason: !Boolean(toolResult.applied) && !Boolean(toolResult.dryRun) ? validation && validation.passed === false ? "patch-validation-failed" : "patch-apply-failed" : void 0,
|
|
2664
3061
|
message,
|
|
2665
3062
|
validation: validation && typeof validation.passed === "boolean" ? {
|
|
2666
3063
|
passed: validation.passed,
|
|
2667
3064
|
error: typeof validation.error === "string" ? validation.error : void 0
|
|
2668
|
-
} : void 0
|
|
3065
|
+
} : void 0,
|
|
3066
|
+
validationPhases: Array.isArray(toolResult.validationPhases) ? toolResult.validationPhases : void 0
|
|
2669
3067
|
});
|
|
2670
3068
|
}
|
|
2671
3069
|
}
|
|
3070
|
+
emitProgress("agent-step", `Completed agent step ${agentSteps} with ${toolResults.length} tool result(s).`, {
|
|
3071
|
+
provider,
|
|
3072
|
+
model: modelName
|
|
3073
|
+
});
|
|
2672
3074
|
}
|
|
2673
3075
|
});
|
|
3076
|
+
const llmUsage = [
|
|
3077
|
+
{
|
|
3078
|
+
purpose: "orchestration",
|
|
3079
|
+
provider,
|
|
3080
|
+
model: modelName,
|
|
3081
|
+
latencyMs: Date.now() - started,
|
|
3082
|
+
promptChars: prompt.length + systemPrompt.length,
|
|
3083
|
+
completionChars: result.text.length,
|
|
3084
|
+
estimatedCostUsd: estimateModelCostUsd({
|
|
3085
|
+
provider,
|
|
3086
|
+
promptChars: prompt.length + systemPrompt.length,
|
|
3087
|
+
completionChars: result.text.length
|
|
3088
|
+
})
|
|
3089
|
+
}
|
|
3090
|
+
];
|
|
3091
|
+
emitProgress("pipeline-finish", `Completed remediation with ${collectedResults.length} result(s).`, {
|
|
3092
|
+
provider,
|
|
3093
|
+
model: modelName
|
|
3094
|
+
});
|
|
2674
3095
|
return {
|
|
2675
3096
|
cveId,
|
|
2676
3097
|
cveDetails,
|
|
@@ -2678,6 +3099,7 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
2678
3099
|
results: collectedResults,
|
|
2679
3100
|
agentSteps,
|
|
2680
3101
|
summary: result.text,
|
|
3102
|
+
llmUsage,
|
|
2681
3103
|
correlation: {
|
|
2682
3104
|
requestId: options.requestId,
|
|
2683
3105
|
sessionId: options.sessionId,
|
|
@@ -2688,7 +3110,7 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
2688
3110
|
|
|
2689
3111
|
// src/api/options-schema.ts
|
|
2690
3112
|
var PACKAGE_MANAGER_VALUES = ["npm", "pnpm", "yarn"];
|
|
2691
|
-
var LLM_PROVIDER_VALUES = ["
|
|
3113
|
+
var LLM_PROVIDER_VALUES = ["remote", "local"];
|
|
2692
3114
|
var PROVENANCE_SOURCE_VALUES = ["cli", "sdk", "mcp", "openapi", "unknown"];
|
|
2693
3115
|
var OPTION_DESCRIPTIONS = {
|
|
2694
3116
|
cveId: "CVE ID, e.g. CVE-2021-23337",
|
|
@@ -2698,7 +3120,13 @@ var OPTION_DESCRIPTIONS = {
|
|
|
2698
3120
|
dryRun: "If true, plan changes but write nothing",
|
|
2699
3121
|
preview: "If true, enforce non-mutating preview mode",
|
|
2700
3122
|
runTests: "Run package-manager test command after applying fix",
|
|
2701
|
-
llmProvider: "LLM provider override",
|
|
3123
|
+
llmProvider: "LLM provider override (remote|local)",
|
|
3124
|
+
model: "LLM model override",
|
|
3125
|
+
modelPersonality: "Prompt behavior profile: analytical|pragmatic|balanced",
|
|
3126
|
+
providerSafetyProfile: "Safety posture profile for confidence gates: strict|relaxed",
|
|
3127
|
+
requireConsensusForHighRisk: "Require second-provider agreement for high-risk generated patches",
|
|
3128
|
+
dynamicModelRouting: "Enable dynamic model selection by input size",
|
|
3129
|
+
dynamicRoutingThresholdChars: "Input size threshold used by dynamic model routing",
|
|
2702
3130
|
patchesDir: "Directory to write .patch files (default: ./patches)",
|
|
2703
3131
|
policy: "Optional path to .autoremediator policy file",
|
|
2704
3132
|
requestId: "Request correlation ID",
|
|
@@ -2730,6 +3158,12 @@ function createRemediateOptionSchemaProperties(options) {
|
|
|
2730
3158
|
...includePreview ? { preview: { type: "boolean", description: OPTION_DESCRIPTIONS.preview } } : {},
|
|
2731
3159
|
runTests: { type: "boolean", description: OPTION_DESCRIPTIONS.runTests },
|
|
2732
3160
|
llmProvider: { type: "string", enum: [...LLM_PROVIDER_VALUES], description: OPTION_DESCRIPTIONS.llmProvider },
|
|
3161
|
+
model: { type: "string", description: OPTION_DESCRIPTIONS.model },
|
|
3162
|
+
modelPersonality: { type: "string", enum: ["analytical", "pragmatic", "balanced"], description: OPTION_DESCRIPTIONS.modelPersonality },
|
|
3163
|
+
providerSafetyProfile: { type: "string", enum: ["strict", "relaxed"], description: OPTION_DESCRIPTIONS.providerSafetyProfile },
|
|
3164
|
+
requireConsensusForHighRisk: { type: "boolean", description: OPTION_DESCRIPTIONS.requireConsensusForHighRisk },
|
|
3165
|
+
dynamicModelRouting: { type: "boolean", description: OPTION_DESCRIPTIONS.dynamicModelRouting },
|
|
3166
|
+
dynamicRoutingThresholdChars: { type: "number", description: OPTION_DESCRIPTIONS.dynamicRoutingThresholdChars },
|
|
2733
3167
|
patchesDir: { type: "string", description: OPTION_DESCRIPTIONS.patchesDir },
|
|
2734
3168
|
policy: { type: "string", description: OPTION_DESCRIPTIONS.policy },
|
|
2735
3169
|
...includeEvidence ? { evidence: { type: "boolean", description: OPTION_DESCRIPTIONS.evidence } } : {},
|
|
@@ -2781,7 +3215,10 @@ function createScanReportSchemaProperties() {
|
|
|
2781
3215
|
correlation: { type: "object" },
|
|
2782
3216
|
provenance: { type: "object" },
|
|
2783
3217
|
constraints: { type: "object" },
|
|
2784
|
-
idempotencyKey: { type: "string" }
|
|
3218
|
+
idempotencyKey: { type: "string" },
|
|
3219
|
+
llmUsageCount: { type: "number" },
|
|
3220
|
+
estimatedCostUsd: { type: "number" },
|
|
3221
|
+
totalLlmLatencyMs: { type: "number" }
|
|
2785
3222
|
};
|
|
2786
3223
|
}
|
|
2787
3224
|
|
|
@@ -2851,7 +3288,10 @@ function toCiSummary(report) {
|
|
|
2851
3288
|
correlation: report.correlation,
|
|
2852
3289
|
provenance: report.provenance,
|
|
2853
3290
|
constraints: report.constraints,
|
|
2854
|
-
idempotencyKey: report.idempotencyKey
|
|
3291
|
+
idempotencyKey: report.idempotencyKey,
|
|
3292
|
+
llmUsageCount: report.llmUsageCount,
|
|
3293
|
+
estimatedCostUsd: report.estimatedCostUsd,
|
|
3294
|
+
totalLlmLatencyMs: report.totalLlmLatencyMs
|
|
2855
3295
|
};
|
|
2856
3296
|
}
|
|
2857
3297
|
function ciExitCode(summary) {
|
|
@@ -2921,9 +3361,219 @@ function toSarifOutput(report) {
|
|
|
2921
3361
|
};
|
|
2922
3362
|
}
|
|
2923
3363
|
|
|
3364
|
+
// src/api/patches.ts
|
|
3365
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3366
|
+
import { readdir as readdir2, readFile as readFile3, stat } from "fs/promises";
|
|
3367
|
+
import { isAbsolute, join as join11, resolve } from "path";
|
|
3368
|
+
var DEFAULT_PATCHES_DIR = "./patches";
|
|
3369
|
+
async function listPatchArtifacts(options = {}) {
|
|
3370
|
+
const cwd = options.cwd ?? process.cwd();
|
|
3371
|
+
const patchesDirPath = resolvePatchesDir(cwd, options.patchesDir);
|
|
3372
|
+
if (!existsSync6(patchesDirPath)) {
|
|
3373
|
+
return [];
|
|
3374
|
+
}
|
|
3375
|
+
const entries = await readdir2(patchesDirPath, { withFileTypes: true });
|
|
3376
|
+
const patchFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".patch")).map((entry) => join11(patchesDirPath, entry.name)).sort((left, right) => left.localeCompare(right));
|
|
3377
|
+
const summaries = await Promise.all(
|
|
3378
|
+
patchFiles.map(async (patchFilePath) => {
|
|
3379
|
+
const inspection = await inspectPatchArtifact(patchFilePath, options);
|
|
3380
|
+
return toSummary(inspection);
|
|
3381
|
+
})
|
|
3382
|
+
);
|
|
3383
|
+
return summaries;
|
|
3384
|
+
}
|
|
3385
|
+
async function inspectPatchArtifact(patchFilePath, options = {}) {
|
|
3386
|
+
const cwd = options.cwd ?? process.cwd();
|
|
3387
|
+
const resolvedPatchPath = resolveArtifactPath(cwd, patchFilePath);
|
|
3388
|
+
const patchFileName = resolvedPatchPath.split("/").pop() ?? resolvedPatchPath;
|
|
3389
|
+
const manifestFilePath = `${resolvedPatchPath}.json`;
|
|
3390
|
+
if (!existsSync6(resolvedPatchPath)) {
|
|
3391
|
+
return {
|
|
3392
|
+
patchFilePath: resolvedPatchPath,
|
|
3393
|
+
manifestFilePath: existsSync6(manifestFilePath) ? manifestFilePath : void 0,
|
|
3394
|
+
patchFileName,
|
|
3395
|
+
exists: false,
|
|
3396
|
+
diffValid: false,
|
|
3397
|
+
formatError: "Patch file does not exist."
|
|
3398
|
+
};
|
|
3399
|
+
}
|
|
3400
|
+
const [patchContent, fileStats, manifest] = await Promise.all([
|
|
3401
|
+
readFile3(resolvedPatchPath, "utf8"),
|
|
3402
|
+
stat(resolvedPatchPath),
|
|
3403
|
+
readManifest(manifestFilePath)
|
|
3404
|
+
]);
|
|
3405
|
+
const format = validatePatchDiff(patchContent);
|
|
3406
|
+
const derived = derivePatchMetadata(patchContent);
|
|
3407
|
+
return {
|
|
3408
|
+
patchFilePath: resolvedPatchPath,
|
|
3409
|
+
manifestFilePath: manifest ? manifestFilePath : void 0,
|
|
3410
|
+
patchFileName,
|
|
3411
|
+
cveId: manifest?.cveId,
|
|
3412
|
+
packageName: manifest?.packageName,
|
|
3413
|
+
vulnerableVersion: manifest?.vulnerableVersion,
|
|
3414
|
+
patchMode: manifest?.patchMode,
|
|
3415
|
+
confidence: manifest?.confidence,
|
|
3416
|
+
riskLevel: manifest?.riskLevel,
|
|
3417
|
+
generatedAt: manifest?.generatedAt,
|
|
3418
|
+
files: manifest?.files ?? derived.files,
|
|
3419
|
+
hunkCount: manifest?.hunkCount ?? derived.hunkCount,
|
|
3420
|
+
exists: true,
|
|
3421
|
+
diffValid: format.valid,
|
|
3422
|
+
formatError: format.error,
|
|
3423
|
+
patchSizeBytes: fileStats.size,
|
|
3424
|
+
lineCount: patchContent.split(/\r?\n/).length,
|
|
3425
|
+
manifest
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
async function validatePatchArtifact(patchFilePath, options = {}) {
|
|
3429
|
+
const inspection = await inspectPatchArtifact(patchFilePath, options);
|
|
3430
|
+
const validationPhases = [
|
|
3431
|
+
{
|
|
3432
|
+
phase: "diff-format",
|
|
3433
|
+
passed: inspection.diffValid,
|
|
3434
|
+
error: inspection.diffValid ? void 0 : inspection.formatError,
|
|
3435
|
+
message: inspection.diffValid ? "Patch content is a valid unified diff." : void 0
|
|
3436
|
+
}
|
|
3437
|
+
];
|
|
3438
|
+
if (!inspection.exists) {
|
|
3439
|
+
return {
|
|
3440
|
+
patchFilePath: inspection.patchFilePath,
|
|
3441
|
+
manifestFilePath: inspection.manifestFilePath,
|
|
3442
|
+
exists: false,
|
|
3443
|
+
manifestFound: false,
|
|
3444
|
+
diffValid: false,
|
|
3445
|
+
formatError: inspection.formatError,
|
|
3446
|
+
driftDetected: false,
|
|
3447
|
+
validationPhases
|
|
3448
|
+
};
|
|
3449
|
+
}
|
|
3450
|
+
const manifest = inspection.manifest;
|
|
3451
|
+
const manifestFound = Boolean(manifest);
|
|
3452
|
+
if (!manifest) {
|
|
3453
|
+
validationPhases.push({
|
|
3454
|
+
phase: "manifest-write",
|
|
3455
|
+
passed: false,
|
|
3456
|
+
error: "No patch manifest found for this patch artifact."
|
|
3457
|
+
});
|
|
3458
|
+
return {
|
|
3459
|
+
patchFilePath: inspection.patchFilePath,
|
|
3460
|
+
manifestFilePath: inspection.manifestFilePath,
|
|
3461
|
+
exists: true,
|
|
3462
|
+
manifestFound,
|
|
3463
|
+
diffValid: inspection.diffValid,
|
|
3464
|
+
formatError: inspection.formatError,
|
|
3465
|
+
driftDetected: false,
|
|
3466
|
+
validationPhases
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
validationPhases.push({
|
|
3470
|
+
phase: "manifest-write",
|
|
3471
|
+
passed: true,
|
|
3472
|
+
message: "Patch manifest is present."
|
|
3473
|
+
});
|
|
3474
|
+
const cwd = options.cwd ?? process.cwd();
|
|
3475
|
+
const packageManager = options.packageManager ?? detectPackageManager(cwd);
|
|
3476
|
+
const inventory = await checkInventoryTool.execute({
|
|
3477
|
+
cwd,
|
|
3478
|
+
packageManager
|
|
3479
|
+
});
|
|
3480
|
+
if (inventory.error) {
|
|
3481
|
+
validationPhases.push({
|
|
3482
|
+
phase: "drift",
|
|
3483
|
+
passed: false,
|
|
3484
|
+
error: inventory.error
|
|
3485
|
+
});
|
|
3486
|
+
return {
|
|
3487
|
+
patchFilePath: inspection.patchFilePath,
|
|
3488
|
+
manifestFilePath: inspection.manifestFilePath,
|
|
3489
|
+
exists: true,
|
|
3490
|
+
manifestFound,
|
|
3491
|
+
diffValid: inspection.diffValid,
|
|
3492
|
+
formatError: inspection.formatError,
|
|
3493
|
+
driftDetected: false,
|
|
3494
|
+
cveId: manifest.cveId,
|
|
3495
|
+
packageName: manifest.packageName,
|
|
3496
|
+
vulnerableVersion: manifest.vulnerableVersion,
|
|
3497
|
+
validationPhases
|
|
3498
|
+
};
|
|
3499
|
+
}
|
|
3500
|
+
const matchingPackages = (inventory.packages ?? []).filter(
|
|
3501
|
+
(pkg) => pkg.name === manifest.packageName
|
|
3502
|
+
);
|
|
3503
|
+
const installedVersion = matchingPackages[0]?.version;
|
|
3504
|
+
const inventoryMatch = matchingPackages.some(
|
|
3505
|
+
(pkg) => pkg.version === manifest.vulnerableVersion
|
|
3506
|
+
);
|
|
3507
|
+
const driftDetected = matchingPackages.length > 0 && !inventoryMatch;
|
|
3508
|
+
validationPhases.push({
|
|
3509
|
+
phase: "drift",
|
|
3510
|
+
passed: !driftDetected,
|
|
3511
|
+
message: matchingPackages.length === 0 ? `Package ${manifest.packageName} is not currently installed.` : inventoryMatch ? `Installed version matches manifest target ${manifest.vulnerableVersion}.` : `Installed version ${installedVersion} does not match manifest target ${manifest.vulnerableVersion}.`,
|
|
3512
|
+
error: driftDetected ? "Patch manifest does not match the installed dependency version." : void 0
|
|
3513
|
+
});
|
|
3514
|
+
return {
|
|
3515
|
+
patchFilePath: inspection.patchFilePath,
|
|
3516
|
+
manifestFilePath: inspection.manifestFilePath,
|
|
3517
|
+
exists: true,
|
|
3518
|
+
manifestFound,
|
|
3519
|
+
diffValid: inspection.diffValid,
|
|
3520
|
+
formatError: inspection.formatError,
|
|
3521
|
+
driftDetected,
|
|
3522
|
+
cveId: manifest.cveId,
|
|
3523
|
+
packageName: manifest.packageName,
|
|
3524
|
+
vulnerableVersion: manifest.vulnerableVersion,
|
|
3525
|
+
installedVersion,
|
|
3526
|
+
inventoryMatch,
|
|
3527
|
+
validationPhases
|
|
3528
|
+
};
|
|
3529
|
+
}
|
|
3530
|
+
function resolvePatchesDir(cwd, patchesDir = DEFAULT_PATCHES_DIR) {
|
|
3531
|
+
return isAbsolute(patchesDir) ? patchesDir : resolve(cwd, patchesDir);
|
|
3532
|
+
}
|
|
3533
|
+
function resolveArtifactPath(cwd, patchFilePath) {
|
|
3534
|
+
return isAbsolute(patchFilePath) ? patchFilePath : resolve(cwd, patchFilePath);
|
|
3535
|
+
}
|
|
3536
|
+
async function readManifest(manifestFilePath) {
|
|
3537
|
+
if (!existsSync6(manifestFilePath)) {
|
|
3538
|
+
return void 0;
|
|
3539
|
+
}
|
|
3540
|
+
try {
|
|
3541
|
+
const raw = await readFile3(manifestFilePath, "utf8");
|
|
3542
|
+
return JSON.parse(raw);
|
|
3543
|
+
} catch {
|
|
3544
|
+
return void 0;
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
function derivePatchMetadata(patchContent) {
|
|
3548
|
+
const files = Array.from(
|
|
3549
|
+
new Set(
|
|
3550
|
+
patchContent.split(/\r?\n/).filter((line) => line.startsWith("+++ b/")).map((line) => line.slice("+++ b/".length))
|
|
3551
|
+
)
|
|
3552
|
+
);
|
|
3553
|
+
const hunkCount = patchContent.split(/\r?\n/).filter((line) => line.startsWith("@@ ")).length;
|
|
3554
|
+
return { files, hunkCount };
|
|
3555
|
+
}
|
|
3556
|
+
function toSummary(inspection) {
|
|
3557
|
+
return {
|
|
3558
|
+
patchFilePath: inspection.patchFilePath,
|
|
3559
|
+
manifestFilePath: inspection.manifestFilePath,
|
|
3560
|
+
patchFileName: inspection.patchFileName,
|
|
3561
|
+
cveId: inspection.cveId,
|
|
3562
|
+
packageName: inspection.packageName,
|
|
3563
|
+
vulnerableVersion: inspection.vulnerableVersion,
|
|
3564
|
+
patchMode: inspection.patchMode,
|
|
3565
|
+
confidence: inspection.confidence,
|
|
3566
|
+
riskLevel: inspection.riskLevel,
|
|
3567
|
+
generatedAt: inspection.generatedAt,
|
|
3568
|
+
files: inspection.files,
|
|
3569
|
+
hunkCount: inspection.hunkCount,
|
|
3570
|
+
diffValid: inspection.diffValid
|
|
3571
|
+
};
|
|
3572
|
+
}
|
|
3573
|
+
|
|
2924
3574
|
// src/platform/evidence.ts
|
|
2925
3575
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
2926
|
-
import { join as
|
|
3576
|
+
import { join as join12 } from "path";
|
|
2927
3577
|
function createEvidenceLog(cwd, cveIds, context = {}) {
|
|
2928
3578
|
return {
|
|
2929
3579
|
runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -2932,6 +3582,7 @@ function createEvidenceLog(cwd, cveIds, context = {}) {
|
|
|
2932
3582
|
parentRunId: context.parentRunId,
|
|
2933
3583
|
actor: context.actor,
|
|
2934
3584
|
source: context.source,
|
|
3585
|
+
llmProvider: context.llmProvider,
|
|
2935
3586
|
idempotencyKey: context.idempotencyKey,
|
|
2936
3587
|
cveIds,
|
|
2937
3588
|
cwd,
|
|
@@ -2953,29 +3604,29 @@ function finalizeEvidence(log) {
|
|
|
2953
3604
|
return log;
|
|
2954
3605
|
}
|
|
2955
3606
|
function writeEvidenceLog(cwd, log) {
|
|
2956
|
-
const dir =
|
|
3607
|
+
const dir = join12(cwd, ".autoremediator", "evidence");
|
|
2957
3608
|
mkdirSync2(dir, { recursive: true });
|
|
2958
|
-
const filePath =
|
|
3609
|
+
const filePath = join12(dir, `${log.runId}.json`);
|
|
2959
3610
|
writeFileSync4(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
|
|
2960
3611
|
return filePath;
|
|
2961
3612
|
}
|
|
2962
3613
|
|
|
2963
3614
|
// src/platform/idempotency.ts
|
|
2964
|
-
import { existsSync as
|
|
2965
|
-
import { join as
|
|
3615
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
3616
|
+
import { join as join13 } from "path";
|
|
2966
3617
|
var DEFAULT_INDEX = {
|
|
2967
3618
|
schemaVersion: "1.0",
|
|
2968
3619
|
entries: {}
|
|
2969
3620
|
};
|
|
2970
3621
|
function indexFilePath(cwd) {
|
|
2971
|
-
return
|
|
3622
|
+
return join13(cwd, ".autoremediator", "state", "idempotency.json");
|
|
2972
3623
|
}
|
|
2973
3624
|
function entryKey(idempotencyKey, cveId) {
|
|
2974
3625
|
return `${idempotencyKey}::${cveId.toUpperCase()}`;
|
|
2975
3626
|
}
|
|
2976
3627
|
function loadIndex(cwd) {
|
|
2977
3628
|
const filePath = indexFilePath(cwd);
|
|
2978
|
-
if (!
|
|
3629
|
+
if (!existsSync7(filePath)) return DEFAULT_INDEX;
|
|
2979
3630
|
try {
|
|
2980
3631
|
const parsed = JSON.parse(readFileSync7(filePath, "utf8"));
|
|
2981
3632
|
if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
|
|
@@ -2988,7 +3639,7 @@ function loadIndex(cwd) {
|
|
|
2988
3639
|
}
|
|
2989
3640
|
function saveIndex(cwd, index) {
|
|
2990
3641
|
const filePath = indexFilePath(cwd);
|
|
2991
|
-
mkdirSync3(
|
|
3642
|
+
mkdirSync3(join13(cwd, ".autoremediator", "state"), { recursive: true });
|
|
2992
3643
|
writeFileSync5(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
|
|
2993
3644
|
}
|
|
2994
3645
|
function readIdempotentReport(cwd, idempotencyKey, cveId) {
|
|
@@ -3045,11 +3696,13 @@ async function remediate(cveId, options = {}) {
|
|
|
3045
3696
|
const constraints = resolveConstraints(options, cwd);
|
|
3046
3697
|
const provenance = resolveProvenanceContext(options);
|
|
3047
3698
|
const correlation = resolveCorrelationContext(options);
|
|
3699
|
+
const llmProvider = resolveProvider(options);
|
|
3048
3700
|
const evidenceEnabled = options.evidence !== false;
|
|
3049
3701
|
const evidence = evidenceEnabled ? createEvidenceLog(cwd, [normalizedCveId], {
|
|
3050
3702
|
...correlation,
|
|
3051
3703
|
actor: provenance.actor,
|
|
3052
3704
|
source: provenance.source,
|
|
3705
|
+
llmProvider,
|
|
3053
3706
|
idempotencyKey: options.idempotencyKey
|
|
3054
3707
|
}) : void 0;
|
|
3055
3708
|
if (options.resume && options.idempotencyKey) {
|
|
@@ -3078,7 +3731,8 @@ async function remediate(cveId, options = {}) {
|
|
|
3078
3731
|
{
|
|
3079
3732
|
cveId: normalizedCveId,
|
|
3080
3733
|
dryRun: Boolean(options.dryRun),
|
|
3081
|
-
preview: Boolean(options.preview)
|
|
3734
|
+
preview: Boolean(options.preview),
|
|
3735
|
+
llmProvider
|
|
3082
3736
|
},
|
|
3083
3737
|
{
|
|
3084
3738
|
directDependenciesOnly: Boolean(constraints.directDependenciesOnly),
|
|
@@ -3126,7 +3780,8 @@ async function remediate(cveId, options = {}) {
|
|
|
3126
3780
|
{ cveId: normalizedCveId },
|
|
3127
3781
|
{
|
|
3128
3782
|
resultCount: report.results.length,
|
|
3129
|
-
vulnerableCount: report.vulnerablePackages.length
|
|
3783
|
+
vulnerableCount: report.vulnerablePackages.length,
|
|
3784
|
+
llmUsage: report.llmUsage
|
|
3130
3785
|
}
|
|
3131
3786
|
);
|
|
3132
3787
|
finalizeEvidence(evidence);
|
|
@@ -3409,6 +4064,7 @@ async function remediateFromScan(inputPath, options = {}) {
|
|
|
3409
4064
|
const findings = parseScanInput(inputPath, format);
|
|
3410
4065
|
const cveIds = uniqueCveIds(findings);
|
|
3411
4066
|
const policy = loadPolicy(cwd, options.policy);
|
|
4067
|
+
const llmProvider = resolveProvider(options);
|
|
3412
4068
|
const correlation = resolveCorrelationContext(options);
|
|
3413
4069
|
const provenance = resolveProvenanceContext(options);
|
|
3414
4070
|
const constraints = resolveConstraints(options, cwd);
|
|
@@ -3416,6 +4072,7 @@ async function remediateFromScan(inputPath, options = {}) {
|
|
|
3416
4072
|
...correlation,
|
|
3417
4073
|
actor: provenance.actor,
|
|
3418
4074
|
source: provenance.source,
|
|
4075
|
+
llmProvider,
|
|
3419
4076
|
idempotencyKey: options.idempotencyKey
|
|
3420
4077
|
});
|
|
3421
4078
|
addEvidenceStep(evidence, "scan.parse", { inputPath, format }, { findingCount: findings.length, cveCount: cveIds.length });
|
|
@@ -3433,6 +4090,16 @@ async function remediateFromScan(inputPath, options = {}) {
|
|
|
3433
4090
|
const errors = execution.errors;
|
|
3434
4091
|
const patchCount = execution.patchCount;
|
|
3435
4092
|
const patchValidationFailures = execution.patchValidationFailures;
|
|
4093
|
+
let llmUsageCount = 0;
|
|
4094
|
+
let estimatedCostUsd = 0;
|
|
4095
|
+
let totalLlmLatencyMs = 0;
|
|
4096
|
+
for (const report of reports) {
|
|
4097
|
+
for (const usage of report.llmUsage ?? []) {
|
|
4098
|
+
llmUsageCount += 1;
|
|
4099
|
+
estimatedCostUsd += usage.estimatedCostUsd ?? 0;
|
|
4100
|
+
totalLlmLatencyMs += usage.latencyMs ?? 0;
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
3436
4103
|
const outcome = buildScanOutcome({ reports, errors });
|
|
3437
4104
|
const { status, successCount, failedCount, strategyCounts, dependencyScopeCounts, unresolvedByReason, remediationCount } = outcome;
|
|
3438
4105
|
evidence.summary = {
|
|
@@ -3446,7 +4113,10 @@ async function remediateFromScan(inputPath, options = {}) {
|
|
|
3446
4113
|
strategyCounts,
|
|
3447
4114
|
dependencyScopeCounts,
|
|
3448
4115
|
unresolvedByReason,
|
|
3449
|
-
patchesDir: patchCount > 0 ? patchesDir : void 0
|
|
4116
|
+
patchesDir: patchCount > 0 ? patchesDir : void 0,
|
|
4117
|
+
llmUsageCount: llmUsageCount > 0 ? llmUsageCount : void 0,
|
|
4118
|
+
estimatedCostUsd: llmUsageCount > 0 ? Number(estimatedCostUsd.toFixed(6)) : void 0,
|
|
4119
|
+
totalLlmLatencyMs: llmUsageCount > 0 ? totalLlmLatencyMs : void 0
|
|
3450
4120
|
};
|
|
3451
4121
|
finalizeEvidence(evidence);
|
|
3452
4122
|
const evidenceFile = options.evidence === false ? void 0 : writeEvidenceLog(cwd, evidence);
|
|
@@ -3469,7 +4139,10 @@ async function remediateFromScan(inputPath, options = {}) {
|
|
|
3469
4139
|
correlation,
|
|
3470
4140
|
provenance,
|
|
3471
4141
|
constraints,
|
|
3472
|
-
idempotencyKey: options.idempotencyKey
|
|
4142
|
+
idempotencyKey: options.idempotencyKey,
|
|
4143
|
+
llmUsageCount: llmUsageCount > 0 ? llmUsageCount : void 0,
|
|
4144
|
+
estimatedCostUsd: llmUsageCount > 0 ? Number(estimatedCostUsd.toFixed(6)) : void 0,
|
|
4145
|
+
totalLlmLatencyMs: llmUsageCount > 0 ? totalLlmLatencyMs : void 0
|
|
3473
4146
|
};
|
|
3474
4147
|
}
|
|
3475
4148
|
|
|
@@ -3486,8 +4159,11 @@ export {
|
|
|
3486
4159
|
toCiSummary,
|
|
3487
4160
|
ciExitCode,
|
|
3488
4161
|
toSarifOutput,
|
|
4162
|
+
listPatchArtifacts,
|
|
4163
|
+
inspectPatchArtifact,
|
|
4164
|
+
validatePatchArtifact,
|
|
3489
4165
|
remediate,
|
|
3490
4166
|
planRemediation,
|
|
3491
4167
|
remediateFromScan
|
|
3492
4168
|
};
|
|
3493
|
-
//# sourceMappingURL=chunk-
|
|
4169
|
+
//# sourceMappingURL=chunk-5S4Y3WVZ.js.map
|