@runsec/mcp 1.0.78 → 1.0.80

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 +149 -15
  2. package/package.json +4 -2
package/dist/index.js CHANGED
@@ -435,6 +435,26 @@ var LOCKFILE_LAYOUT_RE = /(?:poetry\.lock|package-lock\.json|pnpm-lock\.yaml|yar
435
435
  function isLockfileLayoutArtifactPath(relPath) {
436
436
  return LOCKFILE_LAYOUT_RE.test(relPath.replace(/\\/g, "/"));
437
437
  }
438
+ var DEV_GENERIC_CREDENTIAL_RE = /(?:postgres:postgres|root:root|root:password|admin:admin|user:password|test:test|guest:guest|changeme:changeme)/i;
439
+ var DEV_DB_HOST_RE = /@(?:dev_db|dev-db|localhost|127\.0\.0\.1|0\.0\.0\.0|host\.docker\.internal|\.local\b|docker\.internal)(?::|\/|$)/i;
440
+ var DEV_DATABASE_URI_RE = /(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis):\/\/(?:[^:@\s]+:[^@\s]+@)?(?:dev_db|dev-db|localhost|127\.0\.0\.1|host\.docker\.internal)/i;
441
+ function blobHasDevDatabaseSecret(text) {
442
+ const blob = (text ?? "").trim();
443
+ if (!blob) return false;
444
+ if (DEV_GENERIC_CREDENTIAL_RE.test(blob)) return true;
445
+ if (DEV_DB_HOST_RE.test(blob)) return true;
446
+ if (DEV_DATABASE_URI_RE.test(blob)) return true;
447
+ return false;
448
+ }
449
+ function findingHasDevDatabaseSecret(finding) {
450
+ const parts = [
451
+ finding.match_text ?? "",
452
+ finding.snippet ?? "",
453
+ finding.description ?? "",
454
+ finding.title ?? ""
455
+ ];
456
+ return parts.some((p) => blobHasDevDatabaseSecret(p));
457
+ }
438
458
  function findingBlobHasEnvInterpolation(finding) {
439
459
  const parts = [
440
460
  finding.match_text ?? "",
@@ -458,6 +478,9 @@ function applyNuclearHardDrop(finding, relPath) {
458
478
  if (ENV_INTERP_RE.test(matchText) || ENV_INTERP_RE.test(snippet) || findingBlobHasEnvInterpolation(finding)) {
459
479
  return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "env_variable_interpolation" };
460
480
  }
481
+ if (findingHasDevDatabaseSecret(finding)) {
482
+ return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "dev_database_placeholder" };
483
+ }
461
484
  if (!findingIsVerified(finding) && (LOCKFILE_LAYOUT_RE.test(relPath) || isLockfileOrModulesPath(relPath) || isLockfileLayoutArtifactPath(relPath))) {
462
485
  return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "lockfile_or_layout_artifact" };
463
486
  }
@@ -496,7 +519,8 @@ function isCustomRegexFinding(finding) {
496
519
  }
497
520
  function looksLikeHighEntropyRawToken(finding) {
498
521
  const blob = `${finding.match_text ?? ""} ${finding.snippet ?? ""}`.trim();
499
- if (blob.length < 24 || ENV_INTERP_RE.test(blob)) return false;
522
+ if (blob.length < 24 || ENV_INTERP_RE.test(blob) || findingHasDevDatabaseSecret(finding)) return false;
523
+ if (/^(?:postgres(?:ql)?|mysql|mongodb|redis):\/\//i.test(blob)) return false;
500
524
  if (/^(?:ghp_|gho_|github_pat_|glpat-|sk-[a-zA-Z0-9]{10,}|AKIA[0-9A-Z]{16}|xox[baprs]-|eyJ[A-Za-z0-9_-]{10,}\.)/i.test(
501
525
  blob
502
526
  )) {
@@ -1821,6 +1845,7 @@ function mapTrufflehogFindings(rows, workspaceRoot) {
1821
1845
  if (isLockfileOrModulesPath(rel)) continue;
1822
1846
  const blob = `${display} ${rawSecret} ${description}`;
1823
1847
  if (hasEnvironmentInterpolation(blob)) continue;
1848
+ if (blobHasDevDatabaseSecret(blob)) continue;
1824
1849
  if (detector.toLowerCase() === "customregex") continue;
1825
1850
  }
1826
1851
  const severity = severityForSecret(detector, verified);
@@ -3093,7 +3118,7 @@ async function runUnifiedScanPipeline(opts) {
3093
3118
  `[runsec] unified scan: engines=${concurrent_duration_ms}ms merge+cognitive=${merge_duration_ms}ms | raw=${allRaw.length} primary=${findings.length} suppressed_cognitive=${findings_suppressed.length}`
3094
3119
  );
3095
3120
  console.error(
3096
- `[runsec] X-RunSec-Verdict: ${cognitiveResult.verdict.http_headers["X-RunSec-Verdict"]} (blocking=${cognitiveResult.verdict.blocking_findings_count})`
3121
+ `[runsec] @runsec/mcp cognitive complete | X-RunSec-Verdict: ${cognitiveResult.verdict.http_headers["X-RunSec-Verdict"]} | primary=${findings.length} suppressed=${findings_suppressed.length} blocking=${cognitiveResult.verdict.blocking_findings_count}`
3097
3122
  );
3098
3123
  return {
3099
3124
  standard,
@@ -5823,8 +5848,10 @@ function isRemediationTool(name) {
5823
5848
  }
5824
5849
 
5825
5850
  // src/hubUploadUrl.ts
5851
+ var import_node_fs17 = __toESM(require("fs"));
5826
5852
  var HUB_UPLOAD_REPORT_PATH = "/api/mcp/upload-report";
5827
5853
  var DEFAULT_PRODUCTION_HUB_ORIGIN = "https://runsec.io";
5854
+ var DEFAULT_DOCKER_INTERNAL_HUB_ORIGIN = "http://frontend:3000";
5828
5855
  var LEGACY_TELEMETRY_INGEST_PATH = "/api/v1/telemetry/ingest";
5829
5856
  function stripTrailingSlash(url) {
5830
5857
  return url.replace(/\/$/, "");
@@ -5856,11 +5883,24 @@ function colocatedHubOrigin() {
5856
5883
  if (!port || !/^\d+$/.test(port)) return null;
5857
5884
  return `http://127.0.0.1:${port}`;
5858
5885
  }
5886
+ function isDockerRuntime() {
5887
+ try {
5888
+ return import_node_fs17.default.existsSync("/.dockerenv");
5889
+ } catch {
5890
+ return false;
5891
+ }
5892
+ }
5893
+ function resolveDockerInternalHubOrigin() {
5894
+ return stripTrailingSlash(
5895
+ process.env.RUNSEC_HUB_INTERNAL_URL?.trim() || process.env.FRONTEND_INTERNAL_URL?.trim() || DEFAULT_DOCKER_INTERNAL_HUB_ORIGIN
5896
+ );
5897
+ }
5859
5898
  function resolveHubBaseUrl() {
5860
5899
  const candidates = [
5861
5900
  process.env.RUNSEC_HUB_URL,
5862
5901
  process.env.RUNSEC_API_URL,
5863
5902
  process.env.RUNSEC_HUB_ORIGIN,
5903
+ process.env.RUNSEC_HUB_INTERNAL_URL,
5864
5904
  process.env.NEXT_PUBLIC_APP_URL,
5865
5905
  process.env.PUBLIC_APP_URL,
5866
5906
  process.env.NEXTAUTH_URL,
@@ -5871,6 +5911,13 @@ function resolveHubBaseUrl() {
5871
5911
  if (candidates.length > 0) {
5872
5912
  return stripTrailingSlash(migrateLegacyIngestUrl(candidates[0]));
5873
5913
  }
5914
+ if (isDockerRuntime()) {
5915
+ const internal = resolveDockerInternalHubOrigin();
5916
+ console.error(
5917
+ `[runsec] Docker runtime \u2014 Hub upload via internal origin ${internal} (set RUNSEC_HUB_URL to override)`
5918
+ );
5919
+ return internal;
5920
+ }
5874
5921
  if (isProductionRuntime()) {
5875
5922
  const colocated = colocatedHubOrigin();
5876
5923
  if (colocated) {
@@ -5910,6 +5957,24 @@ function localhostAlternateUploadUrl(url) {
5910
5957
  return null;
5911
5958
  }
5912
5959
  }
5960
+ function dockerInternalAlternateUploadUrl(url) {
5961
+ if (!isDockerRuntime()) return null;
5962
+ try {
5963
+ const parsed = new URL(url);
5964
+ const internal = new URL(resolveDockerInternalHubOrigin());
5965
+ if (parsed.origin === internal.origin) return null;
5966
+ internal.pathname = HUB_UPLOAD_REPORT_PATH;
5967
+ internal.search = "";
5968
+ internal.hash = "";
5969
+ return internal.toString();
5970
+ } catch {
5971
+ return appendUploadPath(resolveDockerInternalHubOrigin());
5972
+ }
5973
+ }
5974
+
5975
+ // src/telemetryClient.ts
5976
+ var import_node_http = __toESM(require("http"));
5977
+ var import_node_https = __toESM(require("https"));
5913
5978
 
5914
5979
  // src/complianceScores.ts
5915
5980
  var SEVERITY_PENALTY = {
@@ -6019,14 +6084,70 @@ function logHubSyncFailure(message, error, targetUrl) {
6019
6084
  }
6020
6085
  console.error(`[runsec] Hub telemetry upload error (scan saved locally): ${message}`);
6021
6086
  }
6022
- async function postHubUpload(url, apiKey, payload) {
6023
- return await fetch(url, {
6024
- method: "POST",
6025
- headers: hubAuthHeaders(apiKey),
6026
- body: JSON.stringify(payload),
6027
- signal: AbortSignal.timeout(HUB_UPLOAD_TIMEOUT_MS)
6087
+ function postHubUploadNodeHttp(url, apiKey, payload) {
6088
+ return new Promise((resolve, reject) => {
6089
+ const body = JSON.stringify(payload);
6090
+ let parsed;
6091
+ try {
6092
+ parsed = new URL(url);
6093
+ } catch (err) {
6094
+ reject(err);
6095
+ return;
6096
+ }
6097
+ const headers = {
6098
+ ...hubAuthHeaders(apiKey),
6099
+ "Content-Length": String(Buffer.byteLength(body))
6100
+ };
6101
+ const lib = parsed.protocol === "https:" ? import_node_https.default : import_node_http.default;
6102
+ const port = parsed.port !== "" ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
6103
+ const req = lib.request(
6104
+ {
6105
+ hostname: parsed.hostname,
6106
+ port,
6107
+ path: `${parsed.pathname}${parsed.search}`,
6108
+ method: "POST",
6109
+ headers,
6110
+ timeout: HUB_UPLOAD_TIMEOUT_MS
6111
+ },
6112
+ (res) => {
6113
+ const chunks = [];
6114
+ res.on("data", (chunk) => chunks.push(chunk));
6115
+ res.on("end", () => {
6116
+ const text = Buffer.concat(chunks).toString("utf8");
6117
+ const status = res.statusCode ?? 0;
6118
+ resolve({
6119
+ ok: status >= 200 && status < 300,
6120
+ status,
6121
+ statusText: res.statusMessage ?? "",
6122
+ text: async () => text,
6123
+ json: async () => JSON.parse(text || "{}")
6124
+ });
6125
+ });
6126
+ }
6127
+ );
6128
+ req.on("error", reject);
6129
+ req.on("timeout", () => {
6130
+ req.destroy();
6131
+ reject(Object.assign(new Error("Hub upload request timeout"), { code: "ETIMEDOUT" }));
6132
+ });
6133
+ req.write(body);
6134
+ req.end();
6028
6135
  });
6029
6136
  }
6137
+ async function postHubUpload(url, apiKey, payload) {
6138
+ try {
6139
+ return await fetch(url, {
6140
+ method: "POST",
6141
+ headers: hubAuthHeaders(apiKey),
6142
+ body: JSON.stringify(payload),
6143
+ signal: AbortSignal.timeout(HUB_UPLOAD_TIMEOUT_MS)
6144
+ });
6145
+ } catch (fetchError) {
6146
+ console.error("[runsec] fetch() failed \u2014 retrying Hub upload via node:http(s)");
6147
+ dumpHubNetworkError(fetchError, url);
6148
+ return postHubUploadNodeHttp(url, apiKey, payload);
6149
+ }
6150
+ }
6030
6151
  function countSeverityMetrics(findings) {
6031
6152
  const metrics = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
6032
6153
  for (const f of findings) {
@@ -6085,14 +6206,27 @@ async function uploadScanResultsToHub(payload, apiKey) {
6085
6206
  const url = resolveHubUploadUrl();
6086
6207
  console.error(`[runsec] Hub telemetry upload \u2192 ${url}`);
6087
6208
  const attemptUpload = async (targetUrl) => {
6088
- try {
6089
- return await postHubUpload(targetUrl, trimmedKey, payload);
6090
- } catch (firstError) {
6091
- const alt = localhostAlternateUploadUrl(targetUrl);
6092
- if (!alt || alt === targetUrl) throw firstError;
6093
- console.error(`[runsec] Hub telemetry retry \u2192 ${alt}`);
6094
- return await postHubUpload(alt, trimmedKey, payload);
6209
+ const errors = [];
6210
+ const urls = [targetUrl];
6211
+ const localhostAlt = localhostAlternateUploadUrl(targetUrl);
6212
+ if (localhostAlt && localhostAlt !== targetUrl) urls.push(localhostAlt);
6213
+ const dockerAlt = dockerInternalAlternateUploadUrl(targetUrl);
6214
+ if (dockerAlt && !urls.includes(dockerAlt)) urls.push(dockerAlt);
6215
+ let lastError = new Error("No upload attempts made");
6216
+ for (let i = 0; i < urls.length; i++) {
6217
+ const tryUrl = urls[i];
6218
+ if (i > 0) {
6219
+ console.error(`[runsec] Hub telemetry retry \u2192 ${tryUrl}`);
6220
+ }
6221
+ try {
6222
+ return await postHubUpload(tryUrl, trimmedKey, payload);
6223
+ } catch (err) {
6224
+ lastError = err;
6225
+ errors.push(err);
6226
+ dumpHubNetworkError(err, tryUrl);
6227
+ }
6095
6228
  }
6229
+ throw lastError;
6096
6230
  };
6097
6231
  try {
6098
6232
  let response;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runsec/mcp",
3
- "version": "1.0.78",
3
+ "version": "1.0.80",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
6
  "dist",
@@ -30,7 +30,9 @@
30
30
  "test": "vitest run",
31
31
  "test:gold": "tsx scripts/qaValidationReport.ts",
32
32
  "test:gold:parse": "tsx scripts/qaValidationReport.ts --parse-only",
33
- "simulate:output": "tsx scripts/simulate_output.ts"
33
+ "simulate:output": "tsx scripts/simulate_output.ts",
34
+ "debug:pipeline": "tsx scripts/debug-pipeline.ts",
35
+ "test:e2e": "npm run build && tsx scripts/debug-pipeline.ts"
34
36
  },
35
37
  "keywords": [
36
38
  "mcp",