@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.
Files changed (2) hide show
  1. package/dist/index.js +189 -57
  2. 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 HUB_HTTPS_AGENT = new import_node_https.default.Agent({ keepAlive: true, maxSockets: 4, timeout: HUB_UPLOAD_TIMEOUT_MS });
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: check network/VPN/firewall, retry the scan, or set RUNSEC_HUB_URL to your Hub origin (NEXT_PUBLIC_APP_URL).";
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, 240),
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
- findings_suppressed: result.findings_suppressed.map(slimFindingForHubUpload),
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(Buffer.byteLength(body))
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
- req.write(body);
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 preferNodeHttpUpload() {
6295
- if (process.env.VITEST === "true" || process.env.RUNSEC_HUB_USE_FETCH === "1") return false;
6296
- if (process.env.RUNSEC_HUB_USE_NODE_HTTP === "1") return true;
6297
- if (process.platform === "win32") return true;
6298
- return false;
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
- if (preferNodeHttpUpload()) {
6302
- return postHubUploadNodeHttp(url, apiKey, payload);
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 fetch(url, {
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 url = resolveHubUploadUrl();
6383
- const payloadBytes = Buffer.byteLength(JSON.stringify(payload), "utf8");
6384
- console.error(`[runsec] Hub telemetry upload \u2192 ${url} (${payloadBytes} bytes)`);
6385
- const attemptUpload = async (targetUrl) => {
6386
- const errors = [];
6387
- const urls = [targetUrl];
6388
- const localhostAlt = localhostAlternateUploadUrl(targetUrl);
6389
- if (localhostAlt && localhostAlt !== targetUrl) urls.push(localhostAlt);
6390
- const dockerAlt = dockerInternalAlternateUploadUrl(targetUrl);
6391
- if (dockerAlt && !urls.includes(dockerAlt)) urls.push(dockerAlt);
6392
- let lastError = new Error("No upload attempts made");
6393
- for (let i = 0; i < urls.length; i++) {
6394
- const tryUrl = urls[i];
6395
- if (i > 0) {
6396
- console.error(`[runsec] Hub telemetry retry \u2192 ${tryUrl}`);
6397
- }
6398
- try {
6399
- return await postHubUpload(tryUrl, trimmedKey, payload);
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
- try {
6411
- response = await attemptUpload(url);
6412
- } catch (networkError) {
6413
- const message = formatHubSyncErrorMessage(networkError, url);
6414
- logHubSyncFailure(message, networkError, url);
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
- url,
6556
+ lastTryUrl,
6425
6557
  response,
6426
6558
  detailSnippet
6427
6559
  );
6428
- logHubSyncFailure(message, new Error(httpMessage), url);
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, url);
6448
- logHubSyncFailure(message, error, url);
6579
+ const message = formatHubSyncErrorMessage(error, lastTryUrl);
6580
+ logHubSyncFailure(message, error, lastTryUrl);
6449
6581
  return { success: false, message };
6450
6582
  }
6451
6583
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runsec/mcp",
3
- "version": "1.0.85",
3
+ "version": "1.0.88",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
6
  "package.json",