@runsec/mcp 1.0.76 → 1.0.77

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 +79 -18
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5775,6 +5775,45 @@ function isRemediationTool(name) {
5775
5775
  return REMEDIATION_TOOL_NAMES.includes(name);
5776
5776
  }
5777
5777
 
5778
+ // src/hubUploadUrl.ts
5779
+ var HUB_UPLOAD_REPORT_PATH = "/api/mcp/upload-report";
5780
+ var LEGACY_TELEMETRY_INGEST_PATH = "/api/v1/telemetry/ingest";
5781
+ function stripTrailingSlash(url) {
5782
+ return url.replace(/\/$/, "");
5783
+ }
5784
+ function migrateLegacyIngestUrl(url) {
5785
+ if (!url.includes(LEGACY_TELEMETRY_INGEST_PATH)) return url;
5786
+ console.warn(
5787
+ `[runsec] Legacy telemetry ingest URL detected (${LEGACY_TELEMETRY_INGEST_PATH}); using ${HUB_UPLOAD_REPORT_PATH} instead`
5788
+ );
5789
+ return url.replace(LEGACY_TELEMETRY_INGEST_PATH, HUB_UPLOAD_REPORT_PATH);
5790
+ }
5791
+ function resolveHubUploadUrl() {
5792
+ const explicit = [
5793
+ process.env.RUNSEC_HUB_INGEST_URL,
5794
+ process.env.RUNSEC_HUB_UPLOAD_URL,
5795
+ process.env.RUNSEC_TELEMETRY_URL
5796
+ ].map((v) => v?.trim()).find(Boolean);
5797
+ if (explicit) {
5798
+ const migrated = migrateLegacyIngestUrl(explicit);
5799
+ if (migrated.includes(HUB_UPLOAD_REPORT_PATH)) {
5800
+ return stripTrailingSlash(migrated);
5801
+ }
5802
+ if (/^https?:\/\//i.test(migrated)) {
5803
+ return `${stripTrailingSlash(migrated)}${HUB_UPLOAD_REPORT_PATH}`;
5804
+ }
5805
+ return stripTrailingSlash(migrated);
5806
+ }
5807
+ const base = stripTrailingSlash(
5808
+ process.env.RUNSEC_HUB_URL?.trim() || process.env.RUNSEC_API_URL?.trim() || "https://runsec.io"
5809
+ );
5810
+ const migratedBase = migrateLegacyIngestUrl(base);
5811
+ if (migratedBase.includes(HUB_UPLOAD_REPORT_PATH)) {
5812
+ return migratedBase;
5813
+ }
5814
+ return `${migratedBase}${HUB_UPLOAD_REPORT_PATH}`;
5815
+ }
5816
+
5778
5817
  // src/complianceScores.ts
5779
5818
  var SEVERITY_PENALTY = {
5780
5819
  CRITICAL: 15,
@@ -5817,6 +5856,26 @@ function buildHubComplianceBlock(result) {
5817
5856
  }
5818
5857
 
5819
5858
  // src/telemetryClient.ts
5859
+ var HUB_UPLOAD_TIMEOUT_MS = 12e4;
5860
+ function hubAuthHeaders(apiKey) {
5861
+ return {
5862
+ Authorization: `Bearer ${apiKey}`,
5863
+ // Cloudflare/tunnel may strip Authorization on POST; Hub accepts this duplicate.
5864
+ "X-RunSec-Api-Key": apiKey,
5865
+ "Content-Type": "application/json",
5866
+ Accept: "application/json"
5867
+ };
5868
+ }
5869
+ function formatHubSyncErrorMessage(error, targetUrl, response) {
5870
+ const status = response?.status != null ? String(response.status) : error?.response?.status != null ? String(error.response.status) : "n/a";
5871
+ const err = error instanceof Error ? error : new Error(String(error));
5872
+ const cause = err.cause instanceof Error ? err.cause.message : err.cause != null ? String(err.cause) : "";
5873
+ let message = `Sync failed: ${err.message}. Target URL: ${targetUrl}. Status: ${status}`;
5874
+ if (cause && !message.includes(cause)) {
5875
+ message += `. Cause: ${cause}`;
5876
+ }
5877
+ return message;
5878
+ }
5820
5879
  function countSeverityMetrics(findings) {
5821
5880
  const metrics = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
5822
5881
  for (const f of findings) {
@@ -5872,30 +5931,29 @@ async function uploadScanResultsToHub(payload, apiKey) {
5872
5931
  console.warn("[runsec] Hub telemetry skipped: API key missing");
5873
5932
  return { success: false, message: "Sync failed: API key missing" };
5874
5933
  }
5875
- const baseUrl = (process.env.RUNSEC_API_URL || "https://runsec.io").replace(/\/$/, "");
5876
- const url = `${baseUrl}/api/mcp/upload-report`;
5934
+ const url = resolveHubUploadUrl();
5935
+ console.error(`[runsec] Hub telemetry upload \u2192 ${url}`);
5877
5936
  try {
5878
5937
  const response = await fetch(url, {
5879
5938
  method: "POST",
5880
- headers: {
5881
- Authorization: `Bearer ${trimmedKey}`,
5882
- // Cloudflare/tunnel may strip Authorization on POST; Hub accepts this duplicate.
5883
- "X-RunSec-Api-Key": trimmedKey,
5884
- "Content-Type": "application/json",
5885
- Accept: "application/json"
5886
- },
5887
- body: JSON.stringify(payload)
5939
+ headers: hubAuthHeaders(trimmedKey),
5940
+ body: JSON.stringify(payload),
5941
+ signal: AbortSignal.timeout(HUB_UPLOAD_TIMEOUT_MS)
5888
5942
  });
5889
5943
  if (!response.ok) {
5890
5944
  const detail = await response.text().catch(() => "");
5891
5945
  const detailSnippet = detail.slice(0, 500).trim();
5892
5946
  const statusLabel = response.statusText || String(response.status);
5893
- console.warn(
5894
- `[runsec] Hub telemetry upload failed (${response.status}): ${detailSnippet || statusLabel}`
5947
+ const httpMessage = detailSnippet || statusLabel;
5948
+ const message = formatHubSyncErrorMessage(
5949
+ new Error(httpMessage),
5950
+ url,
5951
+ response
5895
5952
  );
5953
+ console.warn(`[runsec] Hub telemetry upload failed: ${message}`);
5896
5954
  return {
5897
5955
  success: false,
5898
- message: `Sync failed: ${detailSnippet || statusLabel}`
5956
+ message
5899
5957
  };
5900
5958
  }
5901
5959
  const body = await response.json().catch(() => ({}));
@@ -5911,9 +5969,9 @@ async function uploadScanResultsToHub(payload, apiKey) {
5911
5969
  projectId
5912
5970
  };
5913
5971
  } catch (error) {
5914
- const message = error instanceof Error ? error.message : String(error);
5972
+ const message = formatHubSyncErrorMessage(error, url);
5915
5973
  console.warn(`[runsec] Hub telemetry upload error (scan saved locally): ${message}`);
5916
- return { success: false, message: `Sync failed: ${message}` };
5974
+ return { success: false, message };
5917
5975
  }
5918
5976
  }
5919
5977
 
@@ -5955,10 +6013,13 @@ function appendCloudSyncToDirective(directive, syncResult) {
5955
6013
  ${lines}`;
5956
6014
  }
5957
6015
  async function verifyApiKey(apiKey) {
5958
- const baseUrl = (process.env.RUNSEC_API_URL || "https://runsec.io").replace(/\/$/, "");
6016
+ const verifyUrl = resolveHubUploadUrl().replace(/\/upload-report\/?$/, "/verify-key");
5959
6017
  try {
5960
- const response = await fetch(`${baseUrl}/api/mcp/verify-key`, {
5961
- headers: { Authorization: `Bearer ${apiKey}` }
6018
+ const response = await fetch(verifyUrl, {
6019
+ headers: {
6020
+ Authorization: `Bearer ${apiKey}`,
6021
+ "X-RunSec-Api-Key": apiKey
6022
+ }
5962
6023
  });
5963
6024
  if (response.status === 401 || response.status === 403) {
5964
6025
  console.error("\u274C FATAL: Invalid RunSec API Key.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runsec/mcp",
3
- "version": "1.0.76",
3
+ "version": "1.0.77",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
6
  "dist",