autoremediator 0.14.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-3NNNFJLV.js → chunk-IXCNMTOO.js} +87 -27
- package/dist/cli.js +2 -3
- package/dist/index.js +87 -26
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +1 -2
- package/dist/openapi/server.js +4 -5
- package/package.json +1 -1
- package/dist/chunk-3NNNFJLV.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/mcp/server.js.map +0 -1
- package/dist/openapi/server.js.map +0 -1
|
@@ -613,13 +613,21 @@ function validatePatchDiff(patchContent) {
|
|
|
613
613
|
// src/api/patches/helpers.ts
|
|
614
614
|
import { existsSync as existsSync3 } from "fs";
|
|
615
615
|
import { readFile } from "fs/promises";
|
|
616
|
-
import { isAbsolute, resolve } from "path";
|
|
616
|
+
import { isAbsolute, resolve, sep } from "path";
|
|
617
617
|
var DEFAULT_PATCHES_DIR = "./patches";
|
|
618
618
|
function resolvePatchesDir(cwd, patchesDir = DEFAULT_PATCHES_DIR) {
|
|
619
619
|
return isAbsolute(patchesDir) ? patchesDir : resolve(cwd, patchesDir);
|
|
620
620
|
}
|
|
621
621
|
function resolveArtifactPath(cwd, patchFilePath) {
|
|
622
|
-
|
|
622
|
+
const resolved = isAbsolute(patchFilePath) ? patchFilePath : resolve(cwd, patchFilePath);
|
|
623
|
+
if (!resolved.endsWith(".patch")) {
|
|
624
|
+
throw new Error(`patchFilePath must point to a .patch file: ${patchFilePath}`);
|
|
625
|
+
}
|
|
626
|
+
const patchesRoot = resolvePatchesDir(cwd);
|
|
627
|
+
if (!resolved.startsWith(patchesRoot + sep)) {
|
|
628
|
+
throw new Error(`patchFilePath must be inside the patches directory: ${patchFilePath}`);
|
|
629
|
+
}
|
|
630
|
+
return resolved;
|
|
623
631
|
}
|
|
624
632
|
async function readManifest(manifestFilePath) {
|
|
625
633
|
if (!existsSync3(manifestFilePath)) {
|
|
@@ -627,7 +635,11 @@ async function readManifest(manifestFilePath) {
|
|
|
627
635
|
}
|
|
628
636
|
try {
|
|
629
637
|
const raw = await readFile(manifestFilePath, "utf8");
|
|
630
|
-
|
|
638
|
+
const parsed = JSON.parse(raw);
|
|
639
|
+
if (parsed === null || typeof parsed !== "object" || parsed.schemaVersion !== "1.0" || typeof parsed.packageName !== "string" || typeof parsed.vulnerableVersion !== "string" || typeof parsed.patchFilePath !== "string" || typeof parsed.patchFileName !== "string" || typeof parsed.applied !== "boolean" || typeof parsed.dryRun !== "boolean" || typeof parsed.generatedAt !== "string") {
|
|
640
|
+
return void 0;
|
|
641
|
+
}
|
|
642
|
+
return parsed;
|
|
631
643
|
} catch {
|
|
632
644
|
return void 0;
|
|
633
645
|
}
|
|
@@ -1062,6 +1074,11 @@ async function loadRemoteFactory() {
|
|
|
1062
1074
|
"AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
|
|
1063
1075
|
);
|
|
1064
1076
|
}
|
|
1077
|
+
if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
|
|
1078
|
+
throw new Error(
|
|
1079
|
+
`AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1065
1082
|
const loaded = await import(moduleName);
|
|
1066
1083
|
const factory = loaded[exportName];
|
|
1067
1084
|
if (typeof factory !== "function") {
|
|
@@ -1223,7 +1240,15 @@ async function httpClient(request) {
|
|
|
1223
1240
|
}
|
|
1224
1241
|
try {
|
|
1225
1242
|
const res = await requestWithTimeout(url, init, timeout);
|
|
1243
|
+
const MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
1244
|
+
const contentLength = res.headers?.get?.("content-length") ?? null;
|
|
1245
|
+
if (contentLength && parseInt(contentLength, 10) > MAX_RESPONSE_BYTES) {
|
|
1246
|
+
throw new HttpError(`Response too large (content-length: ${contentLength})`, "HTTP_ERROR");
|
|
1247
|
+
}
|
|
1226
1248
|
const text = await res.text();
|
|
1249
|
+
if (Buffer.byteLength(text) > MAX_RESPONSE_BYTES) {
|
|
1250
|
+
throw new HttpError("Response body exceeds 10 MB limit", "HTTP_ERROR");
|
|
1251
|
+
}
|
|
1227
1252
|
let data;
|
|
1228
1253
|
try {
|
|
1229
1254
|
data = text ? JSON.parse(text) : {};
|
|
@@ -1862,7 +1887,7 @@ import semver2 from "semver";
|
|
|
1862
1887
|
import { mkdir, rm } from "fs/promises";
|
|
1863
1888
|
import { join as join10 } from "path";
|
|
1864
1889
|
async function sleep(ms) {
|
|
1865
|
-
await new Promise((
|
|
1890
|
+
await new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1866
1891
|
}
|
|
1867
1892
|
async function acquireRepoLock(cwd, options = {}) {
|
|
1868
1893
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
@@ -2621,8 +2646,9 @@ async function resolvePrimaryResult(params) {
|
|
|
2621
2646
|
|
|
2622
2647
|
// src/remediation/tools/fetch-package-source.ts
|
|
2623
2648
|
import { z as z6 } from "zod";
|
|
2624
|
-
import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, rm as rm2 } from "fs/promises";
|
|
2649
|
+
import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile3, rm as rm2 } from "fs/promises";
|
|
2625
2650
|
import { join as join13 } from "path";
|
|
2651
|
+
import { tmpdir } from "os";
|
|
2626
2652
|
import { execa as execa8 } from "execa";
|
|
2627
2653
|
var fetchPackageSourceTool = defineTool({
|
|
2628
2654
|
description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
|
|
@@ -2638,10 +2664,20 @@ var fetchPackageSourceTool = defineTool({
|
|
|
2638
2664
|
version,
|
|
2639
2665
|
filePatterns
|
|
2640
2666
|
}) => {
|
|
2641
|
-
|
|
2667
|
+
if (!/^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(packageName)) {
|
|
2668
|
+
return { success: false, error: `Invalid package name: ${packageName}` };
|
|
2669
|
+
}
|
|
2670
|
+
const safePatterns = (filePatterns ?? ["*.js", "*.ts"]).filter(
|
|
2671
|
+
(p) => /^[a-zA-Z0-9._/*?-]+$/.test(p)
|
|
2672
|
+
);
|
|
2673
|
+
if (safePatterns.length === 0) {
|
|
2674
|
+
return { success: false, error: "No valid file patterns provided." };
|
|
2675
|
+
}
|
|
2676
|
+
const tempBaseDir = await mkdtemp(join13(tmpdir(), "autoremediator-pkg-"));
|
|
2642
2677
|
const extractDir = join13(tempBaseDir, "out");
|
|
2643
2678
|
try {
|
|
2644
|
-
const
|
|
2679
|
+
const scopedName = packageName.split("/").pop();
|
|
2680
|
+
const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
|
|
2645
2681
|
await mkdir2(tempBaseDir, { recursive: true });
|
|
2646
2682
|
const tarballPath = join13(tempBaseDir, "package.tgz");
|
|
2647
2683
|
await execa8("curl", ["-L", "-o", tarballPath, npmUrl]);
|
|
@@ -2669,7 +2705,7 @@ var fetchPackageSourceTool = defineTool({
|
|
|
2669
2705
|
await walkDir(fullPath, relPath);
|
|
2670
2706
|
}
|
|
2671
2707
|
} else if (file.isFile()) {
|
|
2672
|
-
const matches =
|
|
2708
|
+
const matches = safePatterns.some((pattern) => {
|
|
2673
2709
|
const regex = new RegExp(
|
|
2674
2710
|
`^${pattern.replace(/\*/g, ".*").replace(/\./g, "\\.")}$`
|
|
2675
2711
|
);
|
|
@@ -2691,7 +2727,7 @@ var fetchPackageSourceTool = defineTool({
|
|
|
2691
2727
|
if (Object.keys(sourceCode).length === 0) {
|
|
2692
2728
|
return {
|
|
2693
2729
|
success: false,
|
|
2694
|
-
error: `No source files matching patterns [${
|
|
2730
|
+
error: `No source files matching patterns [${safePatterns.join(", ")}] found in ${packageName}@${version}. Download succeeded but extraction yielded no matching files.`
|
|
2695
2731
|
};
|
|
2696
2732
|
}
|
|
2697
2733
|
return {
|
|
@@ -3032,9 +3068,9 @@ import { execa as execa10 } from "execa";
|
|
|
3032
3068
|
|
|
3033
3069
|
// src/remediation/tools/apply-patch-file/helpers.ts
|
|
3034
3070
|
import { existsSync as existsSync8 } from "fs";
|
|
3035
|
-
import { mkdtemp, readFile as readFile4, rm as rm3, writeFile } from "fs/promises";
|
|
3071
|
+
import { mkdtemp as mkdtemp2, readFile as readFile4, rm as rm3, writeFile } from "fs/promises";
|
|
3036
3072
|
import { createHash } from "crypto";
|
|
3037
|
-
import { tmpdir } from "os";
|
|
3073
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
3038
3074
|
import { join as join14 } from "path";
|
|
3039
3075
|
import { execa as execa9 } from "execa";
|
|
3040
3076
|
async function resolvePatchMode(packageManager, cwd) {
|
|
@@ -3179,7 +3215,7 @@ ${createResult.stderr}`);
|
|
|
3179
3215
|
error: `Could not determine native patch directory for ${packageSpec}.`
|
|
3180
3216
|
};
|
|
3181
3217
|
}
|
|
3182
|
-
const tempPatchDir = await
|
|
3218
|
+
const tempPatchDir = await mkdtemp2(join14(tmpdir2(), "autoremediator-native-patch-"));
|
|
3183
3219
|
const tempPatchFile = join14(tempPatchDir, "change.patch");
|
|
3184
3220
|
try {
|
|
3185
3221
|
await writeFile(tempPatchFile, patchContent, "utf8");
|
|
@@ -4661,6 +4697,13 @@ async function enrichWithOssfScorecard(details) {
|
|
|
4661
4697
|
async function probeFeed(url, cveId, token) {
|
|
4662
4698
|
try {
|
|
4663
4699
|
const feedUrl = new URL(url);
|
|
4700
|
+
const hostname = feedUrl.hostname.toLowerCase();
|
|
4701
|
+
if (hostname === "localhost" || /^127\./.test(hostname) || /^10\./.test(hostname) || /^172\.(1[6-9]|2\d|3[01])\./.test(hostname) || /^192\.168\./.test(hostname) || hostname === "0.0.0.0" || /^169\.254\./.test(hostname) || hostname === "[::1]" || hostname === "::1") {
|
|
4702
|
+
return void 0;
|
|
4703
|
+
}
|
|
4704
|
+
if (feedUrl.protocol !== "https:" && feedUrl.protocol !== "http:") {
|
|
4705
|
+
return void 0;
|
|
4706
|
+
}
|
|
4664
4707
|
feedUrl.searchParams.set("cve", cveId);
|
|
4665
4708
|
const headers = {};
|
|
4666
4709
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
@@ -5091,6 +5134,7 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
5091
5134
|
|
|
5092
5135
|
// src/api/change-request/index.ts
|
|
5093
5136
|
import { execa as execa11 } from "execa";
|
|
5137
|
+
import { randomBytes } from "crypto";
|
|
5094
5138
|
function sanitizeBranchToken(value) {
|
|
5095
5139
|
return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
5096
5140
|
}
|
|
@@ -5143,7 +5187,7 @@ async function ensureRepoHasChanges(cwd) {
|
|
|
5143
5187
|
}
|
|
5144
5188
|
function toBranchName(prefix, cveIds) {
|
|
5145
5189
|
const token = sanitizeBranchToken(cveIds.join("-") || "remediation");
|
|
5146
|
-
return `${sanitizeBranchToken(prefix)}/${token}-${
|
|
5190
|
+
return `${sanitizeBranchToken(prefix)}/${token}-${randomBytes(6).toString("hex")}`;
|
|
5147
5191
|
}
|
|
5148
5192
|
async function createGitHubRequest(params) {
|
|
5149
5193
|
const args = ["pr", "create", "--head", params.head, "--base", params.base, "--title", params.title, "--body", params.body];
|
|
@@ -5189,11 +5233,12 @@ async function createChangeRequestsForReports(params) {
|
|
|
5189
5233
|
const provider = resolveProvider2(options);
|
|
5190
5234
|
const grouping = resolveGrouping(options);
|
|
5191
5235
|
const plan = buildPlan(reports);
|
|
5192
|
-
const titlePrefix = options.titlePrefix?.trim();
|
|
5236
|
+
const titlePrefix = options.titlePrefix?.trim().slice(0, 200);
|
|
5237
|
+
const bodyFooter = options.bodyFooter ? options.bodyFooter.slice(0, 2e3) : void 0;
|
|
5193
5238
|
const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
|
|
5194
|
-
const body =
|
|
5239
|
+
const body = bodyFooter ? `${plan.body}
|
|
5195
5240
|
|
|
5196
|
-
${
|
|
5241
|
+
${bodyFooter}` : plan.body;
|
|
5197
5242
|
try {
|
|
5198
5243
|
const hasChanges = await ensureRepoHasChanges(cwd);
|
|
5199
5244
|
if (!hasChanges) {
|
|
@@ -5216,6 +5261,12 @@ ${options.bodyFooter}` : plan.body;
|
|
|
5216
5261
|
const branchPrefix = options.branchPrefix ?? "autoremediator";
|
|
5217
5262
|
const branchName = toBranchName(branchPrefix, plan.cveIds);
|
|
5218
5263
|
const pushRemote = options.pushRemote ?? "origin";
|
|
5264
|
+
if (options.pushRemote && !/^[a-zA-Z0-9._-]+$/.test(options.pushRemote)) {
|
|
5265
|
+
throw new Error(`Invalid pushRemote value: ${options.pushRemote}`);
|
|
5266
|
+
}
|
|
5267
|
+
if (options.repository && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(options.repository)) {
|
|
5268
|
+
throw new Error(`Invalid repository format: ${options.repository}`);
|
|
5269
|
+
}
|
|
5219
5270
|
await execa11("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
|
|
5220
5271
|
await execa11("git", ["add", "-A"], { cwd, stdio: "pipe" });
|
|
5221
5272
|
await execa11("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
|
|
@@ -5903,13 +5954,13 @@ async function planRemediation(cveId, options = {}) {
|
|
|
5903
5954
|
}
|
|
5904
5955
|
|
|
5905
5956
|
// src/scanner/parse-input.ts
|
|
5906
|
-
import { extname as extname2 } from "path";
|
|
5957
|
+
import { extname as extname2, resolve as resolve2 } from "path";
|
|
5907
5958
|
import { readFileSync as readFileSync14 } from "fs";
|
|
5908
5959
|
import { execa as execa12 } from "execa";
|
|
5909
5960
|
|
|
5910
5961
|
// src/scanner/adapters/npm-audit.ts
|
|
5911
5962
|
import { readFileSync as readFileSync11 } from "fs";
|
|
5912
|
-
var CVE_REGEX = /CVE-\d{4}-\d
|
|
5963
|
+
var CVE_REGEX = /CVE-\d{4}-\d{1,7}/gi;
|
|
5913
5964
|
function normalizeSeverity(raw) {
|
|
5914
5965
|
if (!raw) return "UNKNOWN";
|
|
5915
5966
|
const up = raw.toUpperCase();
|
|
@@ -5948,7 +5999,7 @@ function parseNpmAuditJsonFile(filePath) {
|
|
|
5948
5999
|
|
|
5949
6000
|
// src/scanner/adapters/yarn-audit.ts
|
|
5950
6001
|
import { readFileSync as readFileSync12 } from "fs";
|
|
5951
|
-
var CVE_REGEX2 = /CVE-\d{4}-\d
|
|
6002
|
+
var CVE_REGEX2 = /CVE-\d{4}-\d{1,7}/gi;
|
|
5952
6003
|
function normalizeSeverity2(raw) {
|
|
5953
6004
|
if (!raw) return "UNKNOWN";
|
|
5954
6005
|
const up = raw.toUpperCase();
|
|
@@ -5996,7 +6047,7 @@ function parseYarnAuditJsonFile(filePath) {
|
|
|
5996
6047
|
|
|
5997
6048
|
// src/scanner/adapters/sarif.ts
|
|
5998
6049
|
import { readFileSync as readFileSync13 } from "fs";
|
|
5999
|
-
var CVE_REGEX3 = /CVE-\d{4}-\d
|
|
6050
|
+
var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
|
|
6000
6051
|
function extractPackageName(result) {
|
|
6001
6052
|
const pkg = result.properties?.["packageName"];
|
|
6002
6053
|
return typeof pkg === "string" ? pkg : void 0;
|
|
@@ -6005,9 +6056,15 @@ function parseSarifFromString(content) {
|
|
|
6005
6056
|
const report = JSON.parse(content);
|
|
6006
6057
|
const findings = [];
|
|
6007
6058
|
const seen = /* @__PURE__ */ new Set();
|
|
6008
|
-
|
|
6059
|
+
const MAX_RUNS = 100;
|
|
6060
|
+
const MAX_TOTAL_RESULTS = 1e4;
|
|
6061
|
+
let totalResults = 0;
|
|
6062
|
+
for (const run of (report.runs ?? []).slice(0, MAX_RUNS)) {
|
|
6009
6063
|
for (const result of run.results ?? []) {
|
|
6010
|
-
|
|
6064
|
+
if (totalResults++ >= MAX_TOTAL_RESULTS) break;
|
|
6065
|
+
const ruleId = (result.ruleId ?? "").slice(0, 1024);
|
|
6066
|
+
const messageText = (result.message?.text ?? "").slice(0, 4096);
|
|
6067
|
+
const combined = `${ruleId} ${messageText}`;
|
|
6011
6068
|
const matches = combined.match(CVE_REGEX3) ?? [];
|
|
6012
6069
|
for (const match of matches) {
|
|
6013
6070
|
const cveId = match.toUpperCase();
|
|
@@ -6033,15 +6090,19 @@ function parseSarifFile(filePath) {
|
|
|
6033
6090
|
|
|
6034
6091
|
// src/scanner/parse-input.ts
|
|
6035
6092
|
function parseScanInput(filePath, format) {
|
|
6036
|
-
|
|
6093
|
+
if (filePath.includes("\0")) {
|
|
6094
|
+
throw new Error("Invalid scan input path: path contains null bytes");
|
|
6095
|
+
}
|
|
6096
|
+
const resolvedPath = resolve2(filePath);
|
|
6097
|
+
const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
|
|
6037
6098
|
if (resolved === "npm-audit") {
|
|
6038
|
-
return parseNpmAuditJsonFile(
|
|
6099
|
+
return parseNpmAuditJsonFile(resolvedPath);
|
|
6039
6100
|
}
|
|
6040
6101
|
if (resolved === "yarn-audit") {
|
|
6041
|
-
return parseYarnAuditJsonFile(
|
|
6102
|
+
return parseYarnAuditJsonFile(resolvedPath);
|
|
6042
6103
|
}
|
|
6043
6104
|
if (resolved === "sarif") {
|
|
6044
|
-
return parseSarifFile(
|
|
6105
|
+
return parseSarifFile(resolvedPath);
|
|
6045
6106
|
}
|
|
6046
6107
|
throw new Error(`Unsupported input format: ${resolved}`);
|
|
6047
6108
|
}
|
|
@@ -6840,4 +6901,3 @@ export {
|
|
|
6840
6901
|
updateOutdated,
|
|
6841
6902
|
PACKAGE_VERSION
|
|
6842
6903
|
};
|
|
6843
|
-
//# sourceMappingURL=chunk-3NNNFJLV.js.map
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
toSarifOutput,
|
|
13
13
|
updateOutdated,
|
|
14
14
|
validatePatchArtifact
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-IXCNMTOO.js";
|
|
16
16
|
|
|
17
17
|
// src/cli/index.ts
|
|
18
18
|
import { fileURLToPath } from "url";
|
|
@@ -507,7 +507,7 @@ async function runPortfolio(targetsFilePath, opts) {
|
|
|
507
507
|
|
|
508
508
|
// src/cli/types.ts
|
|
509
509
|
function isCveId(value) {
|
|
510
|
-
return /^CVE-\d{4}-\d
|
|
510
|
+
return /^CVE-\d{4}-\d{1,7}$/i.test(value);
|
|
511
511
|
}
|
|
512
512
|
|
|
513
513
|
// src/cli/validators.ts
|
|
@@ -727,4 +727,3 @@ if (isMainModule()) {
|
|
|
727
727
|
export {
|
|
728
728
|
createProgram2 as createProgram
|
|
729
729
|
};
|
|
730
|
-
//# sourceMappingURL=cli.js.map
|
package/dist/index.js
CHANGED
|
@@ -1007,13 +1007,21 @@ function validatePatchDiff(patchContent) {
|
|
|
1007
1007
|
// src/api/patches/helpers.ts
|
|
1008
1008
|
import { existsSync as existsSync3 } from "fs";
|
|
1009
1009
|
import { readFile } from "fs/promises";
|
|
1010
|
-
import { isAbsolute, resolve } from "path";
|
|
1010
|
+
import { isAbsolute, resolve, sep } from "path";
|
|
1011
1011
|
var DEFAULT_PATCHES_DIR = "./patches";
|
|
1012
1012
|
function resolvePatchesDir(cwd, patchesDir = DEFAULT_PATCHES_DIR) {
|
|
1013
1013
|
return isAbsolute(patchesDir) ? patchesDir : resolve(cwd, patchesDir);
|
|
1014
1014
|
}
|
|
1015
1015
|
function resolveArtifactPath(cwd, patchFilePath) {
|
|
1016
|
-
|
|
1016
|
+
const resolved = isAbsolute(patchFilePath) ? patchFilePath : resolve(cwd, patchFilePath);
|
|
1017
|
+
if (!resolved.endsWith(".patch")) {
|
|
1018
|
+
throw new Error(`patchFilePath must point to a .patch file: ${patchFilePath}`);
|
|
1019
|
+
}
|
|
1020
|
+
const patchesRoot = resolvePatchesDir(cwd);
|
|
1021
|
+
if (!resolved.startsWith(patchesRoot + sep)) {
|
|
1022
|
+
throw new Error(`patchFilePath must be inside the patches directory: ${patchFilePath}`);
|
|
1023
|
+
}
|
|
1024
|
+
return resolved;
|
|
1017
1025
|
}
|
|
1018
1026
|
async function readManifest(manifestFilePath) {
|
|
1019
1027
|
if (!existsSync3(manifestFilePath)) {
|
|
@@ -1021,7 +1029,11 @@ async function readManifest(manifestFilePath) {
|
|
|
1021
1029
|
}
|
|
1022
1030
|
try {
|
|
1023
1031
|
const raw = await readFile(manifestFilePath, "utf8");
|
|
1024
|
-
|
|
1032
|
+
const parsed = JSON.parse(raw);
|
|
1033
|
+
if (parsed === null || typeof parsed !== "object" || parsed.schemaVersion !== "1.0" || typeof parsed.packageName !== "string" || typeof parsed.vulnerableVersion !== "string" || typeof parsed.patchFilePath !== "string" || typeof parsed.patchFileName !== "string" || typeof parsed.applied !== "boolean" || typeof parsed.dryRun !== "boolean" || typeof parsed.generatedAt !== "string") {
|
|
1034
|
+
return void 0;
|
|
1035
|
+
}
|
|
1036
|
+
return parsed;
|
|
1025
1037
|
} catch {
|
|
1026
1038
|
return void 0;
|
|
1027
1039
|
}
|
|
@@ -1456,6 +1468,11 @@ async function loadRemoteFactory() {
|
|
|
1456
1468
|
"AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
|
|
1457
1469
|
);
|
|
1458
1470
|
}
|
|
1471
|
+
if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
|
|
1472
|
+
throw new Error(
|
|
1473
|
+
`AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1459
1476
|
const loaded = await import(moduleName);
|
|
1460
1477
|
const factory = loaded[exportName];
|
|
1461
1478
|
if (typeof factory !== "function") {
|
|
@@ -1617,7 +1634,15 @@ async function httpClient(request) {
|
|
|
1617
1634
|
}
|
|
1618
1635
|
try {
|
|
1619
1636
|
const res = await requestWithTimeout(url, init, timeout);
|
|
1637
|
+
const MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
1638
|
+
const contentLength = res.headers?.get?.("content-length") ?? null;
|
|
1639
|
+
if (contentLength && parseInt(contentLength, 10) > MAX_RESPONSE_BYTES) {
|
|
1640
|
+
throw new HttpError(`Response too large (content-length: ${contentLength})`, "HTTP_ERROR");
|
|
1641
|
+
}
|
|
1620
1642
|
const text = await res.text();
|
|
1643
|
+
if (Buffer.byteLength(text) > MAX_RESPONSE_BYTES) {
|
|
1644
|
+
throw new HttpError("Response body exceeds 10 MB limit", "HTTP_ERROR");
|
|
1645
|
+
}
|
|
1621
1646
|
let data;
|
|
1622
1647
|
try {
|
|
1623
1648
|
data = text ? JSON.parse(text) : {};
|
|
@@ -2256,7 +2281,7 @@ import semver2 from "semver";
|
|
|
2256
2281
|
import { mkdir, rm } from "fs/promises";
|
|
2257
2282
|
import { join as join10 } from "path";
|
|
2258
2283
|
async function sleep(ms) {
|
|
2259
|
-
await new Promise((
|
|
2284
|
+
await new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
2260
2285
|
}
|
|
2261
2286
|
async function acquireRepoLock(cwd, options = {}) {
|
|
2262
2287
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
@@ -3015,8 +3040,9 @@ async function resolvePrimaryResult(params) {
|
|
|
3015
3040
|
|
|
3016
3041
|
// src/remediation/tools/fetch-package-source.ts
|
|
3017
3042
|
import { z as z6 } from "zod";
|
|
3018
|
-
import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, rm as rm2 } from "fs/promises";
|
|
3043
|
+
import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile3, rm as rm2 } from "fs/promises";
|
|
3019
3044
|
import { join as join13 } from "path";
|
|
3045
|
+
import { tmpdir } from "os";
|
|
3020
3046
|
import { execa as execa8 } from "execa";
|
|
3021
3047
|
var fetchPackageSourceTool = defineTool({
|
|
3022
3048
|
description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
|
|
@@ -3032,10 +3058,20 @@ var fetchPackageSourceTool = defineTool({
|
|
|
3032
3058
|
version,
|
|
3033
3059
|
filePatterns
|
|
3034
3060
|
}) => {
|
|
3035
|
-
|
|
3061
|
+
if (!/^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(packageName)) {
|
|
3062
|
+
return { success: false, error: `Invalid package name: ${packageName}` };
|
|
3063
|
+
}
|
|
3064
|
+
const safePatterns = (filePatterns ?? ["*.js", "*.ts"]).filter(
|
|
3065
|
+
(p) => /^[a-zA-Z0-9._/*?-]+$/.test(p)
|
|
3066
|
+
);
|
|
3067
|
+
if (safePatterns.length === 0) {
|
|
3068
|
+
return { success: false, error: "No valid file patterns provided." };
|
|
3069
|
+
}
|
|
3070
|
+
const tempBaseDir = await mkdtemp(join13(tmpdir(), "autoremediator-pkg-"));
|
|
3036
3071
|
const extractDir = join13(tempBaseDir, "out");
|
|
3037
3072
|
try {
|
|
3038
|
-
const
|
|
3073
|
+
const scopedName = packageName.split("/").pop();
|
|
3074
|
+
const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
|
|
3039
3075
|
await mkdir2(tempBaseDir, { recursive: true });
|
|
3040
3076
|
const tarballPath = join13(tempBaseDir, "package.tgz");
|
|
3041
3077
|
await execa8("curl", ["-L", "-o", tarballPath, npmUrl]);
|
|
@@ -3063,7 +3099,7 @@ var fetchPackageSourceTool = defineTool({
|
|
|
3063
3099
|
await walkDir(fullPath, relPath);
|
|
3064
3100
|
}
|
|
3065
3101
|
} else if (file.isFile()) {
|
|
3066
|
-
const matches =
|
|
3102
|
+
const matches = safePatterns.some((pattern) => {
|
|
3067
3103
|
const regex = new RegExp(
|
|
3068
3104
|
`^${pattern.replace(/\*/g, ".*").replace(/\./g, "\\.")}$`
|
|
3069
3105
|
);
|
|
@@ -3085,7 +3121,7 @@ var fetchPackageSourceTool = defineTool({
|
|
|
3085
3121
|
if (Object.keys(sourceCode).length === 0) {
|
|
3086
3122
|
return {
|
|
3087
3123
|
success: false,
|
|
3088
|
-
error: `No source files matching patterns [${
|
|
3124
|
+
error: `No source files matching patterns [${safePatterns.join(", ")}] found in ${packageName}@${version}. Download succeeded but extraction yielded no matching files.`
|
|
3089
3125
|
};
|
|
3090
3126
|
}
|
|
3091
3127
|
return {
|
|
@@ -3426,9 +3462,9 @@ import { execa as execa10 } from "execa";
|
|
|
3426
3462
|
|
|
3427
3463
|
// src/remediation/tools/apply-patch-file/helpers.ts
|
|
3428
3464
|
import { existsSync as existsSync8 } from "fs";
|
|
3429
|
-
import { mkdtemp, readFile as readFile4, rm as rm3, writeFile } from "fs/promises";
|
|
3465
|
+
import { mkdtemp as mkdtemp2, readFile as readFile4, rm as rm3, writeFile } from "fs/promises";
|
|
3430
3466
|
import { createHash } from "crypto";
|
|
3431
|
-
import { tmpdir } from "os";
|
|
3467
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
3432
3468
|
import { join as join14 } from "path";
|
|
3433
3469
|
import { execa as execa9 } from "execa";
|
|
3434
3470
|
async function resolvePatchMode(packageManager, cwd) {
|
|
@@ -3573,7 +3609,7 @@ ${createResult.stderr}`);
|
|
|
3573
3609
|
error: `Could not determine native patch directory for ${packageSpec}.`
|
|
3574
3610
|
};
|
|
3575
3611
|
}
|
|
3576
|
-
const tempPatchDir = await
|
|
3612
|
+
const tempPatchDir = await mkdtemp2(join14(tmpdir2(), "autoremediator-native-patch-"));
|
|
3577
3613
|
const tempPatchFile = join14(tempPatchDir, "change.patch");
|
|
3578
3614
|
try {
|
|
3579
3615
|
await writeFile(tempPatchFile, patchContent, "utf8");
|
|
@@ -5055,6 +5091,13 @@ async function enrichWithOssfScorecard(details) {
|
|
|
5055
5091
|
async function probeFeed(url, cveId, token) {
|
|
5056
5092
|
try {
|
|
5057
5093
|
const feedUrl = new URL(url);
|
|
5094
|
+
const hostname = feedUrl.hostname.toLowerCase();
|
|
5095
|
+
if (hostname === "localhost" || /^127\./.test(hostname) || /^10\./.test(hostname) || /^172\.(1[6-9]|2\d|3[01])\./.test(hostname) || /^192\.168\./.test(hostname) || hostname === "0.0.0.0" || /^169\.254\./.test(hostname) || hostname === "[::1]" || hostname === "::1") {
|
|
5096
|
+
return void 0;
|
|
5097
|
+
}
|
|
5098
|
+
if (feedUrl.protocol !== "https:" && feedUrl.protocol !== "http:") {
|
|
5099
|
+
return void 0;
|
|
5100
|
+
}
|
|
5058
5101
|
feedUrl.searchParams.set("cve", cveId);
|
|
5059
5102
|
const headers = {};
|
|
5060
5103
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
@@ -5485,6 +5528,7 @@ async function runRemediationPipeline(cveId, options = {}) {
|
|
|
5485
5528
|
|
|
5486
5529
|
// src/api/change-request/index.ts
|
|
5487
5530
|
import { execa as execa11 } from "execa";
|
|
5531
|
+
import { randomBytes } from "crypto";
|
|
5488
5532
|
function sanitizeBranchToken(value) {
|
|
5489
5533
|
return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
5490
5534
|
}
|
|
@@ -5537,7 +5581,7 @@ async function ensureRepoHasChanges(cwd) {
|
|
|
5537
5581
|
}
|
|
5538
5582
|
function toBranchName(prefix, cveIds) {
|
|
5539
5583
|
const token = sanitizeBranchToken(cveIds.join("-") || "remediation");
|
|
5540
|
-
return `${sanitizeBranchToken(prefix)}/${token}-${
|
|
5584
|
+
return `${sanitizeBranchToken(prefix)}/${token}-${randomBytes(6).toString("hex")}`;
|
|
5541
5585
|
}
|
|
5542
5586
|
async function createGitHubRequest(params) {
|
|
5543
5587
|
const args = ["pr", "create", "--head", params.head, "--base", params.base, "--title", params.title, "--body", params.body];
|
|
@@ -5583,11 +5627,12 @@ async function createChangeRequestsForReports(params) {
|
|
|
5583
5627
|
const provider = resolveProvider2(options);
|
|
5584
5628
|
const grouping = resolveGrouping(options);
|
|
5585
5629
|
const plan = buildPlan(reports);
|
|
5586
|
-
const titlePrefix = options.titlePrefix?.trim();
|
|
5630
|
+
const titlePrefix = options.titlePrefix?.trim().slice(0, 200);
|
|
5631
|
+
const bodyFooter = options.bodyFooter ? options.bodyFooter.slice(0, 2e3) : void 0;
|
|
5587
5632
|
const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
|
|
5588
|
-
const body =
|
|
5633
|
+
const body = bodyFooter ? `${plan.body}
|
|
5589
5634
|
|
|
5590
|
-
${
|
|
5635
|
+
${bodyFooter}` : plan.body;
|
|
5591
5636
|
try {
|
|
5592
5637
|
const hasChanges = await ensureRepoHasChanges(cwd);
|
|
5593
5638
|
if (!hasChanges) {
|
|
@@ -5610,6 +5655,12 @@ ${options.bodyFooter}` : plan.body;
|
|
|
5610
5655
|
const branchPrefix = options.branchPrefix ?? "autoremediator";
|
|
5611
5656
|
const branchName = toBranchName(branchPrefix, plan.cveIds);
|
|
5612
5657
|
const pushRemote = options.pushRemote ?? "origin";
|
|
5658
|
+
if (options.pushRemote && !/^[a-zA-Z0-9._-]+$/.test(options.pushRemote)) {
|
|
5659
|
+
throw new Error(`Invalid pushRemote value: ${options.pushRemote}`);
|
|
5660
|
+
}
|
|
5661
|
+
if (options.repository && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(options.repository)) {
|
|
5662
|
+
throw new Error(`Invalid repository format: ${options.repository}`);
|
|
5663
|
+
}
|
|
5613
5664
|
await execa11("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
|
|
5614
5665
|
await execa11("git", ["add", "-A"], { cwd, stdio: "pipe" });
|
|
5615
5666
|
await execa11("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
|
|
@@ -5966,13 +6017,13 @@ async function planRemediation(cveId, options = {}) {
|
|
|
5966
6017
|
}
|
|
5967
6018
|
|
|
5968
6019
|
// src/scanner/parse-input.ts
|
|
5969
|
-
import { extname as extname2 } from "path";
|
|
6020
|
+
import { extname as extname2, resolve as resolve2 } from "path";
|
|
5970
6021
|
import { readFileSync as readFileSync14 } from "fs";
|
|
5971
6022
|
import { execa as execa12 } from "execa";
|
|
5972
6023
|
|
|
5973
6024
|
// src/scanner/adapters/npm-audit.ts
|
|
5974
6025
|
import { readFileSync as readFileSync11 } from "fs";
|
|
5975
|
-
var CVE_REGEX = /CVE-\d{4}-\d
|
|
6026
|
+
var CVE_REGEX = /CVE-\d{4}-\d{1,7}/gi;
|
|
5976
6027
|
function normalizeSeverity(raw) {
|
|
5977
6028
|
if (!raw) return "UNKNOWN";
|
|
5978
6029
|
const up = raw.toUpperCase();
|
|
@@ -6011,7 +6062,7 @@ function parseNpmAuditJsonFile(filePath) {
|
|
|
6011
6062
|
|
|
6012
6063
|
// src/scanner/adapters/yarn-audit.ts
|
|
6013
6064
|
import { readFileSync as readFileSync12 } from "fs";
|
|
6014
|
-
var CVE_REGEX2 = /CVE-\d{4}-\d
|
|
6065
|
+
var CVE_REGEX2 = /CVE-\d{4}-\d{1,7}/gi;
|
|
6015
6066
|
function normalizeSeverity2(raw) {
|
|
6016
6067
|
if (!raw) return "UNKNOWN";
|
|
6017
6068
|
const up = raw.toUpperCase();
|
|
@@ -6059,7 +6110,7 @@ function parseYarnAuditJsonFile(filePath) {
|
|
|
6059
6110
|
|
|
6060
6111
|
// src/scanner/adapters/sarif.ts
|
|
6061
6112
|
import { readFileSync as readFileSync13 } from "fs";
|
|
6062
|
-
var CVE_REGEX3 = /CVE-\d{4}-\d
|
|
6113
|
+
var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
|
|
6063
6114
|
function extractPackageName(result) {
|
|
6064
6115
|
const pkg = result.properties?.["packageName"];
|
|
6065
6116
|
return typeof pkg === "string" ? pkg : void 0;
|
|
@@ -6068,9 +6119,15 @@ function parseSarifFromString(content) {
|
|
|
6068
6119
|
const report = JSON.parse(content);
|
|
6069
6120
|
const findings = [];
|
|
6070
6121
|
const seen = /* @__PURE__ */ new Set();
|
|
6071
|
-
|
|
6122
|
+
const MAX_RUNS = 100;
|
|
6123
|
+
const MAX_TOTAL_RESULTS = 1e4;
|
|
6124
|
+
let totalResults = 0;
|
|
6125
|
+
for (const run of (report.runs ?? []).slice(0, MAX_RUNS)) {
|
|
6072
6126
|
for (const result of run.results ?? []) {
|
|
6073
|
-
|
|
6127
|
+
if (totalResults++ >= MAX_TOTAL_RESULTS) break;
|
|
6128
|
+
const ruleId = (result.ruleId ?? "").slice(0, 1024);
|
|
6129
|
+
const messageText = (result.message?.text ?? "").slice(0, 4096);
|
|
6130
|
+
const combined = `${ruleId} ${messageText}`;
|
|
6074
6131
|
const matches = combined.match(CVE_REGEX3) ?? [];
|
|
6075
6132
|
for (const match of matches) {
|
|
6076
6133
|
const cveId = match.toUpperCase();
|
|
@@ -6096,15 +6153,19 @@ function parseSarifFile(filePath) {
|
|
|
6096
6153
|
|
|
6097
6154
|
// src/scanner/parse-input.ts
|
|
6098
6155
|
function parseScanInput(filePath, format) {
|
|
6099
|
-
|
|
6156
|
+
if (filePath.includes("\0")) {
|
|
6157
|
+
throw new Error("Invalid scan input path: path contains null bytes");
|
|
6158
|
+
}
|
|
6159
|
+
const resolvedPath = resolve2(filePath);
|
|
6160
|
+
const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
|
|
6100
6161
|
if (resolved === "npm-audit") {
|
|
6101
|
-
return parseNpmAuditJsonFile(
|
|
6162
|
+
return parseNpmAuditJsonFile(resolvedPath);
|
|
6102
6163
|
}
|
|
6103
6164
|
if (resolved === "yarn-audit") {
|
|
6104
|
-
return parseYarnAuditJsonFile(
|
|
6165
|
+
return parseYarnAuditJsonFile(resolvedPath);
|
|
6105
6166
|
}
|
|
6106
6167
|
if (resolved === "sarif") {
|
|
6107
|
-
return parseSarifFile(
|
|
6168
|
+
return parseSarifFile(resolvedPath);
|
|
6108
6169
|
}
|
|
6109
6170
|
throw new Error(`Unsupported input format: ${resolved}`);
|
|
6110
6171
|
}
|