@runsec/mcp 1.0.84 → 1.0.85
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/index.js +154 -24
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3285,7 +3285,8 @@ function buildAuditReportMetrics(result) {
|
|
|
3285
3285
|
raw_engines: result.raw_engines,
|
|
3286
3286
|
cwe_counts: cweCounts,
|
|
3287
3287
|
verdict: result.verdict,
|
|
3288
|
-
cognitive: result.cognitive
|
|
3288
|
+
cognitive: result.cognitive,
|
|
3289
|
+
findings_suppressed: result.findings_suppressed
|
|
3289
3290
|
};
|
|
3290
3291
|
}
|
|
3291
3292
|
async function executeAudit(toolName, args) {
|
|
@@ -3534,6 +3535,67 @@ function countFindingsByTechStack(findings) {
|
|
|
3534
3535
|
}
|
|
3535
3536
|
return Array.from(counts.entries()).map(([tag, count]) => ({ tag, count })).sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag));
|
|
3536
3537
|
}
|
|
3538
|
+
function suppressionReasonLabel(finding) {
|
|
3539
|
+
const reason = String(finding.suppression_reason ?? "").trim();
|
|
3540
|
+
if (reason) return reason;
|
|
3541
|
+
const conf = finding.confidence_score;
|
|
3542
|
+
if (typeof conf === "number" && conf <= 0.01) return "nuclear_hard_drop";
|
|
3543
|
+
if (typeof conf === "number" && conf < 0.8) return "cognitive_below_threshold";
|
|
3544
|
+
return "cognitive_suppressed";
|
|
3545
|
+
}
|
|
3546
|
+
function renderSuppressedFindingsSection(suppressed) {
|
|
3547
|
+
const out = [];
|
|
3548
|
+
if (suppressed.length === 0) return out;
|
|
3549
|
+
const byReason = /* @__PURE__ */ new Map();
|
|
3550
|
+
for (const row of suppressed) {
|
|
3551
|
+
const key = suppressionReasonLabel(row);
|
|
3552
|
+
const bucket = byReason.get(key);
|
|
3553
|
+
if (bucket) bucket.push(row);
|
|
3554
|
+
else byReason.set(key, [row]);
|
|
3555
|
+
}
|
|
3556
|
+
out.push("---");
|
|
3557
|
+
out.push(`## Appendix: Cognitively suppressed findings (${suppressed.length})`);
|
|
3558
|
+
out.push("");
|
|
3559
|
+
out.push(
|
|
3560
|
+
"These TruffleHog/Semgrep hits were processed by the cognitive engine and scored below the primary report threshold (0.8). They do **not** change `X-RunSec-Verdict`."
|
|
3561
|
+
);
|
|
3562
|
+
out.push("");
|
|
3563
|
+
out.push("| Suppression reason | Count |");
|
|
3564
|
+
out.push("|---|---:|");
|
|
3565
|
+
const sortedReasons = Array.from(byReason.entries()).sort((a, b) => b[1].length - a[1].length);
|
|
3566
|
+
for (const [reason, rows] of sortedReasons) {
|
|
3567
|
+
out.push(`| \`${safeText(reason)}\` | ${rows.length} |`);
|
|
3568
|
+
}
|
|
3569
|
+
const maxFilesPerReason = 40;
|
|
3570
|
+
for (const [reason, rows] of sortedReasons) {
|
|
3571
|
+
out.push("");
|
|
3572
|
+
out.push(`### ${safeText(reason)} (${rows.length})`);
|
|
3573
|
+
out.push("");
|
|
3574
|
+
out.push("**Affected files:**");
|
|
3575
|
+
const locations = [
|
|
3576
|
+
...new Set(
|
|
3577
|
+
rows.map((row) => `${String(row.file_path || "unknown")}:${Number(row.line ?? 0)}`)
|
|
3578
|
+
)
|
|
3579
|
+
].sort();
|
|
3580
|
+
for (const loc of locations.slice(0, maxFilesPerReason)) {
|
|
3581
|
+
out.push(`- \`${safeText(loc)}\``);
|
|
3582
|
+
}
|
|
3583
|
+
if (locations.length > maxFilesPerReason) {
|
|
3584
|
+
out.push(`- _+${locations.length - maxFilesPerReason} additional locations omitted._`);
|
|
3585
|
+
}
|
|
3586
|
+
const sample = rows.find((r) => String(r.snippet ?? r.match_text ?? "").trim());
|
|
3587
|
+
const sampleText = String(sample?.snippet ?? sample?.match_text ?? "").trim();
|
|
3588
|
+
if (sampleText) {
|
|
3589
|
+
out.push("");
|
|
3590
|
+
out.push("**Example snippet:**");
|
|
3591
|
+
out.push("");
|
|
3592
|
+
out.push("```text");
|
|
3593
|
+
out.push(safeText(sampleText.slice(0, 400)));
|
|
3594
|
+
out.push("```");
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
return out;
|
|
3598
|
+
}
|
|
3537
3599
|
function renderTechStackSummary(findings) {
|
|
3538
3600
|
const rows = countFindingsByTechStack(findings);
|
|
3539
3601
|
const out = [];
|
|
@@ -3742,11 +3804,18 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
|
|
|
3742
3804
|
out.push(
|
|
3743
3805
|
`- **Code Vulnerabilities:** ${byCategory.code.length} | **Exposed Secrets:** ${executiveCategoryLabel("secrets", byCategory.secrets.length, es)} | **Vulnerable Dependencies:** ${executiveCategoryLabel("dependencies", byCategory.dependencies.length, es)}`
|
|
3744
3806
|
);
|
|
3807
|
+
const suppressedRows = Array.isArray(metrics.findings_suppressed) ? metrics.findings_suppressed : [];
|
|
3808
|
+
const rawSecrets = es?.trufflehog?.finding_count ?? metrics.cognitive?.findings_total ?? 0;
|
|
3745
3809
|
out.push(
|
|
3746
|
-
`- **Primary findings (cognitive-filtered):** ${findings.length} | **Suppressed (.runsecignore):** ${Number(metrics.suppressed_fp_count || 0)} | **Suppressed (cognitive):** ${Number(metrics.cognitive_suppressed_count
|
|
3810
|
+
`- **Primary findings (cognitive-filtered):** ${findings.length} | **Suppressed (.runsecignore):** ${Number(metrics.suppressed_fp_count || 0)} | **Suppressed (cognitive):** ${Number(metrics.cognitive_suppressed_count ?? suppressedRows.length)}`
|
|
3747
3811
|
);
|
|
3812
|
+
if (Number(rawSecrets) > 0 && findings.length === 0 && suppressedRows.length > 0) {
|
|
3813
|
+
out.push(
|
|
3814
|
+
`- **Raw TruffleHog hits:** ${rawSecrets} \u2014 see **Appendix: Cognitively suppressed findings** below.`
|
|
3815
|
+
);
|
|
3816
|
+
}
|
|
3748
3817
|
out.push("");
|
|
3749
|
-
out.push(...renderTechStackSummary(findings));
|
|
3818
|
+
out.push(...renderTechStackSummary([...findings, ...suppressedRows]));
|
|
3750
3819
|
out.push("");
|
|
3751
3820
|
let sectionNum = 1;
|
|
3752
3821
|
for (const category of CATEGORY_ORDER) {
|
|
@@ -3759,6 +3828,7 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
|
|
|
3759
3828
|
out.push(...renderFindingRows(rows));
|
|
3760
3829
|
sectionNum += 1;
|
|
3761
3830
|
}
|
|
3831
|
+
out.push(...renderSuppressedFindingsSection(suppressedRows));
|
|
3762
3832
|
out.push("---");
|
|
3763
3833
|
out.push("<details><summary>Telemetry (machine)</summary>\n");
|
|
3764
3834
|
out.push("```json");
|
|
@@ -6058,6 +6128,17 @@ function buildHubComplianceBlock(result) {
|
|
|
6058
6128
|
|
|
6059
6129
|
// src/telemetryClient.ts
|
|
6060
6130
|
var HUB_UPLOAD_TIMEOUT_MS = 12e4;
|
|
6131
|
+
var HUB_UPLOAD_MAX_RETRIES = 4;
|
|
6132
|
+
var HUB_HTTPS_AGENT = new import_node_https.default.Agent({ keepAlive: true, maxSockets: 4, timeout: HUB_UPLOAD_TIMEOUT_MS });
|
|
6133
|
+
function sleepMs(ms) {
|
|
6134
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6135
|
+
}
|
|
6136
|
+
function isRetryableHubNetworkError(error) {
|
|
6137
|
+
const code = networkErrorCode(error);
|
|
6138
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6139
|
+
const blob = `${code} ${message}`;
|
|
6140
|
+
return /ECONNRESET|ECONNREFUSED|ETIMEDOUT|EPIPE|ENOTFOUND|ECONNABORTED|socket hang up|network/i.test(blob);
|
|
6141
|
+
}
|
|
6061
6142
|
function hubAuthHeaders(apiKey) {
|
|
6062
6143
|
return {
|
|
6063
6144
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -6112,11 +6193,47 @@ function formatHubSyncErrorMessage(error, targetUrl, response, responseBody) {
|
|
|
6112
6193
|
if (responseBody?.trim()) {
|
|
6113
6194
|
message += `. Body: ${responseBody.trim().slice(0, 300)}`;
|
|
6114
6195
|
}
|
|
6115
|
-
if (status === "n/a" && /fetch failed|ECONNREFUSED|ENOTFOUND|ETIMEDOUT|CERT|SSL|TLS/i.test(err.message + code)) {
|
|
6116
|
-
message += ". Hint: set RUNSEC_HUB_URL to your
|
|
6196
|
+
if (status === "n/a" && /fetch failed|ECONNRESET|ECONNREFUSED|ENOTFOUND|ETIMEDOUT|CERT|SSL|TLS/i.test(err.message + code)) {
|
|
6197
|
+
message += ". Hint: check network/VPN/firewall, retry the scan, or set RUNSEC_HUB_URL to your Hub origin (NEXT_PUBLIC_APP_URL).";
|
|
6117
6198
|
}
|
|
6118
6199
|
return message;
|
|
6119
6200
|
}
|
|
6201
|
+
function slimFindingForHubUpload(finding) {
|
|
6202
|
+
return {
|
|
6203
|
+
category: finding.category,
|
|
6204
|
+
rule_id: finding.rule_id,
|
|
6205
|
+
severity: finding.severity,
|
|
6206
|
+
file_path: finding.file_path,
|
|
6207
|
+
line: finding.line,
|
|
6208
|
+
detector_name: finding.detector_name,
|
|
6209
|
+
description: finding.description?.slice(0, 240),
|
|
6210
|
+
match_text: finding.match_text?.slice(0, 120),
|
|
6211
|
+
confidence_score: finding.confidence_score,
|
|
6212
|
+
suppressed: finding.suppressed,
|
|
6213
|
+
suppression_reason: finding.suppression_reason
|
|
6214
|
+
};
|
|
6215
|
+
}
|
|
6216
|
+
function slimRunsecJsonForHubUpload(result, reportMetrics, workspacePath, verdict, metrics, compliance, complianceASVS) {
|
|
6217
|
+
return {
|
|
6218
|
+
source: "runsec_mcp",
|
|
6219
|
+
standard: result.standard,
|
|
6220
|
+
workspace_path: workspacePath,
|
|
6221
|
+
verdict,
|
|
6222
|
+
metrics,
|
|
6223
|
+
compliance,
|
|
6224
|
+
complianceASVS,
|
|
6225
|
+
report_metrics: reportMetrics,
|
|
6226
|
+
findings: result.findings.map(slimFindingForHubUpload),
|
|
6227
|
+
findings_suppressed: result.findings_suppressed.map(slimFindingForHubUpload),
|
|
6228
|
+
duration_ms: result.duration_ms,
|
|
6229
|
+
findings_count: result.findings_count,
|
|
6230
|
+
cognitive_suppressed_count: result.cognitive_suppressed_count,
|
|
6231
|
+
engines: result.engines,
|
|
6232
|
+
engine_summary: result.engine_summary,
|
|
6233
|
+
cognitive: result.cognitive,
|
|
6234
|
+
verdict_detail: result.verdict
|
|
6235
|
+
};
|
|
6236
|
+
}
|
|
6120
6237
|
function logHubSyncFailure(message, error, targetUrl) {
|
|
6121
6238
|
if (error != null && targetUrl) {
|
|
6122
6239
|
dumpHubNetworkError(error, targetUrl);
|
|
@@ -6146,7 +6263,8 @@ function postHubUploadNodeHttp(url, apiKey, payload) {
|
|
|
6146
6263
|
path: `${parsed.pathname}${parsed.search}`,
|
|
6147
6264
|
method: "POST",
|
|
6148
6265
|
headers,
|
|
6149
|
-
timeout: HUB_UPLOAD_TIMEOUT_MS
|
|
6266
|
+
timeout: HUB_UPLOAD_TIMEOUT_MS,
|
|
6267
|
+
agent: lib === import_node_https.default ? HUB_HTTPS_AGENT : void 0
|
|
6150
6268
|
},
|
|
6151
6269
|
(res) => {
|
|
6152
6270
|
const chunks = [];
|
|
@@ -6174,11 +6292,12 @@ function postHubUploadNodeHttp(url, apiKey, payload) {
|
|
|
6174
6292
|
});
|
|
6175
6293
|
}
|
|
6176
6294
|
function preferNodeHttpUpload() {
|
|
6177
|
-
if (process.
|
|
6295
|
+
if (process.env.VITEST === "true" || process.env.RUNSEC_HUB_USE_FETCH === "1") return false;
|
|
6178
6296
|
if (process.env.RUNSEC_HUB_USE_NODE_HTTP === "1") return true;
|
|
6297
|
+
if (process.platform === "win32") return true;
|
|
6179
6298
|
return false;
|
|
6180
6299
|
}
|
|
6181
|
-
async function
|
|
6300
|
+
async function postHubUploadOnce(url, apiKey, payload) {
|
|
6182
6301
|
if (preferNodeHttpUpload()) {
|
|
6183
6302
|
return postHubUploadNodeHttp(url, apiKey, payload);
|
|
6184
6303
|
}
|
|
@@ -6195,6 +6314,25 @@ async function postHubUpload(url, apiKey, payload) {
|
|
|
6195
6314
|
return postHubUploadNodeHttp(url, apiKey, payload);
|
|
6196
6315
|
}
|
|
6197
6316
|
}
|
|
6317
|
+
async function postHubUpload(url, apiKey, payload) {
|
|
6318
|
+
let lastError = new Error("Hub upload failed");
|
|
6319
|
+
for (let attempt = 0; attempt < HUB_UPLOAD_MAX_RETRIES; attempt++) {
|
|
6320
|
+
try {
|
|
6321
|
+
return await postHubUploadOnce(url, apiKey, payload);
|
|
6322
|
+
} catch (error) {
|
|
6323
|
+
lastError = error;
|
|
6324
|
+
if (attempt >= HUB_UPLOAD_MAX_RETRIES - 1 || !isRetryableHubNetworkError(error)) {
|
|
6325
|
+
throw error;
|
|
6326
|
+
}
|
|
6327
|
+
const delayMs = 1e3 * 2 ** attempt;
|
|
6328
|
+
console.error(
|
|
6329
|
+
`[runsec] Hub upload retry ${attempt + 2}/${HUB_UPLOAD_MAX_RETRIES} in ${delayMs}ms (${networkErrorCode(error) || "network"})`
|
|
6330
|
+
);
|
|
6331
|
+
await sleepMs(delayMs);
|
|
6332
|
+
}
|
|
6333
|
+
}
|
|
6334
|
+
throw lastError;
|
|
6335
|
+
}
|
|
6198
6336
|
function countSeverityMetrics(findings) {
|
|
6199
6337
|
const metrics = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
|
|
6200
6338
|
for (const f of findings) {
|
|
@@ -6218,24 +6356,15 @@ function buildHubUploadPayload(result, reportMetrics, workspacePath, projectId)
|
|
|
6218
6356
|
const metrics = countSeverityMetrics(result.findings);
|
|
6219
6357
|
const verdict = resolveScanVerdict(result);
|
|
6220
6358
|
const { compliance, complianceASVS } = buildHubComplianceBlock(result);
|
|
6221
|
-
const runsecJson =
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6359
|
+
const runsecJson = slimRunsecJsonForHubUpload(
|
|
6360
|
+
result,
|
|
6361
|
+
reportMetrics,
|
|
6362
|
+
workspacePath,
|
|
6225
6363
|
verdict,
|
|
6226
6364
|
metrics,
|
|
6227
6365
|
compliance,
|
|
6228
|
-
complianceASVS
|
|
6229
|
-
|
|
6230
|
-
report_metrics: reportMetrics,
|
|
6231
|
-
findings: result.findings,
|
|
6232
|
-
findings_suppressed: result.findings_suppressed,
|
|
6233
|
-
duration_ms: result.duration_ms,
|
|
6234
|
-
findings_count: result.findings_count,
|
|
6235
|
-
engines: result.engines,
|
|
6236
|
-
engine_summary: result.engine_summary,
|
|
6237
|
-
cognitive: result.cognitive
|
|
6238
|
-
};
|
|
6366
|
+
complianceASVS
|
|
6367
|
+
);
|
|
6239
6368
|
const resolvedProjectId = projectId?.trim() || process.env.RUNSEC_PROJECT_ID?.trim() || void 0;
|
|
6240
6369
|
return {
|
|
6241
6370
|
projectId: resolvedProjectId,
|
|
@@ -6251,7 +6380,8 @@ async function uploadScanResultsToHub(payload, apiKey) {
|
|
|
6251
6380
|
return { success: false, message: "Sync failed: API key missing" };
|
|
6252
6381
|
}
|
|
6253
6382
|
const url = resolveHubUploadUrl();
|
|
6254
|
-
|
|
6383
|
+
const payloadBytes = Buffer.byteLength(JSON.stringify(payload), "utf8");
|
|
6384
|
+
console.error(`[runsec] Hub telemetry upload \u2192 ${url} (${payloadBytes} bytes)`);
|
|
6255
6385
|
const attemptUpload = async (targetUrl) => {
|
|
6256
6386
|
const errors = [];
|
|
6257
6387
|
const urls = [targetUrl];
|