@runsec/mcp 1.0.87 → 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 +124 -40
  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 {
@@ -6195,7 +6208,7 @@ function formatHubSyncErrorMessage(error, targetUrl, response, responseBody) {
6195
6208
  message += `. Body: ${responseBody.trim().slice(0, 300)}`;
6196
6209
  }
6197
6210
  if (status === "n/a" && /fetch failed|ECONNRESET|ECONNREFUSED|ENOTFOUND|ETIMEDOUT|CERT|SSL|TLS/i.test(err.message + code)) {
6198
- 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.";
6199
6212
  }
6200
6213
  return message;
6201
6214
  }
@@ -6213,12 +6226,17 @@ function slimFindingForHubUpload(finding) {
6213
6226
  suppression_reason: finding.suppression_reason
6214
6227
  };
6215
6228
  }
6216
- function buildSuppressedSummaryForHub(findings) {
6229
+ function buildSuppressedSummaryForHub(findings, includeLocations) {
6217
6230
  const byReason = {};
6218
- const locations = [];
6219
6231
  for (const f of findings) {
6220
6232
  const reason = String(f.suppression_reason ?? "cognitive_suppressed");
6221
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");
6222
6240
  locations.push({
6223
6241
  file_path: f.file_path,
6224
6242
  line: f.line ?? 0,
@@ -6228,7 +6246,8 @@ function buildSuppressedSummaryForHub(findings) {
6228
6246
  severity: f.severity
6229
6247
  });
6230
6248
  }
6231
- return { total: findings.length, by_reason: byReason, locations };
6249
+ summary.locations = locations;
6250
+ return summary;
6232
6251
  }
6233
6252
  function slimEngineSummaryForHub(engineSummary) {
6234
6253
  if (!engineSummary) return void 0;
@@ -6252,9 +6271,43 @@ function slimReportMetricsForHub(reportMetrics) {
6252
6271
  engine_summary: slimEngineSummaryForHub(engine_summary)
6253
6272
  };
6254
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
+ }
6255
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
+ }
6256
6308
  return {
6257
6309
  source: "runsec_mcp",
6310
+ transport: "full",
6258
6311
  standard: result.standard,
6259
6312
  workspace_path: workspacePath,
6260
6313
  verdict,
@@ -6263,7 +6316,7 @@ function slimRunsecJsonForHubUpload(result, reportMetrics, workspacePath, verdic
6263
6316
  complianceASVS,
6264
6317
  report_metrics: slimReportMetricsForHub(reportMetrics),
6265
6318
  findings: result.findings.map(slimFindingForHubUpload),
6266
- findings_suppressed_summary: buildSuppressedSummaryForHub(result.findings_suppressed),
6319
+ findings_suppressed_summary: buildSuppressedSummaryForHub(result.findings_suppressed, true),
6267
6320
  findings_suppressed_count: result.findings_suppressed.length,
6268
6321
  duration_ms: result.duration_ms,
6269
6322
  findings_count: result.findings_count,
@@ -6274,6 +6327,32 @@ function slimRunsecJsonForHubUpload(result, reportMetrics, workspacePath, verdic
6274
6327
  verdict_detail: result.verdict
6275
6328
  };
6276
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
+ }
6277
6356
  function logHubSyncFailure(message, error, targetUrl) {
6278
6357
  if (error != null && targetUrl) {
6279
6358
  dumpHubNetworkError(error, targetUrl);
@@ -6427,39 +6506,44 @@ async function uploadScanResultsToHub(payload, apiKey) {
6427
6506
  console.warn("[runsec] Hub telemetry skipped: API key missing");
6428
6507
  return { success: false, message: "Sync failed: API key missing" };
6429
6508
  }
6430
- const url = resolveHubUploadUrl();
6431
- const payloadBytes = Buffer.byteLength(JSON.stringify(payload), "utf8");
6432
- console.error(`[runsec] Hub telemetry upload \u2192 ${url} (${payloadBytes} bytes)`);
6433
- const attemptUpload = async (targetUrl) => {
6434
- const errors = [];
6435
- const urls = [targetUrl];
6436
- const localhostAlt = localhostAlternateUploadUrl(targetUrl);
6437
- if (localhostAlt && localhostAlt !== targetUrl) urls.push(localhostAlt);
6438
- const dockerAlt = dockerInternalAlternateUploadUrl(targetUrl);
6439
- if (dockerAlt && !urls.includes(dockerAlt)) urls.push(dockerAlt);
6440
- let lastError = new Error("No upload attempts made");
6441
- for (let i = 0; i < urls.length; i++) {
6442
- const tryUrl = urls[i];
6443
- if (i > 0) {
6444
- console.error(`[runsec] Hub telemetry retry \u2192 ${tryUrl}`);
6445
- }
6446
- try {
6447
- return await postHubUpload(tryUrl, trimmedKey, payload);
6448
- } catch (err) {
6449
- lastError = err;
6450
- errors.push(err);
6451
- dumpHubNetworkError(err, tryUrl);
6452
- }
6453
- }
6454
- throw lastError;
6455
- };
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;
6456
6527
  try {
6457
6528
  let response;
6458
- try {
6459
- response = await attemptUpload(url);
6460
- } catch (networkError) {
6461
- const message = formatHubSyncErrorMessage(networkError, url);
6462
- 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);
6463
6547
  return { success: false, message };
6464
6548
  }
6465
6549
  if (!response.ok) {
@@ -6469,11 +6553,11 @@ async function uploadScanResultsToHub(payload, apiKey) {
6469
6553
  const httpMessage = detailSnippet || statusLabel;
6470
6554
  const message = formatHubSyncErrorMessage(
6471
6555
  new Error(httpMessage),
6472
- url,
6556
+ lastTryUrl,
6473
6557
  response,
6474
6558
  detailSnippet
6475
6559
  );
6476
- logHubSyncFailure(message, new Error(httpMessage), url);
6560
+ logHubSyncFailure(message, new Error(httpMessage), lastTryUrl);
6477
6561
  return {
6478
6562
  success: false,
6479
6563
  message
@@ -6492,8 +6576,8 @@ async function uploadScanResultsToHub(payload, apiKey) {
6492
6576
  projectId
6493
6577
  };
6494
6578
  } catch (error) {
6495
- const message = formatHubSyncErrorMessage(error, url);
6496
- logHubSyncFailure(message, error, url);
6579
+ const message = formatHubSyncErrorMessage(error, lastTryUrl);
6580
+ logHubSyncFailure(message, error, lastTryUrl);
6497
6581
  return { success: false, message };
6498
6582
  }
6499
6583
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runsec/mcp",
3
- "version": "1.0.87",
3
+ "version": "1.0.88",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
6
  "package.json",