@runsec/mcp 1.0.73 → 1.0.75

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.
@@ -353,7 +353,7 @@ detectors:
353
353
  - email
354
354
  - e-mail
355
355
  regex:
356
- pattern: '(?i)(?:email|e-mail)\s*[:=]\s*[\x27"]?(?!.*@(example\.(com|org|net)|test\.|mock\.|localhost|invalid|\.test|\.example|noreply\.|no-reply\.|fixture\.|sample\.|dummy\.|placeholder\.))[a-z0-9._%+-]+@[a-z0-9][a-z0-9.-]*\.[a-z]{2,}[\x27"]?'
356
+ pattern: '(?i)(?:email|e-mail)\s*[:=]\s*[\x27"]?[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}[\x27"]?'
357
357
 
358
358
  - name: PII Phone RU
359
359
  keywords:
package/dist/index.js CHANGED
@@ -413,7 +413,8 @@ var import_node_fs3 = __toESM(require("fs"));
413
413
  var import_node_path3 = __toESM(require("path"));
414
414
 
415
415
  // src/engine/secretHeuristics.ts
416
- var ENV_INTERP_RE = /\$\{[A-Z0-9_]+\}|\$[A-Z][A-Z0-9_]{2,}|%\([A-Za-z0-9_.]+\)s|process\.env\.|os\.getenv\(|getenv\(|environ\[/i;
416
+ var STRICT_ENV_INTERP_RE = /(?:\$\{[A-Z0-9_]+\}|\$[A-Z][A-Z0-9_]{2,}|%\([A-Za-z0-9_.]+\)s|process\.env\.|os\.getenv\(|getenv\(|environ\[)/i;
417
+ var ENV_INTERP_RE = STRICT_ENV_INTERP_RE;
417
418
  var LOCKFILE_BASENAMES = /^(?:poetry\.lock|package-lock\.json|pnpm-lock\.yaml|yarn\.lock|cargo\.lock|composer\.lock|gemfile\.lock)$/i;
418
419
  function hasEnvironmentInterpolation(text) {
419
420
  return ENV_INTERP_RE.test(text);
@@ -431,8 +432,37 @@ function isLockfileOrModulesPath(relPath) {
431
432
  function isTrufflehogVerified(verifiedFlag, description) {
432
433
  return verifiedFlag || /\(verified\)/i.test(description);
433
434
  }
435
+ function isStaticLayoutDumpPath(relPath) {
436
+ const normalized = relPath.replace(/\\/g, "/").toLowerCase();
437
+ const base = normalized.split("/").pop() ?? normalized;
438
+ if (base.endsWith(".storyboard") || base.endsWith(".xib") || base.endsWith(".docx")) return true;
439
+ if (base.endsWith(".lock") || /\.lock$/i.test(base)) return true;
440
+ return false;
441
+ }
442
+ function matchTextHasStrictEnvInterpolation(matchText, snippet) {
443
+ const m = (matchText ?? "").trim();
444
+ const s = (snippet ?? "").trim();
445
+ if (m && STRICT_ENV_INTERP_RE.test(m)) return true;
446
+ if (s && STRICT_ENV_INTERP_RE.test(s)) return true;
447
+ return false;
448
+ }
434
449
 
435
450
  // src/engine/cognitiveEngine.ts
451
+ var ABSOLUTE_ENV_INTERP_CAP = 0.05;
452
+ var ABSOLUTE_STATIC_DUMP_CAP = 0.01;
453
+ function resolveAbsoluteSuppression(finding, relPath) {
454
+ if (matchTextHasStrictEnvInterpolation(finding.match_text ?? "", finding.snippet ?? "")) {
455
+ return { cap: ABSOLUTE_ENV_INTERP_CAP, reason: "environment_variable_interpolation" };
456
+ }
457
+ const verified = isTrufflehogVerified(
458
+ /\(verified\)/i.test(finding.description) || /\(verified\)/i.test(finding.match_text ?? ""),
459
+ finding.description
460
+ );
461
+ if (!verified && isStaticLayoutDumpPath(relPath)) {
462
+ return { cap: ABSOLUTE_STATIC_DUMP_CAP, reason: "static_layout_or_lockfile_unverified" };
463
+ }
464
+ return null;
465
+ }
436
466
  var PRIMARY_LOG_THRESHOLD = 0.8;
437
467
  var CONFIDENCE_THRESHOLD = PRIMARY_LOG_THRESHOLD;
438
468
  var ELITE_LOW_CONFIDENCE_CAP = 0.28;
@@ -823,6 +853,10 @@ function attackPathConcrete(finding, confidence, eliteHard, allReasons, snippet)
823
853
  return confidence >= PRIMARY_LOG_THRESHOLD;
824
854
  }
825
855
  function baseConfidenceForFinding(finding, phase1, relPath, category, repoRoot) {
856
+ const absolute = resolveAbsoluteSuppression(finding, relPath);
857
+ if (absolute) {
858
+ return [absolute.cap, [absolute.reason]];
859
+ }
826
860
  const reasons = [];
827
861
  let score = category === "secrets" ? 0.9 : category === "dependencies" ? 0.78 : 0.82;
828
862
  const title = finding.description;
@@ -842,8 +876,8 @@ function baseConfidenceForFinding(finding, phase1, relPath, category, repoRoot)
842
876
  if (isUnverifiedTrufflehogSecret(finding)) {
843
877
  const secretBlob = `${finding.match_text} ${finding.snippet ?? ""}`;
844
878
  if (hasEnvironmentInterpolation(secretBlob)) {
845
- score = Math.min(score, 0.15);
846
- reasons.push("environment_interpolation_placeholder");
879
+ score = Math.min(score, ABSOLUTE_ENV_INTERP_CAP);
880
+ reasons.push("environment_variable_interpolation");
847
881
  }
848
882
  if (isLockfileOrModulesPath(relPath)) {
849
883
  score = Math.min(score, 0.1);
@@ -923,6 +957,7 @@ function downgradeSeverity(severity, cap) {
923
957
  function enrichAuditFinding(repoRoot, finding, opts) {
924
958
  const applyFp = opts?.applyFalsePositiveFilter ?? !isCalibrationTestbedPath(finding.file_path);
925
959
  const relPath = finding.file_path.replace(/\\/g, "/");
960
+ const absoluteSuppress = resolveAbsoluteSuppression(finding, relPath);
926
961
  const phase1 = phase1ContextResearch(repoRoot, relPath);
927
962
  let [conf, reasons] = baseConfidenceForFinding(finding, phase1, relPath, finding.category, repoRoot);
928
963
  const [boost, boostReason] = comparativeAnalysisBoost(repoRoot, relPath, applyFp);
@@ -947,11 +982,18 @@ function enrichAuditFinding(repoRoot, finding, opts) {
947
982
  }
948
983
  }
949
984
  }
950
- conf = Math.max(0.05, Math.min(1, conf));
985
+ if (absoluteSuppress) {
986
+ conf = Math.min(conf, absoluteSuppress.cap);
987
+ if (!reasons.includes(absoluteSuppress.reason)) {
988
+ reasons.push(absoluteSuppress.reason);
989
+ }
990
+ } else {
991
+ conf = Math.max(0.05, Math.min(1, conf));
992
+ }
951
993
  const snippetL = snippetLower(finding);
952
994
  const attackConcrete = attackPathConcrete(finding, conf, eliteHard, reasons, snippetL);
953
995
  const critique = selfCritique(finding, conf, phase1);
954
- const primaryOk = conf >= PRIMARY_LOG_THRESHOLD && !hardExcludeFromPrimaryLog(relPath, finding.description, conf, eliteHard);
996
+ const primaryOk = !absoluteSuppress && conf >= PRIMARY_LOG_THRESHOLD && !hardExcludeFromPrimaryLog(relPath, finding.description, conf, eliteHard);
955
997
  let severity = finding.severity;
956
998
  const originalSeverity = finding.original_severity ?? finding.severity;
957
999
  if (reasons.includes("phase1_protection_lib_present") && conf <= 0.25) {
@@ -959,6 +1001,7 @@ function enrichAuditFinding(repoRoot, finding, opts) {
959
1001
  } else if (!primaryOk && conf < PRIMARY_LOG_THRESHOLD) {
960
1002
  severity = downgradeSeverity(severity, critique.suggested_severity_cap);
961
1003
  }
1004
+ const forceSuppressed = Boolean(absoluteSuppress);
962
1005
  return {
963
1006
  ...finding,
964
1007
  severity,
@@ -966,6 +1009,10 @@ function enrichAuditFinding(repoRoot, finding, opts) {
966
1009
  confidence_score: Math.round(conf * 1e3) / 1e3,
967
1010
  confidence_reasons: reasons,
968
1011
  primary_log_eligible: primaryOk,
1012
+ ...forceSuppressed ? {
1013
+ suppressed: true,
1014
+ suppression_reason: absoluteSuppress.reason
1015
+ } : {},
969
1016
  attack_path_concrete: attackConcrete,
970
1017
  cognitive: {
971
1018
  phase1_context_research: phase1,
@@ -1063,8 +1110,8 @@ function buildVerdict(primaryFindings) {
1063
1110
  function applyCognitivePipeline(workspaceRoot, findings) {
1064
1111
  const enriched = findings.map((f) => enrichAuditFinding(workspaceRoot, f));
1065
1112
  const { kept, duplicates } = deduplicateSameLineFindings(enriched);
1066
- const primary = kept.filter((f) => f.primary_log_eligible);
1067
- const suppressed = kept.filter((f) => !f.primary_log_eligible).concat(duplicates);
1113
+ const primary = kept.filter((f) => f.primary_log_eligible && !f.suppressed);
1114
+ const suppressed = kept.filter((f) => !f.primary_log_eligible || f.suppressed).concat(duplicates);
1068
1115
  return {
1069
1116
  primary,
1070
1117
  suppressed,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runsec/mcp",
3
- "version": "1.0.73",
3
+ "version": "1.0.75",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
6
  "dist",