@runsec/mcp 1.0.80 → 1.0.82

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 +182 -119
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -435,6 +435,25 @@ 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
+ function isStaticLayoutDumpPath(relPath) {
439
+ return isLockfileLayoutArtifactPath(relPath);
440
+ }
441
+ function isHexChecksumBlob(text) {
442
+ const compact = (text ?? "").replace(/\s+/g, "").trim();
443
+ if (compact.length < 40 || compact.length > 128) return false;
444
+ return /^[0-9a-f]+$/i.test(compact);
445
+ }
446
+ var UNVERIFIED_NOISE_DETECTORS = /* @__PURE__ */ new Set([
447
+ "customregex",
448
+ "sentrytoken",
449
+ "hashicorpvault"
450
+ ]);
451
+ function isUnverifiedTrufflehogNoiseDetector(detectorName) {
452
+ const d = detectorName.trim().toLowerCase().replace(/\s+/g, "");
453
+ if (UNVERIFIED_NOISE_DETECTORS.has(d)) return true;
454
+ if (d === "sentry" || d === "sentrytoken") return true;
455
+ return false;
456
+ }
438
457
  var DEV_GENERIC_CREDENTIAL_RE = /(?:postgres:postgres|root:root|root:password|admin:admin|user:password|test:test|guest:guest|changeme:changeme)/i;
439
458
  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
459
  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;
@@ -481,9 +500,17 @@ function applyNuclearHardDrop(finding, relPath) {
481
500
  if (findingHasDevDatabaseSecret(finding)) {
482
501
  return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "dev_database_placeholder" };
483
502
  }
484
- if (!findingIsVerified(finding) && (LOCKFILE_LAYOUT_RE.test(relPath) || isLockfileOrModulesPath(relPath) || isLockfileLayoutArtifactPath(relPath))) {
503
+ const secretBlob = `${matchText} ${snippet}`.trim();
504
+ if (!findingIsVerified(finding) && isHexChecksumBlob(secretBlob)) {
505
+ return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "package_checksum_hex" };
506
+ }
507
+ if (!findingIsVerified(finding) && (LOCKFILE_LAYOUT_RE.test(relPath) || isLockfileOrModulesPath(relPath) || isLockfileLayoutArtifactPath(relPath) || isStaticLayoutDumpPath(relPath))) {
485
508
  return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "lockfile_or_layout_artifact" };
486
509
  }
510
+ const detector = String(finding.detector_name ?? "").trim();
511
+ if (!findingIsVerified(finding) && isUnverifiedTrufflehogNoiseDetector(detector)) {
512
+ return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "unverified_trufflehog_noise_detector" };
513
+ }
487
514
  if (isCustomRegexFinding(finding) && !findingIsVerified(finding)) {
488
515
  return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "customregex_unverified" };
489
516
  }
@@ -527,7 +554,10 @@ function looksLikeHighEntropyRawToken(finding) {
527
554
  return true;
528
555
  }
529
556
  const compact = blob.replace(/\s+/g, "");
530
- if (compact.length >= 40 && /^[A-Za-z0-9+/=_-]+$/.test(compact)) return true;
557
+ if (isHexChecksumBlob(compact)) return false;
558
+ if (compact.length >= 40 && /^[A-Za-z0-9+/=_-]+$/.test(compact) && /[A-Z]/.test(compact) && /[a-z]/.test(compact)) {
559
+ return true;
560
+ }
531
561
  return false;
532
562
  }
533
563
  function customRegexNeedsBaseClamp(finding) {
@@ -1842,11 +1872,12 @@ function mapTrufflehogFindings(rows, workspaceRoot) {
1842
1872
  const display = redacted || rawSecret || "[secret redacted]";
1843
1873
  const description = `TruffleHog: exposed ${detector}${verified ? " (verified)" : ""}`;
1844
1874
  if (!isTrufflehogVerified(verified, description)) {
1845
- if (isLockfileOrModulesPath(rel)) continue;
1846
1875
  const blob = `${display} ${rawSecret} ${description}`;
1876
+ if (isLockfileOrModulesPath(rel) || isStaticLayoutDumpPath(rel)) continue;
1847
1877
  if (hasEnvironmentInterpolation(blob)) continue;
1848
1878
  if (blobHasDevDatabaseSecret(blob)) continue;
1849
- if (detector.toLowerCase() === "customregex") continue;
1879
+ if (isHexChecksumBlob(display) || isHexChecksumBlob(rawSecret)) continue;
1880
+ if (isUnverifiedTrufflehogNoiseDetector(detector)) continue;
1850
1881
  }
1851
1882
  const severity = severityForSecret(detector, verified);
1852
1883
  findings.push({
@@ -3287,8 +3318,29 @@ async function executeAudit(toolName, args) {
3287
3318
  }
3288
3319
 
3289
3320
  // src/engine/reportFormatter.ts
3321
+ var import_node_fs10 = __toESM(require("fs"));
3322
+ var import_node_path16 = __toESM(require("path"));
3323
+
3324
+ // src/version.ts
3290
3325
  var import_node_fs9 = __toESM(require("fs"));
3291
3326
  var import_node_path15 = __toESM(require("path"));
3327
+ function loadVersion() {
3328
+ const candidates = [
3329
+ import_node_path15.default.join(__dirname, "..", "package.json"),
3330
+ import_node_path15.default.join(__dirname, "package.json")
3331
+ ];
3332
+ for (const candidate of candidates) {
3333
+ try {
3334
+ const raw = JSON.parse(import_node_fs9.default.readFileSync(candidate, "utf8"));
3335
+ if (raw.version?.trim()) return raw.version.trim();
3336
+ } catch {
3337
+ }
3338
+ }
3339
+ return "unknown";
3340
+ }
3341
+ var RUNSEC_MCP_VERSION = loadVersion();
3342
+
3343
+ // src/engine/reportFormatter.ts
3292
3344
  var REPORT_SECTION_TITLES = {
3293
3345
  code: "Code Vulnerabilities",
3294
3346
  secrets: "Exposed Secrets",
@@ -3381,7 +3433,7 @@ function shortRuleLabel(ruleId) {
3381
3433
  return parts[parts.length - 1] || ruleId;
3382
3434
  }
3383
3435
  function snippetLanguage(filePath) {
3384
- const ext = import_node_path15.default.extname(filePath).replace(/^\./, "").toLowerCase();
3436
+ const ext = import_node_path16.default.extname(filePath).replace(/^\./, "").toLowerCase();
3385
3437
  const map = {
3386
3438
  yml: "yaml",
3387
3439
  yaml: "yaml",
@@ -3483,9 +3535,9 @@ var TECH_STACK_BY_EXT = {
3483
3535
  };
3484
3536
  function techStackFromFilePath(filePath) {
3485
3537
  const normalized = String(filePath ?? "").replace(/\\/g, "/");
3486
- const base = import_node_path15.default.basename(normalized).toLowerCase();
3538
+ const base = import_node_path16.default.basename(normalized).toLowerCase();
3487
3539
  if (base === "dockerfile" || base.startsWith("dockerfile.")) return "Docker";
3488
- const ext = import_node_path15.default.extname(normalized).toLowerCase();
3540
+ const ext = import_node_path16.default.extname(normalized).toLowerCase();
3489
3541
  return TECH_STACK_BY_EXT[ext] ?? "Other";
3490
3542
  }
3491
3543
  function countFindingsByTechStack(findings) {
@@ -3679,6 +3731,7 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
3679
3731
  out.push(`# RunSec Unified Security Report`);
3680
3732
  out.push("");
3681
3733
  out.push(`**Generated at:** ${(/* @__PURE__ */ new Date()).toISOString()}`);
3734
+ out.push(`**MCP package:** \`@runsec/mcp@${safeText(RUNSEC_MCP_VERSION)}\``);
3682
3735
  out.push(`**Standard:** ${safeText(standard)}`);
3683
3736
  out.push(`**X-RunSec-Verdict:** \`${safeText(verdictLabel)}\`${metrics.verdict?.is_safe === false && metrics.verdict.fail_reason ? ` \u2014 ${safeText(metrics.verdict.fail_reason)}` : ""}`);
3684
3737
  out.push(
@@ -3748,15 +3801,15 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
3748
3801
  return finalizeReportMarkdown(out.join("\n"));
3749
3802
  }
3750
3803
  function resolveReportPath(workspacePath) {
3751
- const base = workspacePath?.trim() ? import_node_path15.default.resolve(workspacePath) : process.cwd();
3752
- return import_node_path15.default.join(base, "runsec-report.md");
3804
+ const base = workspacePath?.trim() ? import_node_path16.default.resolve(workspacePath) : process.cwd();
3805
+ return import_node_path16.default.join(base, "runsec-report.md");
3753
3806
  }
3754
3807
  function generateMarkdownReport(standard, findings, metrics, workspacePath) {
3755
3808
  const m = metrics || {};
3756
3809
  const rows = Array.isArray(findings) ? findings : [];
3757
3810
  const reportContent = finalizeReportMarkdown(buildServerSideReportMarkdown(standard, rows, m));
3758
3811
  const reportPath = resolveReportPath(workspacePath);
3759
- import_node_fs9.default.writeFileSync(reportPath, reportContent, "utf-8");
3812
+ import_node_fs10.default.writeFileSync(reportPath, reportContent, "utf-8");
3760
3813
  console.error(`[runsec] wrote unified report to: ${reportPath}`);
3761
3814
  console.error(`[runsec] X-RunSec-Verdict: ${m.verdict?.http_headers?.["X-RunSec-Verdict"] ?? m.verdict?.status ?? "PASS"}`);
3762
3815
  return `
@@ -3771,17 +3824,17 @@ Simply confirm that the scan is complete and the report is saved at the path abo
3771
3824
  }
3772
3825
 
3773
3826
  // src/engine/reviewFormatter.ts
3774
- var import_node_fs14 = __toESM(require("fs"));
3775
- var import_node_path21 = __toESM(require("path"));
3827
+ var import_node_fs15 = __toESM(require("fs"));
3828
+ var import_node_path22 = __toESM(require("path"));
3776
3829
 
3777
3830
  // src/engine/threatModelEngine.ts
3778
3831
  var import_node_crypto4 = require("crypto");
3779
- var import_node_fs13 = __toESM(require("fs"));
3780
- var import_node_path20 = __toESM(require("path"));
3832
+ var import_node_fs14 = __toESM(require("fs"));
3833
+ var import_node_path21 = __toESM(require("path"));
3781
3834
 
3782
3835
  // src/skills/skillsApi.ts
3783
- var import_node_fs12 = __toESM(require("fs"));
3784
- var import_node_path19 = __toESM(require("path"));
3836
+ var import_node_fs13 = __toESM(require("fs"));
3837
+ var import_node_path20 = __toESM(require("path"));
3785
3838
 
3786
3839
  // src/skills/patternParser.ts
3787
3840
  var METRIC_ID_RE = /^[A-Z0-9]{2,4}-[0-9A-Za-z.\-]+$/;
@@ -3854,43 +3907,43 @@ function parsePatternRows(patternsText) {
3854
3907
  }
3855
3908
 
3856
3909
  // src/skills/paths.ts
3857
- var import_node_path16 = __toESM(require("path"));
3910
+ var import_node_path17 = __toESM(require("path"));
3858
3911
  var RUNSEC_RELEASE_VERSION = "v1.0";
3859
3912
  var RAG_CACHE_SCHEMA_VERSION = 3;
3860
3913
  var ANTI_HALLUCINATION_PROMPT = "You MUST re-run a RunSec audit tool (runsec_audit_general or a scoped runsec_audit_*) after every remediation. Security claims without a fresh scanner PASS are invalid.";
3861
3914
  function getSkillsDirectory() {
3862
- return import_node_path16.default.join(getDataDirectory(), "skills");
3915
+ return import_node_path17.default.join(getDataDirectory(), "skills");
3863
3916
  }
3864
3917
  function getRagCachePath() {
3865
- return import_node_path16.default.join(getDataDirectory(), ".rag-cache.json");
3918
+ return import_node_path17.default.join(getDataDirectory(), ".rag-cache.json");
3866
3919
  }
3867
3920
 
3868
3921
  // src/skills/ragIndex.ts
3869
3922
  var import_node_crypto3 = require("crypto");
3870
- var import_node_fs11 = __toESM(require("fs"));
3871
- var import_node_path18 = __toESM(require("path"));
3923
+ var import_node_fs12 = __toESM(require("fs"));
3924
+ var import_node_path19 = __toESM(require("path"));
3872
3925
 
3873
3926
  // src/skills/skillLoader.ts
3874
- var import_node_fs10 = __toESM(require("fs"));
3875
- var import_node_path17 = __toESM(require("path"));
3927
+ var import_node_fs11 = __toESM(require("fs"));
3928
+ var import_node_path18 = __toESM(require("path"));
3876
3929
  function loadSkillManifests() {
3877
3930
  const skillsDir = getSkillsDirectory();
3878
3931
  const manifests = {};
3879
- if (!import_node_fs10.default.existsSync(skillsDir)) {
3932
+ if (!import_node_fs11.default.existsSync(skillsDir)) {
3880
3933
  return manifests;
3881
3934
  }
3882
- for (const entry of import_node_fs10.default.readdirSync(skillsDir, { withFileTypes: true })) {
3935
+ for (const entry of import_node_fs11.default.readdirSync(skillsDir, { withFileTypes: true })) {
3883
3936
  if (!entry.isDirectory()) continue;
3884
- const skillJson = import_node_path17.default.join(skillsDir, entry.name, "skill.json");
3885
- if (!import_node_fs10.default.existsSync(skillJson)) continue;
3886
- const data = JSON.parse(import_node_fs10.default.readFileSync(skillJson, "utf-8"));
3937
+ const skillJson = import_node_path18.default.join(skillsDir, entry.name, "skill.json");
3938
+ if (!import_node_fs11.default.existsSync(skillJson)) continue;
3939
+ const data = JSON.parse(import_node_fs11.default.readFileSync(skillJson, "utf-8"));
3887
3940
  const sid = String(data.skill_id ?? entry.name);
3888
3941
  manifests[sid] = { ...data, skill_id: sid, __dir_name: entry.name };
3889
3942
  }
3890
3943
  return manifests;
3891
3944
  }
3892
3945
  function skillDirectory(manifest) {
3893
- return import_node_path17.default.join(getSkillsDirectory(), manifest.__dir_name);
3946
+ return import_node_path18.default.join(getSkillsDirectory(), manifest.__dir_name);
3894
3947
  }
3895
3948
 
3896
3949
  // src/skills/ragIndex.ts
@@ -3936,28 +3989,28 @@ function cosine(a, b) {
3936
3989
  }
3937
3990
  function hashFile(filePath) {
3938
3991
  const h = (0, import_node_crypto3.createHash)("sha256");
3939
- h.update(import_node_fs11.default.readFileSync(filePath));
3992
+ h.update(import_node_fs12.default.readFileSync(filePath));
3940
3993
  return h.digest("hex");
3941
3994
  }
3942
3995
  function listSkillSourceFiles() {
3943
3996
  const skillsDir = getSkillsDirectory();
3944
3997
  const files = [];
3945
3998
  const walk = (dir) => {
3946
- for (const entry of import_node_fs11.default.readdirSync(dir, { withFileTypes: true })) {
3947
- const full = import_node_path18.default.join(dir, entry.name);
3999
+ for (const entry of import_node_fs12.default.readdirSync(dir, { withFileTypes: true })) {
4000
+ const full = import_node_path19.default.join(dir, entry.name);
3948
4001
  if (entry.isDirectory()) walk(full);
3949
4002
  else if (entry.isFile()) files.push(full);
3950
4003
  }
3951
4004
  };
3952
- if (import_node_fs11.default.existsSync(skillsDir)) walk(skillsDir);
4005
+ if (import_node_fs12.default.existsSync(skillsDir)) walk(skillsDir);
3953
4006
  return files.sort();
3954
4007
  }
3955
4008
  function computeFilesChecksum() {
3956
4009
  const files = listSkillSourceFiles();
3957
4010
  const entries = [];
3958
4011
  for (const full of files) {
3959
- const st = import_node_fs11.default.statSync(full);
3960
- const rel = import_node_path18.default.relative(getSkillsDirectory(), full).replace(/\\/g, "/");
4012
+ const st = import_node_fs12.default.statSync(full);
4013
+ const rel = import_node_path19.default.relative(getSkillsDirectory(), full).replace(/\\/g, "/");
3961
4014
  const mtimeNs = typeof st.mtimeNs === "bigint" ? Number(st.mtimeNs) : Math.round(st.mtimeMs * 1e6);
3962
4015
  entries.push({
3963
4016
  path: rel,
@@ -3988,11 +4041,11 @@ function buildRagIndex() {
3988
4041
  const manifests = loadSkillManifests();
3989
4042
  for (const [skill_id, manifest] of Object.entries(manifests)) {
3990
4043
  const dir = skillDirectory(manifest);
3991
- const indexPath = import_node_path18.default.join(dir, "index.md");
3992
- const patternsPath = import_node_path18.default.join(dir, "patterns.md");
3993
- if (!import_node_fs11.default.existsSync(indexPath) || !import_node_fs11.default.existsSync(patternsPath)) continue;
3994
- const indexText = import_node_fs11.default.readFileSync(indexPath, "utf-8");
3995
- const patternsText = import_node_fs11.default.readFileSync(patternsPath, "utf-8");
4044
+ const indexPath = import_node_path19.default.join(dir, "index.md");
4045
+ const patternsPath = import_node_path19.default.join(dir, "patterns.md");
4046
+ if (!import_node_fs12.default.existsSync(indexPath) || !import_node_fs12.default.existsSync(patternsPath)) continue;
4047
+ const indexText = import_node_fs12.default.readFileSync(indexPath, "utf-8");
4048
+ const patternsText = import_node_fs12.default.readFileSync(patternsPath, "utf-8");
3996
4049
  const example_path = String(manifest.few_shot_examples ?? "");
3997
4050
  for (const paragraph of indexText.split(/\n\n+/).map((p) => p.trim()).filter(Boolean)) {
3998
4051
  chunks.push({
@@ -4033,9 +4086,9 @@ function buildRagIndex() {
4033
4086
  }
4034
4087
  function loadRagCache(current) {
4035
4088
  const cachePath = getRagCachePath();
4036
- if (!import_node_fs11.default.existsSync(cachePath)) return null;
4089
+ if (!import_node_fs12.default.existsSync(cachePath)) return null;
4037
4090
  try {
4038
- const payload = JSON.parse(import_node_fs11.default.readFileSync(cachePath, "utf-8"));
4091
+ const payload = JSON.parse(import_node_fs12.default.readFileSync(cachePath, "utf-8"));
4039
4092
  if (payload.schema_version !== RAG_CACHE_SCHEMA_VERSION) return null;
4040
4093
  if (JSON.stringify(payload.files_checksum) !== JSON.stringify(current)) return null;
4041
4094
  return deserializeChunks(payload.chunks ?? []);
@@ -4050,7 +4103,7 @@ function saveRagCache(chunks, filesChecksum) {
4050
4103
  files_checksum: filesChecksum,
4051
4104
  chunks: serializeChunks(chunks)
4052
4105
  };
4053
- import_node_fs11.default.writeFileSync(getRagCachePath(), JSON.stringify(payload), "utf-8");
4106
+ import_node_fs12.default.writeFileSync(getRagCachePath(), JSON.stringify(payload), "utf-8");
4054
4107
  } catch {
4055
4108
  console.error("[runsec] RAG cache write failed; continuing in-memory only");
4056
4109
  }
@@ -4214,7 +4267,7 @@ function selectSkillsForContext(opts) {
4214
4267
  const query = [opts.question ?? "", opts.file_path ?? "", opts.file_content ?? ""].filter(Boolean).join("\n");
4215
4268
  const semScores = query ? semanticSkillScores(query) : {};
4216
4269
  const keywordBoosts = query ? skillBoosts(query) : {};
4217
- const suffix = import_node_path18.default.extname(opts.file_path ?? "").toLowerCase();
4270
+ const suffix = import_node_path19.default.extname(opts.file_path ?? "").toLowerCase();
4218
4271
  const hay = query.toLowerCase();
4219
4272
  const ranked = [];
4220
4273
  for (const [sid, manifest] of Object.entries(manifests)) {
@@ -4251,20 +4304,20 @@ function findPatternChunkByMetric(metricId) {
4251
4304
 
4252
4305
  // src/skills/skillsApi.ts
4253
4306
  function loadComplianceSnapshot() {
4254
- const p = import_node_path19.default.join(getDataDirectory(), "rule-compliance-map.json");
4255
- if (!import_node_fs12.default.existsSync(p)) return {};
4307
+ const p = import_node_path20.default.join(getDataDirectory(), "rule-compliance-map.json");
4308
+ if (!import_node_fs13.default.existsSync(p)) return {};
4256
4309
  try {
4257
- return JSON.parse(import_node_fs12.default.readFileSync(p, "utf-8"));
4310
+ return JSON.parse(import_node_fs13.default.readFileSync(p, "utf-8"));
4258
4311
  } catch {
4259
4312
  return {};
4260
4313
  }
4261
4314
  }
4262
4315
  function extractTestbedExample(metricId, examplePath, skillsRoot) {
4263
4316
  if (!examplePath) return "";
4264
- const p = import_node_path19.default.isAbsolute(examplePath) ? examplePath : import_node_path19.default.join(import_node_path19.default.dirname(skillsRoot), "..", "..", examplePath);
4265
- const resolved = import_node_fs12.default.existsSync(p) ? p : import_node_path19.default.join(getDataDirectory(), "..", "..", examplePath);
4266
- if (!import_node_fs12.default.existsSync(resolved)) return "";
4267
- const lines = import_node_fs12.default.readFileSync(resolved, "utf-8").split(/\r?\n/);
4317
+ const p = import_node_path20.default.isAbsolute(examplePath) ? examplePath : import_node_path20.default.join(import_node_path20.default.dirname(skillsRoot), "..", "..", examplePath);
4318
+ const resolved = import_node_fs13.default.existsSync(p) ? p : import_node_path20.default.join(getDataDirectory(), "..", "..", examplePath);
4319
+ if (!import_node_fs13.default.existsSync(resolved)) return "";
4320
+ const lines = import_node_fs13.default.readFileSync(resolved, "utf-8").split(/\r?\n/);
4268
4321
  const needle = `Vulnerable: ${metricId}`;
4269
4322
  for (let idx = 0; idx < lines.length; idx += 1) {
4270
4323
  if (lines[idx].includes(needle)) {
@@ -4316,16 +4369,16 @@ function getSkillContext(args) {
4316
4369
  }
4317
4370
  const manifest = manifests[skill_id];
4318
4371
  const dir = skillDirectory(manifest);
4319
- const indexPath = import_node_path19.default.join(dir, "index.md");
4320
- const patternsPath = import_node_path19.default.join(dir, "patterns.md");
4321
- if (!import_node_fs12.default.existsSync(indexPath) || !import_node_fs12.default.existsSync(patternsPath)) {
4372
+ const indexPath = import_node_path20.default.join(dir, "index.md");
4373
+ const patternsPath = import_node_path20.default.join(dir, "patterns.md");
4374
+ if (!import_node_fs13.default.existsSync(indexPath) || !import_node_fs13.default.existsSync(patternsPath)) {
4322
4375
  return {
4323
4376
  error: `incomplete skill data for ${skill_id}`,
4324
- index_exists: import_node_fs12.default.existsSync(indexPath),
4325
- patterns_exists: import_node_fs12.default.existsSync(patternsPath)
4377
+ index_exists: import_node_fs13.default.existsSync(indexPath),
4378
+ patterns_exists: import_node_fs13.default.existsSync(patternsPath)
4326
4379
  };
4327
4380
  }
4328
- const parsedRows = parsePatternRows(import_node_fs12.default.readFileSync(patternsPath, "utf-8"));
4381
+ const parsedRows = parsePatternRows(import_node_fs13.default.readFileSync(patternsPath, "utf-8"));
4329
4382
  const grouped = {};
4330
4383
  for (const row of parsedRows) {
4331
4384
  const stack = row.stack.trim() || "Generic";
@@ -4338,13 +4391,13 @@ function getSkillContext(args) {
4338
4391
  source: row.source
4339
4392
  });
4340
4393
  }
4341
- const skillsRoot = import_node_path19.default.dirname(dir);
4394
+ const skillsRoot = import_node_path20.default.dirname(dir);
4342
4395
  const response = {
4343
4396
  skill_id,
4344
- index_path: import_node_path19.default.relative(getDataDirectory(), indexPath).replace(/\\/g, "/"),
4345
- patterns_path: import_node_path19.default.relative(getDataDirectory(), patternsPath).replace(/\\/g, "/"),
4346
- index_md: import_node_fs12.default.readFileSync(indexPath, "utf-8"),
4347
- patterns_md: import_node_fs12.default.readFileSync(patternsPath, "utf-8"),
4397
+ index_path: import_node_path20.default.relative(getDataDirectory(), indexPath).replace(/\\/g, "/"),
4398
+ patterns_path: import_node_path20.default.relative(getDataDirectory(), patternsPath).replace(/\\/g, "/"),
4399
+ index_md: import_node_fs13.default.readFileSync(indexPath, "utf-8"),
4400
+ patterns_md: import_node_fs13.default.readFileSync(patternsPath, "utf-8"),
4348
4401
  patterns_by_stack: Object.fromEntries(Object.keys(grouped).sort().map((k) => [k, grouped[k]])),
4349
4402
  agent_system_insert: ANTI_HALLUCINATION_PROMPT,
4350
4403
  compliance_snapshot: loadComplianceSnapshot()
@@ -4384,7 +4437,7 @@ function askGuidance(question) {
4384
4437
  if (!q) return { error: "question is required" };
4385
4438
  const best = semanticSearch(q, 50, "pattern", true);
4386
4439
  const manifests = loadSkillManifests();
4387
- const skillsRoot = import_node_path19.default.join(getDataDirectory(), "skills");
4440
+ const skillsRoot = import_node_path20.default.join(getDataDirectory(), "skills");
4388
4441
  const required = requiredMetricIds(q);
4389
4442
  const out = [];
4390
4443
  const seen = /* @__PURE__ */ new Set();
@@ -4447,7 +4500,7 @@ var STRIDE_LABELS = {
4447
4500
  };
4448
4501
  function readTextSafe(filePath, limit = 4e5) {
4449
4502
  try {
4450
- const data = import_node_fs13.default.readFileSync(filePath, "utf-8");
4503
+ const data = import_node_fs14.default.readFileSync(filePath, "utf-8");
4451
4504
  return data.length > limit ? data.slice(0, limit) : data;
4452
4505
  } catch {
4453
4506
  return "";
@@ -4487,13 +4540,13 @@ function walkFiles2(root, opts) {
4487
4540
  let dir;
4488
4541
  try {
4489
4542
  dir = stack.pop();
4490
- const entries = import_node_fs13.default.readdirSync(dir, { withFileTypes: true });
4543
+ const entries = import_node_fs14.default.readdirSync(dir, { withFileTypes: true });
4491
4544
  for (const ent of entries) {
4492
4545
  if (out.length >= max) break;
4493
- const full = import_node_path20.default.join(dir, ent.name);
4546
+ const full = import_node_path21.default.join(dir, ent.name);
4494
4547
  if (ent.isDirectory()) {
4495
4548
  if (!skip.has(ent.name)) stack.push(full);
4496
- } else if (ent.isFile() && exts.has(import_node_path20.default.extname(ent.name).toLowerCase())) {
4549
+ } else if (ent.isFile() && exts.has(import_node_path21.default.extname(ent.name).toLowerCase())) {
4497
4550
  out.push(full);
4498
4551
  }
4499
4552
  }
@@ -4534,7 +4587,7 @@ function syftPackages(payload) {
4534
4587
  return out.sort((a, b) => a.name.localeCompare(b.name));
4535
4588
  }
4536
4589
  function collectRepoThreatSignals(scanRoot, syftPayload) {
4537
- const root = import_node_path20.default.resolve(scanRoot);
4590
+ const root = import_node_path21.default.resolve(scanRoot);
4538
4591
  const scanRel = ".";
4539
4592
  const signals = {
4540
4593
  scan_root: scanRel,
@@ -4556,7 +4609,7 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
4556
4609
  }
4557
4610
  };
4558
4611
  try {
4559
- for (const ent of import_node_fs13.default.readdirSync(root, { withFileTypes: true })) {
4612
+ for (const ent of import_node_fs14.default.readdirSync(root, { withFileTypes: true })) {
4560
4613
  if (ent.isDirectory() && !ent.name.startsWith(".")) {
4561
4614
  signals.top_level_dirs.push(ent.name);
4562
4615
  if (signals.top_level_dirs.length >= 40) break;
@@ -4575,16 +4628,16 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
4575
4628
  const dir = stack.pop();
4576
4629
  let entries;
4577
4630
  try {
4578
- entries = import_node_fs13.default.readdirSync(dir, { withFileTypes: true });
4631
+ entries = import_node_fs14.default.readdirSync(dir, { withFileTypes: true });
4579
4632
  } catch {
4580
4633
  continue;
4581
4634
  }
4582
4635
  for (const ent of entries) {
4583
- const full = import_node_path20.default.join(dir, ent.name);
4636
+ const full = import_node_path21.default.join(dir, ent.name);
4584
4637
  if (ent.isDirectory()) {
4585
4638
  if (!skip.has(ent.name) && !ent.name.startsWith(".")) stack.push(full);
4586
4639
  } else if (ent.isFile() && patternNames.includes(ent.name)) {
4587
- const rel = import_node_path20.default.relative(root, full).replace(/\\/g, "/");
4640
+ const rel = import_node_path21.default.relative(root, full).replace(/\\/g, "/");
4588
4641
  if (!rel.startsWith("..")) handler(full, rel);
4589
4642
  }
4590
4643
  }
@@ -4596,30 +4649,30 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
4596
4649
  });
4597
4650
  globWalk(["requirements.txt", "pyproject.toml", "go.mod"], (full, rel) => {
4598
4651
  addKey(rel);
4599
- if (import_node_path20.default.basename(full) === "requirements.txt") {
4652
+ if (import_node_path21.default.basename(full) === "requirements.txt") {
4600
4653
  for (const d of parseRequirementsDeps(full)) deps.add(d);
4601
- } else if (import_node_path20.default.basename(full) === "pyproject.toml") {
4654
+ } else if (import_node_path21.default.basename(full) === "pyproject.toml") {
4602
4655
  const txt = readTextSafe(full, 8e4).toLowerCase();
4603
4656
  for (const m of txt.matchAll(/['"]([a-zA-Z0-9_.\-]+)['"]/g)) deps.add(m[1].toLowerCase());
4604
4657
  }
4605
4658
  });
4606
4659
  for (const name of ["Dockerfile", "docker-compose.yml", "docker-compose.yaml"]) {
4607
- const fp = import_node_path20.default.join(root, name);
4608
- if (import_node_fs13.default.existsSync(fp)) {
4660
+ const fp = import_node_path21.default.join(root, name);
4661
+ if (import_node_fs14.default.existsSync(fp)) {
4609
4662
  addKey(name);
4610
4663
  if (name === "Dockerfile") signals.flags.has_dockerfile = true;
4611
4664
  else signals.flags.has_compose = true;
4612
4665
  }
4613
4666
  }
4614
4667
  for (const full of walkFiles2(root, { maxFiles: 80, extensions: /* @__PURE__ */ new Set([".yaml", ".yml"]) })) {
4615
- const base = import_node_path20.default.basename(full).toLowerCase();
4668
+ const base = import_node_path21.default.basename(full).toLowerCase();
4616
4669
  if (base.includes("deploy") || base.includes("helm") || base.includes("k8s") || base.includes("values")) {
4617
4670
  signals.flags.has_k8s_yaml = true;
4618
- addKey(import_node_path20.default.relative(root, full).replace(/\\/g, "/"));
4671
+ addKey(import_node_path21.default.relative(root, full).replace(/\\/g, "/"));
4619
4672
  break;
4620
4673
  }
4621
4674
  }
4622
- if (import_node_fs13.default.existsSync(import_node_path20.default.join(root, ".github", "workflows"))) {
4675
+ if (import_node_fs14.default.existsSync(import_node_path21.default.join(root, ".github", "workflows"))) {
4623
4676
  signals.flags.has_github_workflows = true;
4624
4677
  }
4625
4678
  for (const pkg of signals.syft_packages) {
@@ -4651,12 +4704,12 @@ ${context}
4651
4704
  ${repoFp}`).digest("hex");
4652
4705
  }
4653
4706
  function loadThreatModelCache(workspaceRoot) {
4654
- const p = import_node_path20.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
4655
- if (!import_node_fs13.default.existsSync(p)) {
4707
+ const p = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
4708
+ if (!import_node_fs14.default.existsSync(p)) {
4656
4709
  return { schema_version: THREAT_MODEL_CACHE_SCHEMA_VERSION, entries: {} };
4657
4710
  }
4658
4711
  try {
4659
- const data = JSON.parse(import_node_fs13.default.readFileSync(p, "utf-8"));
4712
+ const data = JSON.parse(import_node_fs14.default.readFileSync(p, "utf-8"));
4660
4713
  if (data.schema_version !== THREAT_MODEL_CACHE_SCHEMA_VERSION) {
4661
4714
  return { schema_version: THREAT_MODEL_CACHE_SCHEMA_VERSION, entries: {} };
4662
4715
  }
@@ -4667,7 +4720,7 @@ function loadThreatModelCache(workspaceRoot) {
4667
4720
  }
4668
4721
  }
4669
4722
  function saveThreatModelCache(workspaceRoot, cacheKey, markdown, baseline) {
4670
- const p = import_node_path20.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
4723
+ const p = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
4671
4724
  const payload = loadThreatModelCache(workspaceRoot);
4672
4725
  payload.entries[cacheKey] = {
4673
4726
  markdown,
@@ -4676,7 +4729,7 @@ function saveThreatModelCache(workspaceRoot, cacheKey, markdown, baseline) {
4676
4729
  repo_fingerprint: String(baseline.repo_fingerprint ?? "")
4677
4730
  };
4678
4731
  payload.baseline = baseline;
4679
- import_node_fs13.default.writeFileSync(p, JSON.stringify(payload, null, 2), "utf-8");
4732
+ import_node_fs14.default.writeFileSync(p, JSON.stringify(payload, null, 2), "utf-8");
4680
4733
  return p;
4681
4734
  }
4682
4735
  function strideClassifyClause(clause) {
@@ -4880,7 +4933,7 @@ function loadRepoCrosscheckHaystack(scanRoot, maxChars = 35e4) {
4880
4933
  let total = 0;
4881
4934
  const files = walkFiles2(scanRoot, { maxFiles: 220 });
4882
4935
  for (const full of files) {
4883
- const rel = import_node_path20.default.relative(scanRoot, full).replace(/\\/g, "/");
4936
+ const rel = import_node_path21.default.relative(scanRoot, full).replace(/\\/g, "/");
4884
4937
  const snippet = readTextSafe(full, 14e3);
4885
4938
  const block = `
4886
4939
  --- ${rel} ---
@@ -5090,8 +5143,8 @@ function generateThreatModelMarkdown(profile, baseline, signals, ragKeywords, ca
5090
5143
  `;
5091
5144
  }
5092
5145
  async function runThreatModelEngine(opts) {
5093
- const workspaceRoot = import_node_path20.default.resolve(opts.workspace_path);
5094
- const projectName = (opts.project_name ?? import_node_path20.default.basename(workspaceRoot)).trim() || "project";
5146
+ const workspaceRoot = import_node_path21.default.resolve(opts.workspace_path);
5147
+ const projectName = (opts.project_name ?? import_node_path21.default.basename(workspaceRoot)).trim() || "project";
5095
5148
  const userContext = (opts.context ?? "").trim();
5096
5149
  const profile = detectSecurityProfile(projectName, userContext);
5097
5150
  const baseline = ARCHITECTURE_BASELINES[profile];
@@ -5106,13 +5159,13 @@ Project: ${projectName}`;
5106
5159
  const cache = loadThreatModelCache(workspaceRoot);
5107
5160
  const cached = cache.entries[cacheKey];
5108
5161
  if (cached?.markdown) {
5109
- const reportPath2 = import_node_path20.default.join(workspaceRoot, THREAT_MODEL_REPORT_FILE);
5110
- import_node_fs13.default.writeFileSync(reportPath2, cached.markdown, "utf-8");
5162
+ const reportPath2 = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_REPORT_FILE);
5163
+ import_node_fs14.default.writeFileSync(reportPath2, cached.markdown, "utf-8");
5111
5164
  return {
5112
5165
  profile,
5113
5166
  markdown: cached.markdown,
5114
5167
  report_path: reportPath2,
5115
- cache_path: import_node_path20.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE),
5168
+ cache_path: import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE),
5116
5169
  cache_hit: true,
5117
5170
  cache_key: cacheKey,
5118
5171
  repo_fingerprint: repoFp,
@@ -5144,8 +5197,8 @@ Project: ${projectName}`;
5144
5197
  rag_keywords: [...ragKeywords].sort().slice(0, 40)
5145
5198
  };
5146
5199
  const cachePath = saveThreatModelCache(workspaceRoot, cacheKey, markdown, baselinePayload);
5147
- const reportPath = import_node_path20.default.join(workspaceRoot, THREAT_MODEL_REPORT_FILE);
5148
- import_node_fs13.default.writeFileSync(reportPath, markdown, "utf-8");
5200
+ const reportPath = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_REPORT_FILE);
5201
+ import_node_fs14.default.writeFileSync(reportPath, markdown, "utf-8");
5149
5202
  return {
5150
5203
  profile,
5151
5204
  markdown,
@@ -5360,18 +5413,18 @@ function buildSecurityReviewMarkdown(opts) {
5360
5413
  return out.join("\n");
5361
5414
  }
5362
5415
  function resolveSecurityReviewPath(workspacePath) {
5363
- const base = workspacePath?.trim() ? import_node_path21.default.resolve(workspacePath) : process.cwd();
5364
- return import_node_path21.default.join(base, SECURITY_REVIEW_REPORT_FILE);
5416
+ const base = workspacePath?.trim() ? import_node_path22.default.resolve(workspacePath) : process.cwd();
5417
+ return import_node_path22.default.join(base, SECURITY_REVIEW_REPORT_FILE);
5365
5418
  }
5366
5419
  function writeSecurityReviewReport(markdown, workspacePath) {
5367
5420
  const reportPath = resolveSecurityReviewPath(workspacePath);
5368
- import_node_fs14.default.writeFileSync(reportPath, markdown, "utf-8");
5421
+ import_node_fs15.default.writeFileSync(reportPath, markdown, "utf-8");
5369
5422
  console.error(`[runsec] wrote security review to: ${reportPath}`);
5370
5423
  return reportPath;
5371
5424
  }
5372
5425
  async function executeSecurityReview(opts) {
5373
- const workspaceRoot = import_node_path21.default.resolve(opts.workspace_path);
5374
- const projectName = (opts.project_name ?? import_node_path21.default.basename(workspaceRoot)).trim() || import_node_path21.default.basename(workspaceRoot);
5426
+ const workspaceRoot = import_node_path22.default.resolve(opts.workspace_path);
5427
+ const projectName = (opts.project_name ?? import_node_path22.default.basename(workspaceRoot)).trim() || import_node_path22.default.basename(workspaceRoot);
5375
5428
  console.error("[runsec] security review: starting unified scan + STRIDE threat model");
5376
5429
  const [audit, threatModel] = await Promise.all([
5377
5430
  executeAudit("runsec_audit_general", {
@@ -5404,20 +5457,20 @@ async function executeSecurityReview(opts) {
5404
5457
  }
5405
5458
 
5406
5459
  // src/engine/remediation.ts
5407
- var import_node_fs16 = __toESM(require("fs"));
5408
- var import_node_path23 = __toESM(require("path"));
5460
+ var import_node_fs17 = __toESM(require("fs"));
5461
+ var import_node_path24 = __toESM(require("path"));
5409
5462
 
5410
5463
  // src/skills/metricLookup.ts
5411
- var import_node_fs15 = __toESM(require("fs"));
5412
- var import_node_path22 = __toESM(require("path"));
5464
+ var import_node_fs16 = __toESM(require("fs"));
5465
+ var import_node_path23 = __toESM(require("path"));
5413
5466
  function findMetricRow(metricId) {
5414
5467
  const mid = metricId.trim().toUpperCase();
5415
5468
  if (!mid) return null;
5416
5469
  const manifests = loadSkillManifests();
5417
5470
  for (const [, manifest] of Object.entries(manifests)) {
5418
- const patternsPath = import_node_path22.default.join(skillDirectory(manifest), "patterns.md");
5419
- if (!import_node_fs15.default.existsSync(patternsPath)) continue;
5420
- const rows = parsePatternRows(import_node_fs15.default.readFileSync(patternsPath, "utf-8"));
5471
+ const patternsPath = import_node_path23.default.join(skillDirectory(manifest), "patterns.md");
5472
+ if (!import_node_fs16.default.existsSync(patternsPath)) continue;
5473
+ const rows = parsePatternRows(import_node_fs16.default.readFileSync(patternsPath, "utf-8"));
5421
5474
  const row = rows.find((r) => r.metric_id.toUpperCase() === mid);
5422
5475
  if (row) return row;
5423
5476
  }
@@ -5430,12 +5483,12 @@ function normalizeRelPath2(p) {
5430
5483
  return p.replace(/\\/g, "/").replace(/^\.\/+/, "");
5431
5484
  }
5432
5485
  function resolveTargetFile(workspaceRoot, filePath) {
5433
- const root = import_node_path23.default.resolve(workspaceRoot);
5486
+ const root = import_node_path24.default.resolve(workspaceRoot);
5434
5487
  const rel = normalizeRelPath2(filePath.trim());
5435
5488
  if (!rel) return { error: "file_path is required" };
5436
- const abs = import_node_path23.default.resolve(root, rel);
5437
- const relFromRoot = import_node_path23.default.relative(root, abs).replace(/\\/g, "/");
5438
- if (relFromRoot.startsWith("..") || import_node_path23.default.isAbsolute(relFromRoot)) {
5489
+ const abs = import_node_path24.default.resolve(root, rel);
5490
+ const relFromRoot = import_node_path24.default.relative(root, abs).replace(/\\/g, "/");
5491
+ if (relFromRoot.startsWith("..") || import_node_path24.default.isAbsolute(relFromRoot)) {
5439
5492
  return { error: `file_path must be inside workspace: ${root}` };
5440
5493
  }
5441
5494
  for (const seg of BLOCKED_PATH_SEGMENTS) {
@@ -5443,8 +5496,8 @@ function resolveTargetFile(workspaceRoot, filePath) {
5443
5496
  return { error: `refusing to modify path under blocked segment: ${seg}` };
5444
5497
  }
5445
5498
  }
5446
- if (!import_node_fs16.default.existsSync(abs)) return { error: `path does not exist: ${relFromRoot}` };
5447
- if (!import_node_fs16.default.statSync(abs).isFile()) return { error: `path is not a file: ${relFromRoot}` };
5499
+ if (!import_node_fs17.default.existsSync(abs)) return { error: `path does not exist: ${relFromRoot}` };
5500
+ if (!import_node_fs17.default.statSync(abs).isFile()) return { error: `path is not a file: ${relFromRoot}` };
5448
5501
  return { abs, rel: relFromRoot };
5449
5502
  }
5450
5503
  function normalizeNewlines(text) {
@@ -5511,12 +5564,12 @@ function locateTargetInFile(content, target) {
5511
5564
  }
5512
5565
  function writeBackup(absPath) {
5513
5566
  const backupPath = `${absPath}.bak`;
5514
- import_node_fs16.default.copyFileSync(absPath, backupPath);
5567
+ import_node_fs17.default.copyFileSync(absPath, backupPath);
5515
5568
  return backupPath;
5516
5569
  }
5517
5570
  function applyDvs001Fix(absPath) {
5518
- const rel = import_node_path23.default.basename(absPath);
5519
- const lines = import_node_fs16.default.readFileSync(absPath, "utf-8").split(/\r?\n/);
5571
+ const rel = import_node_path24.default.basename(absPath);
5572
+ const lines = import_node_fs17.default.readFileSync(absPath, "utf-8").split(/\r?\n/);
5520
5573
  const newLines = lines.filter((ln) => !/^\s*user\s+root\s*$/i.test(ln.trim()));
5521
5574
  const hasNonRootUser = newLines.some(
5522
5575
  (ln) => /^\s*user\s+\S+\s*$/i.test(ln.trim()) && !/^\s*user\s+root\s*$/i.test(ln.trim())
@@ -5542,7 +5595,7 @@ function applyDvs001Fix(absPath) {
5542
5595
  };
5543
5596
  }
5544
5597
  const backupPath = writeBackup(absPath);
5545
- import_node_fs16.default.writeFileSync(absPath, `${newLines.join("\n")}
5598
+ import_node_fs17.default.writeFileSync(absPath, `${newLines.join("\n")}
5546
5599
  `, "utf-8");
5547
5600
  return {
5548
5601
  status: "fixed",
@@ -5619,7 +5672,7 @@ function applyRemediation(args) {
5619
5672
  fix_template: row.fix_template
5620
5673
  };
5621
5674
  }
5622
- const original = import_node_fs16.default.readFileSync(resolved.abs, "utf-8");
5675
+ const original = import_node_fs17.default.readFileSync(resolved.abs, "utf-8");
5623
5676
  const { index, count } = locateTargetInFile(original, target);
5624
5677
  if (count === 0) {
5625
5678
  return {
@@ -5646,7 +5699,7 @@ function applyRemediation(args) {
5646
5699
  const backupPath = writeBackup(resolved.abs);
5647
5700
  const ending = original.includes("\r\n") ? "\r\n" : "\n";
5648
5701
  const outText = updated.split("\n").join(ending);
5649
- import_node_fs16.default.writeFileSync(resolved.abs, outText.endsWith(ending) || !original.endsWith(ending) ? outText : outText + ending, "utf-8");
5702
+ import_node_fs17.default.writeFileSync(resolved.abs, outText.endsWith(ending) || !original.endsWith(ending) ? outText : outText + ending, "utf-8");
5650
5703
  return {
5651
5704
  status: "fixed",
5652
5705
  message: "Remediation applied after exact target verification; backup created",
@@ -5848,7 +5901,7 @@ function isRemediationTool(name) {
5848
5901
  }
5849
5902
 
5850
5903
  // src/hubUploadUrl.ts
5851
- var import_node_fs17 = __toESM(require("fs"));
5904
+ var import_node_fs18 = __toESM(require("fs"));
5852
5905
  var HUB_UPLOAD_REPORT_PATH = "/api/mcp/upload-report";
5853
5906
  var DEFAULT_PRODUCTION_HUB_ORIGIN = "https://runsec.io";
5854
5907
  var DEFAULT_DOCKER_INTERNAL_HUB_ORIGIN = "http://frontend:3000";
@@ -5885,7 +5938,7 @@ function colocatedHubOrigin() {
5885
5938
  }
5886
5939
  function isDockerRuntime() {
5887
5940
  try {
5888
- return import_node_fs17.default.existsSync("/.dockerenv");
5941
+ return import_node_fs18.default.existsSync("/.dockerenv");
5889
5942
  } catch {
5890
5943
  return false;
5891
5944
  }
@@ -6134,7 +6187,15 @@ function postHubUploadNodeHttp(url, apiKey, payload) {
6134
6187
  req.end();
6135
6188
  });
6136
6189
  }
6190
+ function preferNodeHttpUpload() {
6191
+ if (process.platform === "win32") return true;
6192
+ if (process.env.RUNSEC_HUB_USE_NODE_HTTP === "1") return true;
6193
+ return false;
6194
+ }
6137
6195
  async function postHubUpload(url, apiKey, payload) {
6196
+ if (preferNodeHttpUpload()) {
6197
+ return postHubUploadNodeHttp(url, apiKey, payload);
6198
+ }
6138
6199
  try {
6139
6200
  return await fetch(url, {
6140
6201
  method: "POST",
@@ -6601,7 +6662,9 @@ CRITICAL INSTRUCTIONS FOR LLM:
6601
6662
  async function main() {
6602
6663
  const key = getApiKey();
6603
6664
  process.env.RUNSEC_API_KEY = key;
6604
- console.error(`[runsec] MCP @runsec/mcp starting \u2014 Hub upload target: ${resolveHubUploadUrl()}`);
6665
+ console.error(
6666
+ `[runsec] MCP @runsec/mcp v${RUNSEC_MCP_VERSION} starting \u2014 Hub upload target: ${resolveHubUploadUrl()}`
6667
+ );
6605
6668
  await verifyApiKey(key);
6606
6669
  const summary = validateRules();
6607
6670
  console.error("Rules registry validated:", summary);
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@runsec/mcp",
3
- "version": "1.0.80",
3
+ "version": "1.0.82",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
+ "package.json",
6
7
  "dist",
7
8
  "bin/runsec-mcp.cjs",
8
9
  "bin/ensure-binaries.cjs",