@runsec/mcp 1.0.83 → 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 +155 -34
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1239,7 +1239,6 @@ function applyCognitivePipeline(workspaceRoot, findings) {
|
|
|
1239
1239
|
(f) => f.suppressed || isNuclearSuppressedFinding(f) || !f.primary_log_eligible
|
|
1240
1240
|
)
|
|
1241
1241
|
).concat(duplicates);
|
|
1242
|
-
const allScored = [...primary, ...suppressed];
|
|
1243
1242
|
console.error(
|
|
1244
1243
|
`[runsec] cognitive: raw=${findings.length} nuclear=${nuclearSuppressed.length} primary=${primary.length} suppressed=${suppressed.length}`
|
|
1245
1244
|
);
|
|
@@ -1249,7 +1248,7 @@ function applyCognitivePipeline(workspaceRoot, findings) {
|
|
|
1249
1248
|
summary: {
|
|
1250
1249
|
version: "v1.0",
|
|
1251
1250
|
primary_log_threshold: PRIMARY_LOG_THRESHOLD,
|
|
1252
|
-
findings_total:
|
|
1251
|
+
findings_total: findings.length,
|
|
1253
1252
|
findings_primary: primary.length,
|
|
1254
1253
|
findings_suppressed: suppressed.length,
|
|
1255
1254
|
false_positive_filtering: true
|
|
@@ -1871,14 +1870,6 @@ function mapTrufflehogFindings(rows, workspaceRoot) {
|
|
|
1871
1870
|
const rawSecret = String(raw.Raw ?? "").trim();
|
|
1872
1871
|
const display = redacted || rawSecret || "[secret redacted]";
|
|
1873
1872
|
const description = `TruffleHog: exposed ${detector}${verified ? " (verified)" : ""}`;
|
|
1874
|
-
if (!isTrufflehogVerified(verified, description)) {
|
|
1875
|
-
const blob = `${display} ${rawSecret} ${description}`;
|
|
1876
|
-
if (isLockfileOrModulesPath(rel) || isStaticLayoutDumpPath(rel)) continue;
|
|
1877
|
-
if (hasEnvironmentInterpolation(blob)) continue;
|
|
1878
|
-
if (blobHasDevDatabaseSecret(blob)) continue;
|
|
1879
|
-
if (isHexChecksumBlob(display) || isHexChecksumBlob(rawSecret)) continue;
|
|
1880
|
-
if (isUnverifiedTrufflehogNoiseDetector(detector)) continue;
|
|
1881
|
-
}
|
|
1882
1873
|
const severity = severityForSecret(detector, verified);
|
|
1883
1874
|
findings.push({
|
|
1884
1875
|
category: "secrets",
|
|
@@ -3294,7 +3285,8 @@ function buildAuditReportMetrics(result) {
|
|
|
3294
3285
|
raw_engines: result.raw_engines,
|
|
3295
3286
|
cwe_counts: cweCounts,
|
|
3296
3287
|
verdict: result.verdict,
|
|
3297
|
-
cognitive: result.cognitive
|
|
3288
|
+
cognitive: result.cognitive,
|
|
3289
|
+
findings_suppressed: result.findings_suppressed
|
|
3298
3290
|
};
|
|
3299
3291
|
}
|
|
3300
3292
|
async function executeAudit(toolName, args) {
|
|
@@ -3543,6 +3535,67 @@ function countFindingsByTechStack(findings) {
|
|
|
3543
3535
|
}
|
|
3544
3536
|
return Array.from(counts.entries()).map(([tag, count]) => ({ tag, count })).sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag));
|
|
3545
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
|
+
}
|
|
3546
3599
|
function renderTechStackSummary(findings) {
|
|
3547
3600
|
const rows = countFindingsByTechStack(findings);
|
|
3548
3601
|
const out = [];
|
|
@@ -3751,11 +3804,18 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
|
|
|
3751
3804
|
out.push(
|
|
3752
3805
|
`- **Code Vulnerabilities:** ${byCategory.code.length} | **Exposed Secrets:** ${executiveCategoryLabel("secrets", byCategory.secrets.length, es)} | **Vulnerable Dependencies:** ${executiveCategoryLabel("dependencies", byCategory.dependencies.length, es)}`
|
|
3753
3806
|
);
|
|
3807
|
+
const suppressedRows = Array.isArray(metrics.findings_suppressed) ? metrics.findings_suppressed : [];
|
|
3808
|
+
const rawSecrets = es?.trufflehog?.finding_count ?? metrics.cognitive?.findings_total ?? 0;
|
|
3754
3809
|
out.push(
|
|
3755
|
-
`- **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)}`
|
|
3756
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
|
+
}
|
|
3757
3817
|
out.push("");
|
|
3758
|
-
out.push(...renderTechStackSummary(findings));
|
|
3818
|
+
out.push(...renderTechStackSummary([...findings, ...suppressedRows]));
|
|
3759
3819
|
out.push("");
|
|
3760
3820
|
let sectionNum = 1;
|
|
3761
3821
|
for (const category of CATEGORY_ORDER) {
|
|
@@ -3768,6 +3828,7 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
|
|
|
3768
3828
|
out.push(...renderFindingRows(rows));
|
|
3769
3829
|
sectionNum += 1;
|
|
3770
3830
|
}
|
|
3831
|
+
out.push(...renderSuppressedFindingsSection(suppressedRows));
|
|
3771
3832
|
out.push("---");
|
|
3772
3833
|
out.push("<details><summary>Telemetry (machine)</summary>\n");
|
|
3773
3834
|
out.push("```json");
|
|
@@ -6067,6 +6128,17 @@ function buildHubComplianceBlock(result) {
|
|
|
6067
6128
|
|
|
6068
6129
|
// src/telemetryClient.ts
|
|
6069
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
|
+
}
|
|
6070
6142
|
function hubAuthHeaders(apiKey) {
|
|
6071
6143
|
return {
|
|
6072
6144
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -6121,11 +6193,47 @@ function formatHubSyncErrorMessage(error, targetUrl, response, responseBody) {
|
|
|
6121
6193
|
if (responseBody?.trim()) {
|
|
6122
6194
|
message += `. Body: ${responseBody.trim().slice(0, 300)}`;
|
|
6123
6195
|
}
|
|
6124
|
-
if (status === "n/a" && /fetch failed|ECONNREFUSED|ENOTFOUND|ETIMEDOUT|CERT|SSL|TLS/i.test(err.message + code)) {
|
|
6125
|
-
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).";
|
|
6126
6198
|
}
|
|
6127
6199
|
return message;
|
|
6128
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
|
+
}
|
|
6129
6237
|
function logHubSyncFailure(message, error, targetUrl) {
|
|
6130
6238
|
if (error != null && targetUrl) {
|
|
6131
6239
|
dumpHubNetworkError(error, targetUrl);
|
|
@@ -6155,7 +6263,8 @@ function postHubUploadNodeHttp(url, apiKey, payload) {
|
|
|
6155
6263
|
path: `${parsed.pathname}${parsed.search}`,
|
|
6156
6264
|
method: "POST",
|
|
6157
6265
|
headers,
|
|
6158
|
-
timeout: HUB_UPLOAD_TIMEOUT_MS
|
|
6266
|
+
timeout: HUB_UPLOAD_TIMEOUT_MS,
|
|
6267
|
+
agent: lib === import_node_https.default ? HUB_HTTPS_AGENT : void 0
|
|
6159
6268
|
},
|
|
6160
6269
|
(res) => {
|
|
6161
6270
|
const chunks = [];
|
|
@@ -6183,11 +6292,12 @@ function postHubUploadNodeHttp(url, apiKey, payload) {
|
|
|
6183
6292
|
});
|
|
6184
6293
|
}
|
|
6185
6294
|
function preferNodeHttpUpload() {
|
|
6186
|
-
if (process.
|
|
6295
|
+
if (process.env.VITEST === "true" || process.env.RUNSEC_HUB_USE_FETCH === "1") return false;
|
|
6187
6296
|
if (process.env.RUNSEC_HUB_USE_NODE_HTTP === "1") return true;
|
|
6297
|
+
if (process.platform === "win32") return true;
|
|
6188
6298
|
return false;
|
|
6189
6299
|
}
|
|
6190
|
-
async function
|
|
6300
|
+
async function postHubUploadOnce(url, apiKey, payload) {
|
|
6191
6301
|
if (preferNodeHttpUpload()) {
|
|
6192
6302
|
return postHubUploadNodeHttp(url, apiKey, payload);
|
|
6193
6303
|
}
|
|
@@ -6204,6 +6314,25 @@ async function postHubUpload(url, apiKey, payload) {
|
|
|
6204
6314
|
return postHubUploadNodeHttp(url, apiKey, payload);
|
|
6205
6315
|
}
|
|
6206
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
|
+
}
|
|
6207
6336
|
function countSeverityMetrics(findings) {
|
|
6208
6337
|
const metrics = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
|
|
6209
6338
|
for (const f of findings) {
|
|
@@ -6227,24 +6356,15 @@ function buildHubUploadPayload(result, reportMetrics, workspacePath, projectId)
|
|
|
6227
6356
|
const metrics = countSeverityMetrics(result.findings);
|
|
6228
6357
|
const verdict = resolveScanVerdict(result);
|
|
6229
6358
|
const { compliance, complianceASVS } = buildHubComplianceBlock(result);
|
|
6230
|
-
const runsecJson =
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6359
|
+
const runsecJson = slimRunsecJsonForHubUpload(
|
|
6360
|
+
result,
|
|
6361
|
+
reportMetrics,
|
|
6362
|
+
workspacePath,
|
|
6234
6363
|
verdict,
|
|
6235
6364
|
metrics,
|
|
6236
6365
|
compliance,
|
|
6237
|
-
complianceASVS
|
|
6238
|
-
|
|
6239
|
-
report_metrics: reportMetrics,
|
|
6240
|
-
findings: result.findings,
|
|
6241
|
-
findings_suppressed: result.findings_suppressed,
|
|
6242
|
-
duration_ms: result.duration_ms,
|
|
6243
|
-
findings_count: result.findings_count,
|
|
6244
|
-
engines: result.engines,
|
|
6245
|
-
engine_summary: result.engine_summary,
|
|
6246
|
-
cognitive: result.cognitive
|
|
6247
|
-
};
|
|
6366
|
+
complianceASVS
|
|
6367
|
+
);
|
|
6248
6368
|
const resolvedProjectId = projectId?.trim() || process.env.RUNSEC_PROJECT_ID?.trim() || void 0;
|
|
6249
6369
|
return {
|
|
6250
6370
|
projectId: resolvedProjectId,
|
|
@@ -6260,7 +6380,8 @@ async function uploadScanResultsToHub(payload, apiKey) {
|
|
|
6260
6380
|
return { success: false, message: "Sync failed: API key missing" };
|
|
6261
6381
|
}
|
|
6262
6382
|
const url = resolveHubUploadUrl();
|
|
6263
|
-
|
|
6383
|
+
const payloadBytes = Buffer.byteLength(JSON.stringify(payload), "utf8");
|
|
6384
|
+
console.error(`[runsec] Hub telemetry upload \u2192 ${url} (${payloadBytes} bytes)`);
|
|
6264
6385
|
const attemptUpload = async (targetUrl) => {
|
|
6265
6386
|
const errors = [];
|
|
6266
6387
|
const urls = [targetUrl];
|