@runsec/mcp 1.0.85 → 1.0.88
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 +189 -57
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6066,6 +6066,19 @@ function localhostAlternateUploadUrl(url) {
|
|
|
6066
6066
|
return null;
|
|
6067
6067
|
}
|
|
6068
6068
|
}
|
|
6069
|
+
function hubUploadUrlCandidates(primaryUrl) {
|
|
6070
|
+
const urls = [];
|
|
6071
|
+
const add = (candidate) => {
|
|
6072
|
+
const trimmed = candidate?.trim();
|
|
6073
|
+
if (trimmed && !urls.includes(trimmed)) urls.push(trimmed);
|
|
6074
|
+
};
|
|
6075
|
+
add(primaryUrl);
|
|
6076
|
+
const direct = process.env.RUNSEC_HUB_DIRECT_ORIGIN?.trim();
|
|
6077
|
+
if (direct) add(appendUploadPath(direct));
|
|
6078
|
+
add(localhostAlternateUploadUrl(primaryUrl));
|
|
6079
|
+
add(dockerInternalAlternateUploadUrl(primaryUrl));
|
|
6080
|
+
return urls;
|
|
6081
|
+
}
|
|
6069
6082
|
function dockerInternalAlternateUploadUrl(url) {
|
|
6070
6083
|
if (!isDockerRuntime()) return null;
|
|
6071
6084
|
try {
|
|
@@ -6129,7 +6142,8 @@ function buildHubComplianceBlock(result) {
|
|
|
6129
6142
|
// src/telemetryClient.ts
|
|
6130
6143
|
var HUB_UPLOAD_TIMEOUT_MS = 12e4;
|
|
6131
6144
|
var HUB_UPLOAD_MAX_RETRIES = 4;
|
|
6132
|
-
var
|
|
6145
|
+
var HUB_WRITE_CHUNK_BYTES = 16 * 1024;
|
|
6146
|
+
var HUB_HTTPS_AGENT = new import_node_https.default.Agent({ keepAlive: false, maxSockets: 2, timeout: HUB_UPLOAD_TIMEOUT_MS });
|
|
6133
6147
|
function sleepMs(ms) {
|
|
6134
6148
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6135
6149
|
}
|
|
@@ -6194,7 +6208,7 @@ function formatHubSyncErrorMessage(error, targetUrl, response, responseBody) {
|
|
|
6194
6208
|
message += `. Body: ${responseBody.trim().slice(0, 300)}`;
|
|
6195
6209
|
}
|
|
6196
6210
|
if (status === "n/a" && /fetch failed|ECONNRESET|ECONNREFUSED|ENOTFOUND|ETIMEDOUT|CERT|SSL|TLS/i.test(err.message + code)) {
|
|
6197
|
-
message += ". Hint:
|
|
6211
|
+
message += ". Hint: if Hub uses Cloudflare Tunnel, set HTTP Host Header to runsec.io (not localhost:3000) in the tunnel public hostname settings, or set RUNSEC_HUB_DIRECT_ORIGIN=http://YOUR_SERVER_IP:3000 in MCP env to bypass the tunnel.";
|
|
6198
6212
|
}
|
|
6199
6213
|
return message;
|
|
6200
6214
|
}
|
|
@@ -6206,34 +6220,139 @@ function slimFindingForHubUpload(finding) {
|
|
|
6206
6220
|
file_path: finding.file_path,
|
|
6207
6221
|
line: finding.line,
|
|
6208
6222
|
detector_name: finding.detector_name,
|
|
6209
|
-
description: finding.description?.slice(0,
|
|
6210
|
-
match_text: finding.match_text?.slice(0, 120),
|
|
6223
|
+
description: finding.description?.slice(0, 160),
|
|
6211
6224
|
confidence_score: finding.confidence_score,
|
|
6212
6225
|
suppressed: finding.suppressed,
|
|
6213
6226
|
suppression_reason: finding.suppression_reason
|
|
6214
6227
|
};
|
|
6215
6228
|
}
|
|
6229
|
+
function buildSuppressedSummaryForHub(findings, includeLocations) {
|
|
6230
|
+
const byReason = {};
|
|
6231
|
+
for (const f of findings) {
|
|
6232
|
+
const reason = String(f.suppression_reason ?? "cognitive_suppressed");
|
|
6233
|
+
byReason[reason] = (byReason[reason] ?? 0) + 1;
|
|
6234
|
+
}
|
|
6235
|
+
const summary = { total: findings.length, by_reason: byReason };
|
|
6236
|
+
if (!includeLocations) return summary;
|
|
6237
|
+
const locations = [];
|
|
6238
|
+
for (const f of findings) {
|
|
6239
|
+
const reason = String(f.suppression_reason ?? "cognitive_suppressed");
|
|
6240
|
+
locations.push({
|
|
6241
|
+
file_path: f.file_path,
|
|
6242
|
+
line: f.line ?? 0,
|
|
6243
|
+
rule_id: f.rule_id,
|
|
6244
|
+
detector_name: f.detector_name,
|
|
6245
|
+
suppression_reason: reason,
|
|
6246
|
+
severity: f.severity
|
|
6247
|
+
});
|
|
6248
|
+
}
|
|
6249
|
+
summary.locations = locations;
|
|
6250
|
+
return summary;
|
|
6251
|
+
}
|
|
6252
|
+
function slimEngineSummaryForHub(engineSummary) {
|
|
6253
|
+
if (!engineSummary) return void 0;
|
|
6254
|
+
const pick = (row) => row ? {
|
|
6255
|
+
engine: row.engine,
|
|
6256
|
+
status: row.status,
|
|
6257
|
+
finding_count: row.finding_count,
|
|
6258
|
+
duration_ms: row.duration_ms
|
|
6259
|
+
} : void 0;
|
|
6260
|
+
return {
|
|
6261
|
+
concurrent_duration_ms: engineSummary.concurrent_duration_ms,
|
|
6262
|
+
semgrep: pick(engineSummary.semgrep),
|
|
6263
|
+
trufflehog: pick(engineSummary.trufflehog),
|
|
6264
|
+
syft: pick(engineSummary.syft)
|
|
6265
|
+
};
|
|
6266
|
+
}
|
|
6267
|
+
function slimReportMetricsForHub(reportMetrics) {
|
|
6268
|
+
const { findings_suppressed: _fs, raw_engines: _raw, engine_summary, ...rest } = reportMetrics;
|
|
6269
|
+
return {
|
|
6270
|
+
...rest,
|
|
6271
|
+
engine_summary: slimEngineSummaryForHub(engine_summary)
|
|
6272
|
+
};
|
|
6273
|
+
}
|
|
6274
|
+
function minimalRunsecJsonForHubUpload(result, workspacePath, verdict, metrics, compliance, complianceASVS) {
|
|
6275
|
+
return {
|
|
6276
|
+
source: "runsec_mcp",
|
|
6277
|
+
transport: "minimal",
|
|
6278
|
+
standard: result.standard,
|
|
6279
|
+
workspace_path: workspacePath,
|
|
6280
|
+
verdict,
|
|
6281
|
+
metrics,
|
|
6282
|
+
compliance,
|
|
6283
|
+
complianceASVS,
|
|
6284
|
+
duration_ms: result.duration_ms,
|
|
6285
|
+
findings_count: result.findings_count,
|
|
6286
|
+
cognitive_suppressed_count: result.cognitive_suppressed_count,
|
|
6287
|
+
findings_suppressed_summary: buildSuppressedSummaryForHub(result.findings_suppressed, false),
|
|
6288
|
+
cognitive: result.cognitive,
|
|
6289
|
+
engine_summary: slimEngineSummaryForHub(result.engine_summary),
|
|
6290
|
+
verdict_detail: {
|
|
6291
|
+
status: result.verdict?.status,
|
|
6292
|
+
blocking_findings_count: result.verdict?.blocking_findings_count,
|
|
6293
|
+
primary_findings_count: result.verdict?.primary_findings_count
|
|
6294
|
+
}
|
|
6295
|
+
};
|
|
6296
|
+
}
|
|
6216
6297
|
function slimRunsecJsonForHubUpload(result, reportMetrics, workspacePath, verdict, metrics, compliance, complianceASVS) {
|
|
6298
|
+
if (process.env.RUNSEC_HUB_FULL_UPLOAD !== "1") {
|
|
6299
|
+
return minimalRunsecJsonForHubUpload(
|
|
6300
|
+
result,
|
|
6301
|
+
workspacePath,
|
|
6302
|
+
verdict,
|
|
6303
|
+
metrics,
|
|
6304
|
+
compliance,
|
|
6305
|
+
complianceASVS
|
|
6306
|
+
);
|
|
6307
|
+
}
|
|
6217
6308
|
return {
|
|
6218
6309
|
source: "runsec_mcp",
|
|
6310
|
+
transport: "full",
|
|
6219
6311
|
standard: result.standard,
|
|
6220
6312
|
workspace_path: workspacePath,
|
|
6221
6313
|
verdict,
|
|
6222
6314
|
metrics,
|
|
6223
6315
|
compliance,
|
|
6224
6316
|
complianceASVS,
|
|
6225
|
-
report_metrics: reportMetrics,
|
|
6317
|
+
report_metrics: slimReportMetricsForHub(reportMetrics),
|
|
6226
6318
|
findings: result.findings.map(slimFindingForHubUpload),
|
|
6227
|
-
|
|
6319
|
+
findings_suppressed_summary: buildSuppressedSummaryForHub(result.findings_suppressed, true),
|
|
6320
|
+
findings_suppressed_count: result.findings_suppressed.length,
|
|
6228
6321
|
duration_ms: result.duration_ms,
|
|
6229
6322
|
findings_count: result.findings_count,
|
|
6230
6323
|
cognitive_suppressed_count: result.cognitive_suppressed_count,
|
|
6231
6324
|
engines: result.engines,
|
|
6232
|
-
engine_summary: result.engine_summary,
|
|
6325
|
+
engine_summary: slimEngineSummaryForHub(result.engine_summary),
|
|
6233
6326
|
cognitive: result.cognitive,
|
|
6234
6327
|
verdict_detail: result.verdict
|
|
6235
6328
|
};
|
|
6236
6329
|
}
|
|
6330
|
+
function toMinimalHubPayload(payload) {
|
|
6331
|
+
const runsecJson = payload.runsecJson ?? {};
|
|
6332
|
+
const minimalJson = {
|
|
6333
|
+
source: "runsec_mcp",
|
|
6334
|
+
transport: "minimal",
|
|
6335
|
+
workspace_path: runsecJson.workspace_path,
|
|
6336
|
+
standard: runsecJson.standard,
|
|
6337
|
+
verdict: payload.verdict,
|
|
6338
|
+
metrics: payload.metrics,
|
|
6339
|
+
compliance: runsecJson.compliance,
|
|
6340
|
+
complianceASVS: runsecJson.complianceASVS,
|
|
6341
|
+
cognitive: runsecJson.cognitive,
|
|
6342
|
+
findings_suppressed_summary: runsecJson.findings_suppressed_summary,
|
|
6343
|
+
findings_suppressed_count: runsecJson.findings_suppressed_count,
|
|
6344
|
+
duration_ms: runsecJson.duration_ms,
|
|
6345
|
+
findings_count: runsecJson.findings_count,
|
|
6346
|
+
engine_summary: runsecJson.engine_summary,
|
|
6347
|
+
verdict_detail: runsecJson.verdict_detail
|
|
6348
|
+
};
|
|
6349
|
+
return {
|
|
6350
|
+
projectId: payload.projectId,
|
|
6351
|
+
verdict: payload.verdict,
|
|
6352
|
+
metrics: payload.metrics,
|
|
6353
|
+
runsecJson: minimalJson
|
|
6354
|
+
};
|
|
6355
|
+
}
|
|
6237
6356
|
function logHubSyncFailure(message, error, targetUrl) {
|
|
6238
6357
|
if (error != null && targetUrl) {
|
|
6239
6358
|
dumpHubNetworkError(error, targetUrl);
|
|
@@ -6250,9 +6369,11 @@ function postHubUploadNodeHttp(url, apiKey, payload) {
|
|
|
6250
6369
|
reject(err);
|
|
6251
6370
|
return;
|
|
6252
6371
|
}
|
|
6372
|
+
const bodyBuffer = Buffer.from(body, "utf8");
|
|
6253
6373
|
const headers = {
|
|
6254
6374
|
...hubAuthHeaders(apiKey),
|
|
6255
|
-
"Content-Length": String(
|
|
6375
|
+
"Content-Length": String(bodyBuffer.length),
|
|
6376
|
+
Connection: "close"
|
|
6256
6377
|
};
|
|
6257
6378
|
const lib = parsed.protocol === "https:" ? import_node_https.default : import_node_http.default;
|
|
6258
6379
|
const port = parsed.port !== "" ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
|
|
@@ -6287,27 +6408,33 @@ function postHubUploadNodeHttp(url, apiKey, payload) {
|
|
|
6287
6408
|
req.destroy();
|
|
6288
6409
|
reject(Object.assign(new Error("Hub upload request timeout"), { code: "ETIMEDOUT" }));
|
|
6289
6410
|
});
|
|
6290
|
-
|
|
6411
|
+
for (let offset = 0; offset < bodyBuffer.length; offset += HUB_WRITE_CHUNK_BYTES) {
|
|
6412
|
+
req.write(bodyBuffer.subarray(offset, offset + HUB_WRITE_CHUNK_BYTES));
|
|
6413
|
+
}
|
|
6291
6414
|
req.end();
|
|
6292
6415
|
});
|
|
6293
6416
|
}
|
|
6294
|
-
function
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6417
|
+
async function postHubUploadFetch(url, apiKey, payload) {
|
|
6418
|
+
return await fetch(url, {
|
|
6419
|
+
method: "POST",
|
|
6420
|
+
headers: hubAuthHeaders(apiKey),
|
|
6421
|
+
body: JSON.stringify(payload),
|
|
6422
|
+
signal: AbortSignal.timeout(HUB_UPLOAD_TIMEOUT_MS)
|
|
6423
|
+
});
|
|
6299
6424
|
}
|
|
6300
6425
|
async function postHubUploadOnce(url, apiKey, payload) {
|
|
6301
|
-
|
|
6302
|
-
|
|
6426
|
+
const useNodeFirst = process.env.RUNSEC_HUB_USE_NODE_HTTP === "1";
|
|
6427
|
+
if (useNodeFirst) {
|
|
6428
|
+
try {
|
|
6429
|
+
return await postHubUploadNodeHttp(url, apiKey, payload);
|
|
6430
|
+
} catch (nodeError) {
|
|
6431
|
+
console.error("[runsec] node:http(s) upload failed \u2014 retrying with fetch()");
|
|
6432
|
+
dumpHubNetworkError(nodeError, url);
|
|
6433
|
+
return postHubUploadFetch(url, apiKey, payload);
|
|
6434
|
+
}
|
|
6303
6435
|
}
|
|
6304
6436
|
try {
|
|
6305
|
-
return await
|
|
6306
|
-
method: "POST",
|
|
6307
|
-
headers: hubAuthHeaders(apiKey),
|
|
6308
|
-
body: JSON.stringify(payload),
|
|
6309
|
-
signal: AbortSignal.timeout(HUB_UPLOAD_TIMEOUT_MS)
|
|
6310
|
-
});
|
|
6437
|
+
return await postHubUploadFetch(url, apiKey, payload);
|
|
6311
6438
|
} catch (fetchError) {
|
|
6312
6439
|
console.error("[runsec] fetch() failed \u2014 retrying Hub upload via node:http(s)");
|
|
6313
6440
|
dumpHubNetworkError(fetchError, url);
|
|
@@ -6379,39 +6506,44 @@ async function uploadScanResultsToHub(payload, apiKey) {
|
|
|
6379
6506
|
console.warn("[runsec] Hub telemetry skipped: API key missing");
|
|
6380
6507
|
return { success: false, message: "Sync failed: API key missing" };
|
|
6381
6508
|
}
|
|
6382
|
-
const
|
|
6383
|
-
const
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
const
|
|
6391
|
-
if (
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
} catch (err) {
|
|
6401
|
-
lastError = err;
|
|
6402
|
-
errors.push(err);
|
|
6403
|
-
dumpHubNetworkError(err, tryUrl);
|
|
6404
|
-
}
|
|
6405
|
-
}
|
|
6406
|
-
throw lastError;
|
|
6407
|
-
};
|
|
6509
|
+
const primaryUrl = resolveHubUploadUrl();
|
|
6510
|
+
const uploadUrls = hubUploadUrlCandidates(primaryUrl);
|
|
6511
|
+
const payloadVariants = [
|
|
6512
|
+
{ label: "minimal", body: payload },
|
|
6513
|
+
{ label: "minimal-fallback", body: toMinimalHubPayload(payload) }
|
|
6514
|
+
];
|
|
6515
|
+
const seenPayloads = /* @__PURE__ */ new Set();
|
|
6516
|
+
const uniqueVariants = payloadVariants.filter((variant) => {
|
|
6517
|
+
const key = JSON.stringify(variant.body);
|
|
6518
|
+
if (seenPayloads.has(key)) return false;
|
|
6519
|
+
seenPayloads.add(key);
|
|
6520
|
+
return true;
|
|
6521
|
+
});
|
|
6522
|
+
console.error(
|
|
6523
|
+
`[runsec] Hub telemetry upload targets: ${uploadUrls.join(" \u2192 ")} (${uniqueVariants[0] ? Buffer.byteLength(JSON.stringify(uniqueVariants[0].body), "utf8") : 0} bytes, mode=${String(uniqueVariants[0]?.body.runsecJson.transport ?? "minimal")})`
|
|
6524
|
+
);
|
|
6525
|
+
let lastNetworkError = new Error("No upload attempts made");
|
|
6526
|
+
let lastTryUrl = primaryUrl;
|
|
6408
6527
|
try {
|
|
6409
6528
|
let response;
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6529
|
+
outer: for (const variant of uniqueVariants) {
|
|
6530
|
+
for (const tryUrl of uploadUrls) {
|
|
6531
|
+
lastTryUrl = tryUrl;
|
|
6532
|
+
const bytes = Buffer.byteLength(JSON.stringify(variant.body), "utf8");
|
|
6533
|
+
console.error(`[runsec] Hub upload attempt (${variant.label}, ${bytes} bytes) \u2192 ${tryUrl}`);
|
|
6534
|
+
try {
|
|
6535
|
+
response = await postHubUpload(tryUrl, trimmedKey, variant.body);
|
|
6536
|
+
break outer;
|
|
6537
|
+
} catch (err) {
|
|
6538
|
+
lastNetworkError = err;
|
|
6539
|
+
dumpHubNetworkError(err, tryUrl);
|
|
6540
|
+
if (!isRetryableHubNetworkError(err)) break;
|
|
6541
|
+
}
|
|
6542
|
+
}
|
|
6543
|
+
}
|
|
6544
|
+
if (!response) {
|
|
6545
|
+
const message = formatHubSyncErrorMessage(lastNetworkError, lastTryUrl);
|
|
6546
|
+
logHubSyncFailure(message, lastNetworkError, lastTryUrl);
|
|
6415
6547
|
return { success: false, message };
|
|
6416
6548
|
}
|
|
6417
6549
|
if (!response.ok) {
|
|
@@ -6421,11 +6553,11 @@ async function uploadScanResultsToHub(payload, apiKey) {
|
|
|
6421
6553
|
const httpMessage = detailSnippet || statusLabel;
|
|
6422
6554
|
const message = formatHubSyncErrorMessage(
|
|
6423
6555
|
new Error(httpMessage),
|
|
6424
|
-
|
|
6556
|
+
lastTryUrl,
|
|
6425
6557
|
response,
|
|
6426
6558
|
detailSnippet
|
|
6427
6559
|
);
|
|
6428
|
-
logHubSyncFailure(message, new Error(httpMessage),
|
|
6560
|
+
logHubSyncFailure(message, new Error(httpMessage), lastTryUrl);
|
|
6429
6561
|
return {
|
|
6430
6562
|
success: false,
|
|
6431
6563
|
message
|
|
@@ -6444,8 +6576,8 @@ async function uploadScanResultsToHub(payload, apiKey) {
|
|
|
6444
6576
|
projectId
|
|
6445
6577
|
};
|
|
6446
6578
|
} catch (error) {
|
|
6447
|
-
const message = formatHubSyncErrorMessage(error,
|
|
6448
|
-
logHubSyncFailure(message, error,
|
|
6579
|
+
const message = formatHubSyncErrorMessage(error, lastTryUrl);
|
|
6580
|
+
logHubSyncFailure(message, error, lastTryUrl);
|
|
6449
6581
|
return { success: false, message };
|
|
6450
6582
|
}
|
|
6451
6583
|
}
|