@runsec/mcp 1.0.80 → 1.0.83

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.
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
4
  const fs = require("node:fs");
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({
@@ -2545,16 +2576,11 @@ var import_promises = __toESM(require("fs/promises"));
2545
2576
  var import_node_os = __toESM(require("os"));
2546
2577
  var import_node_path11 = __toESM(require("path"));
2547
2578
  var TRUFFLEHOG_EXCLUDE_PATTERNS = [
2548
- "**/*.lock",
2549
- "**/package-lock.json",
2550
- "**/pnpm-lock.yaml",
2551
- "**/yarn.lock",
2552
- "**/poetry.lock",
2553
- "**/Cargo.lock",
2554
- "**/composer.lock",
2555
- "**/Gemfile.lock",
2556
- "**/*-lock.json",
2557
- "**/node_modules/**"
2579
+ String.raw`\.lock$`,
2580
+ String.raw`package-lock\.json$`,
2581
+ String.raw`pnpm-lock\.yaml$`,
2582
+ String.raw`-lock\.json$`,
2583
+ String.raw`(^|[\\/])node_modules([\\/]|$)`
2558
2584
  ];
2559
2585
  async function createTrufflehogExcludeFile() {
2560
2586
  const tmpDir = await import_promises.default.mkdtemp(import_node_path11.default.join(import_node_os.default.tmpdir(), "runsec-th-exclude-"));
@@ -3287,8 +3313,29 @@ async function executeAudit(toolName, args) {
3287
3313
  }
3288
3314
 
3289
3315
  // src/engine/reportFormatter.ts
3316
+ var import_node_fs10 = __toESM(require("fs"));
3317
+ var import_node_path16 = __toESM(require("path"));
3318
+
3319
+ // src/version.ts
3290
3320
  var import_node_fs9 = __toESM(require("fs"));
3291
3321
  var import_node_path15 = __toESM(require("path"));
3322
+ function loadVersion() {
3323
+ const candidates = [
3324
+ import_node_path15.default.join(__dirname, "..", "package.json"),
3325
+ import_node_path15.default.join(__dirname, "package.json")
3326
+ ];
3327
+ for (const candidate of candidates) {
3328
+ try {
3329
+ const raw = JSON.parse(import_node_fs9.default.readFileSync(candidate, "utf8"));
3330
+ if (raw.version?.trim()) return raw.version.trim();
3331
+ } catch {
3332
+ }
3333
+ }
3334
+ return "unknown";
3335
+ }
3336
+ var RUNSEC_MCP_VERSION = loadVersion();
3337
+
3338
+ // src/engine/reportFormatter.ts
3292
3339
  var REPORT_SECTION_TITLES = {
3293
3340
  code: "Code Vulnerabilities",
3294
3341
  secrets: "Exposed Secrets",
@@ -3381,7 +3428,7 @@ function shortRuleLabel(ruleId) {
3381
3428
  return parts[parts.length - 1] || ruleId;
3382
3429
  }
3383
3430
  function snippetLanguage(filePath) {
3384
- const ext = import_node_path15.default.extname(filePath).replace(/^\./, "").toLowerCase();
3431
+ const ext = import_node_path16.default.extname(filePath).replace(/^\./, "").toLowerCase();
3385
3432
  const map = {
3386
3433
  yml: "yaml",
3387
3434
  yaml: "yaml",
@@ -3483,9 +3530,9 @@ var TECH_STACK_BY_EXT = {
3483
3530
  };
3484
3531
  function techStackFromFilePath(filePath) {
3485
3532
  const normalized = String(filePath ?? "").replace(/\\/g, "/");
3486
- const base = import_node_path15.default.basename(normalized).toLowerCase();
3533
+ const base = import_node_path16.default.basename(normalized).toLowerCase();
3487
3534
  if (base === "dockerfile" || base.startsWith("dockerfile.")) return "Docker";
3488
- const ext = import_node_path15.default.extname(normalized).toLowerCase();
3535
+ const ext = import_node_path16.default.extname(normalized).toLowerCase();
3489
3536
  return TECH_STACK_BY_EXT[ext] ?? "Other";
3490
3537
  }
3491
3538
  function countFindingsByTechStack(findings) {
@@ -3679,6 +3726,7 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
3679
3726
  out.push(`# RunSec Unified Security Report`);
3680
3727
  out.push("");
3681
3728
  out.push(`**Generated at:** ${(/* @__PURE__ */ new Date()).toISOString()}`);
3729
+ out.push(`**MCP package:** \`@runsec/mcp@${safeText(RUNSEC_MCP_VERSION)}\``);
3682
3730
  out.push(`**Standard:** ${safeText(standard)}`);
3683
3731
  out.push(`**X-RunSec-Verdict:** \`${safeText(verdictLabel)}\`${metrics.verdict?.is_safe === false && metrics.verdict.fail_reason ? ` \u2014 ${safeText(metrics.verdict.fail_reason)}` : ""}`);
3684
3732
  out.push(
@@ -3748,15 +3796,15 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
3748
3796
  return finalizeReportMarkdown(out.join("\n"));
3749
3797
  }
3750
3798
  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");
3799
+ const base = workspacePath?.trim() ? import_node_path16.default.resolve(workspacePath) : process.cwd();
3800
+ return import_node_path16.default.join(base, "runsec-report.md");
3753
3801
  }
3754
3802
  function generateMarkdownReport(standard, findings, metrics, workspacePath) {
3755
3803
  const m = metrics || {};
3756
3804
  const rows = Array.isArray(findings) ? findings : [];
3757
3805
  const reportContent = finalizeReportMarkdown(buildServerSideReportMarkdown(standard, rows, m));
3758
3806
  const reportPath = resolveReportPath(workspacePath);
3759
- import_node_fs9.default.writeFileSync(reportPath, reportContent, "utf-8");
3807
+ import_node_fs10.default.writeFileSync(reportPath, reportContent, "utf-8");
3760
3808
  console.error(`[runsec] wrote unified report to: ${reportPath}`);
3761
3809
  console.error(`[runsec] X-RunSec-Verdict: ${m.verdict?.http_headers?.["X-RunSec-Verdict"] ?? m.verdict?.status ?? "PASS"}`);
3762
3810
  return `
@@ -3771,17 +3819,17 @@ Simply confirm that the scan is complete and the report is saved at the path abo
3771
3819
  }
3772
3820
 
3773
3821
  // src/engine/reviewFormatter.ts
3774
- var import_node_fs14 = __toESM(require("fs"));
3775
- var import_node_path21 = __toESM(require("path"));
3822
+ var import_node_fs15 = __toESM(require("fs"));
3823
+ var import_node_path22 = __toESM(require("path"));
3776
3824
 
3777
3825
  // src/engine/threatModelEngine.ts
3778
3826
  var import_node_crypto4 = require("crypto");
3779
- var import_node_fs13 = __toESM(require("fs"));
3780
- var import_node_path20 = __toESM(require("path"));
3827
+ var import_node_fs14 = __toESM(require("fs"));
3828
+ var import_node_path21 = __toESM(require("path"));
3781
3829
 
3782
3830
  // src/skills/skillsApi.ts
3783
- var import_node_fs12 = __toESM(require("fs"));
3784
- var import_node_path19 = __toESM(require("path"));
3831
+ var import_node_fs13 = __toESM(require("fs"));
3832
+ var import_node_path20 = __toESM(require("path"));
3785
3833
 
3786
3834
  // src/skills/patternParser.ts
3787
3835
  var METRIC_ID_RE = /^[A-Z0-9]{2,4}-[0-9A-Za-z.\-]+$/;
@@ -3854,43 +3902,43 @@ function parsePatternRows(patternsText) {
3854
3902
  }
3855
3903
 
3856
3904
  // src/skills/paths.ts
3857
- var import_node_path16 = __toESM(require("path"));
3905
+ var import_node_path17 = __toESM(require("path"));
3858
3906
  var RUNSEC_RELEASE_VERSION = "v1.0";
3859
3907
  var RAG_CACHE_SCHEMA_VERSION = 3;
3860
3908
  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
3909
  function getSkillsDirectory() {
3862
- return import_node_path16.default.join(getDataDirectory(), "skills");
3910
+ return import_node_path17.default.join(getDataDirectory(), "skills");
3863
3911
  }
3864
3912
  function getRagCachePath() {
3865
- return import_node_path16.default.join(getDataDirectory(), ".rag-cache.json");
3913
+ return import_node_path17.default.join(getDataDirectory(), ".rag-cache.json");
3866
3914
  }
3867
3915
 
3868
3916
  // src/skills/ragIndex.ts
3869
3917
  var import_node_crypto3 = require("crypto");
3870
- var import_node_fs11 = __toESM(require("fs"));
3871
- var import_node_path18 = __toESM(require("path"));
3918
+ var import_node_fs12 = __toESM(require("fs"));
3919
+ var import_node_path19 = __toESM(require("path"));
3872
3920
 
3873
3921
  // src/skills/skillLoader.ts
3874
- var import_node_fs10 = __toESM(require("fs"));
3875
- var import_node_path17 = __toESM(require("path"));
3922
+ var import_node_fs11 = __toESM(require("fs"));
3923
+ var import_node_path18 = __toESM(require("path"));
3876
3924
  function loadSkillManifests() {
3877
3925
  const skillsDir = getSkillsDirectory();
3878
3926
  const manifests = {};
3879
- if (!import_node_fs10.default.existsSync(skillsDir)) {
3927
+ if (!import_node_fs11.default.existsSync(skillsDir)) {
3880
3928
  return manifests;
3881
3929
  }
3882
- for (const entry of import_node_fs10.default.readdirSync(skillsDir, { withFileTypes: true })) {
3930
+ for (const entry of import_node_fs11.default.readdirSync(skillsDir, { withFileTypes: true })) {
3883
3931
  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"));
3932
+ const skillJson = import_node_path18.default.join(skillsDir, entry.name, "skill.json");
3933
+ if (!import_node_fs11.default.existsSync(skillJson)) continue;
3934
+ const data = JSON.parse(import_node_fs11.default.readFileSync(skillJson, "utf-8"));
3887
3935
  const sid = String(data.skill_id ?? entry.name);
3888
3936
  manifests[sid] = { ...data, skill_id: sid, __dir_name: entry.name };
3889
3937
  }
3890
3938
  return manifests;
3891
3939
  }
3892
3940
  function skillDirectory(manifest) {
3893
- return import_node_path17.default.join(getSkillsDirectory(), manifest.__dir_name);
3941
+ return import_node_path18.default.join(getSkillsDirectory(), manifest.__dir_name);
3894
3942
  }
3895
3943
 
3896
3944
  // src/skills/ragIndex.ts
@@ -3936,28 +3984,28 @@ function cosine(a, b) {
3936
3984
  }
3937
3985
  function hashFile(filePath) {
3938
3986
  const h = (0, import_node_crypto3.createHash)("sha256");
3939
- h.update(import_node_fs11.default.readFileSync(filePath));
3987
+ h.update(import_node_fs12.default.readFileSync(filePath));
3940
3988
  return h.digest("hex");
3941
3989
  }
3942
3990
  function listSkillSourceFiles() {
3943
3991
  const skillsDir = getSkillsDirectory();
3944
3992
  const files = [];
3945
3993
  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);
3994
+ for (const entry of import_node_fs12.default.readdirSync(dir, { withFileTypes: true })) {
3995
+ const full = import_node_path19.default.join(dir, entry.name);
3948
3996
  if (entry.isDirectory()) walk(full);
3949
3997
  else if (entry.isFile()) files.push(full);
3950
3998
  }
3951
3999
  };
3952
- if (import_node_fs11.default.existsSync(skillsDir)) walk(skillsDir);
4000
+ if (import_node_fs12.default.existsSync(skillsDir)) walk(skillsDir);
3953
4001
  return files.sort();
3954
4002
  }
3955
4003
  function computeFilesChecksum() {
3956
4004
  const files = listSkillSourceFiles();
3957
4005
  const entries = [];
3958
4006
  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, "/");
4007
+ const st = import_node_fs12.default.statSync(full);
4008
+ const rel = import_node_path19.default.relative(getSkillsDirectory(), full).replace(/\\/g, "/");
3961
4009
  const mtimeNs = typeof st.mtimeNs === "bigint" ? Number(st.mtimeNs) : Math.round(st.mtimeMs * 1e6);
3962
4010
  entries.push({
3963
4011
  path: rel,
@@ -3988,11 +4036,11 @@ function buildRagIndex() {
3988
4036
  const manifests = loadSkillManifests();
3989
4037
  for (const [skill_id, manifest] of Object.entries(manifests)) {
3990
4038
  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");
4039
+ const indexPath = import_node_path19.default.join(dir, "index.md");
4040
+ const patternsPath = import_node_path19.default.join(dir, "patterns.md");
4041
+ if (!import_node_fs12.default.existsSync(indexPath) || !import_node_fs12.default.existsSync(patternsPath)) continue;
4042
+ const indexText = import_node_fs12.default.readFileSync(indexPath, "utf-8");
4043
+ const patternsText = import_node_fs12.default.readFileSync(patternsPath, "utf-8");
3996
4044
  const example_path = String(manifest.few_shot_examples ?? "");
3997
4045
  for (const paragraph of indexText.split(/\n\n+/).map((p) => p.trim()).filter(Boolean)) {
3998
4046
  chunks.push({
@@ -4033,9 +4081,9 @@ function buildRagIndex() {
4033
4081
  }
4034
4082
  function loadRagCache(current) {
4035
4083
  const cachePath = getRagCachePath();
4036
- if (!import_node_fs11.default.existsSync(cachePath)) return null;
4084
+ if (!import_node_fs12.default.existsSync(cachePath)) return null;
4037
4085
  try {
4038
- const payload = JSON.parse(import_node_fs11.default.readFileSync(cachePath, "utf-8"));
4086
+ const payload = JSON.parse(import_node_fs12.default.readFileSync(cachePath, "utf-8"));
4039
4087
  if (payload.schema_version !== RAG_CACHE_SCHEMA_VERSION) return null;
4040
4088
  if (JSON.stringify(payload.files_checksum) !== JSON.stringify(current)) return null;
4041
4089
  return deserializeChunks(payload.chunks ?? []);
@@ -4050,7 +4098,7 @@ function saveRagCache(chunks, filesChecksum) {
4050
4098
  files_checksum: filesChecksum,
4051
4099
  chunks: serializeChunks(chunks)
4052
4100
  };
4053
- import_node_fs11.default.writeFileSync(getRagCachePath(), JSON.stringify(payload), "utf-8");
4101
+ import_node_fs12.default.writeFileSync(getRagCachePath(), JSON.stringify(payload), "utf-8");
4054
4102
  } catch {
4055
4103
  console.error("[runsec] RAG cache write failed; continuing in-memory only");
4056
4104
  }
@@ -4214,7 +4262,7 @@ function selectSkillsForContext(opts) {
4214
4262
  const query = [opts.question ?? "", opts.file_path ?? "", opts.file_content ?? ""].filter(Boolean).join("\n");
4215
4263
  const semScores = query ? semanticSkillScores(query) : {};
4216
4264
  const keywordBoosts = query ? skillBoosts(query) : {};
4217
- const suffix = import_node_path18.default.extname(opts.file_path ?? "").toLowerCase();
4265
+ const suffix = import_node_path19.default.extname(opts.file_path ?? "").toLowerCase();
4218
4266
  const hay = query.toLowerCase();
4219
4267
  const ranked = [];
4220
4268
  for (const [sid, manifest] of Object.entries(manifests)) {
@@ -4251,20 +4299,20 @@ function findPatternChunkByMetric(metricId) {
4251
4299
 
4252
4300
  // src/skills/skillsApi.ts
4253
4301
  function loadComplianceSnapshot() {
4254
- const p = import_node_path19.default.join(getDataDirectory(), "rule-compliance-map.json");
4255
- if (!import_node_fs12.default.existsSync(p)) return {};
4302
+ const p = import_node_path20.default.join(getDataDirectory(), "rule-compliance-map.json");
4303
+ if (!import_node_fs13.default.existsSync(p)) return {};
4256
4304
  try {
4257
- return JSON.parse(import_node_fs12.default.readFileSync(p, "utf-8"));
4305
+ return JSON.parse(import_node_fs13.default.readFileSync(p, "utf-8"));
4258
4306
  } catch {
4259
4307
  return {};
4260
4308
  }
4261
4309
  }
4262
4310
  function extractTestbedExample(metricId, examplePath, skillsRoot) {
4263
4311
  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/);
4312
+ const p = import_node_path20.default.isAbsolute(examplePath) ? examplePath : import_node_path20.default.join(import_node_path20.default.dirname(skillsRoot), "..", "..", examplePath);
4313
+ const resolved = import_node_fs13.default.existsSync(p) ? p : import_node_path20.default.join(getDataDirectory(), "..", "..", examplePath);
4314
+ if (!import_node_fs13.default.existsSync(resolved)) return "";
4315
+ const lines = import_node_fs13.default.readFileSync(resolved, "utf-8").split(/\r?\n/);
4268
4316
  const needle = `Vulnerable: ${metricId}`;
4269
4317
  for (let idx = 0; idx < lines.length; idx += 1) {
4270
4318
  if (lines[idx].includes(needle)) {
@@ -4316,16 +4364,16 @@ function getSkillContext(args) {
4316
4364
  }
4317
4365
  const manifest = manifests[skill_id];
4318
4366
  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)) {
4367
+ const indexPath = import_node_path20.default.join(dir, "index.md");
4368
+ const patternsPath = import_node_path20.default.join(dir, "patterns.md");
4369
+ if (!import_node_fs13.default.existsSync(indexPath) || !import_node_fs13.default.existsSync(patternsPath)) {
4322
4370
  return {
4323
4371
  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)
4372
+ index_exists: import_node_fs13.default.existsSync(indexPath),
4373
+ patterns_exists: import_node_fs13.default.existsSync(patternsPath)
4326
4374
  };
4327
4375
  }
4328
- const parsedRows = parsePatternRows(import_node_fs12.default.readFileSync(patternsPath, "utf-8"));
4376
+ const parsedRows = parsePatternRows(import_node_fs13.default.readFileSync(patternsPath, "utf-8"));
4329
4377
  const grouped = {};
4330
4378
  for (const row of parsedRows) {
4331
4379
  const stack = row.stack.trim() || "Generic";
@@ -4338,13 +4386,13 @@ function getSkillContext(args) {
4338
4386
  source: row.source
4339
4387
  });
4340
4388
  }
4341
- const skillsRoot = import_node_path19.default.dirname(dir);
4389
+ const skillsRoot = import_node_path20.default.dirname(dir);
4342
4390
  const response = {
4343
4391
  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"),
4392
+ index_path: import_node_path20.default.relative(getDataDirectory(), indexPath).replace(/\\/g, "/"),
4393
+ patterns_path: import_node_path20.default.relative(getDataDirectory(), patternsPath).replace(/\\/g, "/"),
4394
+ index_md: import_node_fs13.default.readFileSync(indexPath, "utf-8"),
4395
+ patterns_md: import_node_fs13.default.readFileSync(patternsPath, "utf-8"),
4348
4396
  patterns_by_stack: Object.fromEntries(Object.keys(grouped).sort().map((k) => [k, grouped[k]])),
4349
4397
  agent_system_insert: ANTI_HALLUCINATION_PROMPT,
4350
4398
  compliance_snapshot: loadComplianceSnapshot()
@@ -4384,7 +4432,7 @@ function askGuidance(question) {
4384
4432
  if (!q) return { error: "question is required" };
4385
4433
  const best = semanticSearch(q, 50, "pattern", true);
4386
4434
  const manifests = loadSkillManifests();
4387
- const skillsRoot = import_node_path19.default.join(getDataDirectory(), "skills");
4435
+ const skillsRoot = import_node_path20.default.join(getDataDirectory(), "skills");
4388
4436
  const required = requiredMetricIds(q);
4389
4437
  const out = [];
4390
4438
  const seen = /* @__PURE__ */ new Set();
@@ -4447,7 +4495,7 @@ var STRIDE_LABELS = {
4447
4495
  };
4448
4496
  function readTextSafe(filePath, limit = 4e5) {
4449
4497
  try {
4450
- const data = import_node_fs13.default.readFileSync(filePath, "utf-8");
4498
+ const data = import_node_fs14.default.readFileSync(filePath, "utf-8");
4451
4499
  return data.length > limit ? data.slice(0, limit) : data;
4452
4500
  } catch {
4453
4501
  return "";
@@ -4487,13 +4535,13 @@ function walkFiles2(root, opts) {
4487
4535
  let dir;
4488
4536
  try {
4489
4537
  dir = stack.pop();
4490
- const entries = import_node_fs13.default.readdirSync(dir, { withFileTypes: true });
4538
+ const entries = import_node_fs14.default.readdirSync(dir, { withFileTypes: true });
4491
4539
  for (const ent of entries) {
4492
4540
  if (out.length >= max) break;
4493
- const full = import_node_path20.default.join(dir, ent.name);
4541
+ const full = import_node_path21.default.join(dir, ent.name);
4494
4542
  if (ent.isDirectory()) {
4495
4543
  if (!skip.has(ent.name)) stack.push(full);
4496
- } else if (ent.isFile() && exts.has(import_node_path20.default.extname(ent.name).toLowerCase())) {
4544
+ } else if (ent.isFile() && exts.has(import_node_path21.default.extname(ent.name).toLowerCase())) {
4497
4545
  out.push(full);
4498
4546
  }
4499
4547
  }
@@ -4534,7 +4582,7 @@ function syftPackages(payload) {
4534
4582
  return out.sort((a, b) => a.name.localeCompare(b.name));
4535
4583
  }
4536
4584
  function collectRepoThreatSignals(scanRoot, syftPayload) {
4537
- const root = import_node_path20.default.resolve(scanRoot);
4585
+ const root = import_node_path21.default.resolve(scanRoot);
4538
4586
  const scanRel = ".";
4539
4587
  const signals = {
4540
4588
  scan_root: scanRel,
@@ -4556,7 +4604,7 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
4556
4604
  }
4557
4605
  };
4558
4606
  try {
4559
- for (const ent of import_node_fs13.default.readdirSync(root, { withFileTypes: true })) {
4607
+ for (const ent of import_node_fs14.default.readdirSync(root, { withFileTypes: true })) {
4560
4608
  if (ent.isDirectory() && !ent.name.startsWith(".")) {
4561
4609
  signals.top_level_dirs.push(ent.name);
4562
4610
  if (signals.top_level_dirs.length >= 40) break;
@@ -4575,16 +4623,16 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
4575
4623
  const dir = stack.pop();
4576
4624
  let entries;
4577
4625
  try {
4578
- entries = import_node_fs13.default.readdirSync(dir, { withFileTypes: true });
4626
+ entries = import_node_fs14.default.readdirSync(dir, { withFileTypes: true });
4579
4627
  } catch {
4580
4628
  continue;
4581
4629
  }
4582
4630
  for (const ent of entries) {
4583
- const full = import_node_path20.default.join(dir, ent.name);
4631
+ const full = import_node_path21.default.join(dir, ent.name);
4584
4632
  if (ent.isDirectory()) {
4585
4633
  if (!skip.has(ent.name) && !ent.name.startsWith(".")) stack.push(full);
4586
4634
  } else if (ent.isFile() && patternNames.includes(ent.name)) {
4587
- const rel = import_node_path20.default.relative(root, full).replace(/\\/g, "/");
4635
+ const rel = import_node_path21.default.relative(root, full).replace(/\\/g, "/");
4588
4636
  if (!rel.startsWith("..")) handler(full, rel);
4589
4637
  }
4590
4638
  }
@@ -4596,30 +4644,30 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
4596
4644
  });
4597
4645
  globWalk(["requirements.txt", "pyproject.toml", "go.mod"], (full, rel) => {
4598
4646
  addKey(rel);
4599
- if (import_node_path20.default.basename(full) === "requirements.txt") {
4647
+ if (import_node_path21.default.basename(full) === "requirements.txt") {
4600
4648
  for (const d of parseRequirementsDeps(full)) deps.add(d);
4601
- } else if (import_node_path20.default.basename(full) === "pyproject.toml") {
4649
+ } else if (import_node_path21.default.basename(full) === "pyproject.toml") {
4602
4650
  const txt = readTextSafe(full, 8e4).toLowerCase();
4603
4651
  for (const m of txt.matchAll(/['"]([a-zA-Z0-9_.\-]+)['"]/g)) deps.add(m[1].toLowerCase());
4604
4652
  }
4605
4653
  });
4606
4654
  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)) {
4655
+ const fp = import_node_path21.default.join(root, name);
4656
+ if (import_node_fs14.default.existsSync(fp)) {
4609
4657
  addKey(name);
4610
4658
  if (name === "Dockerfile") signals.flags.has_dockerfile = true;
4611
4659
  else signals.flags.has_compose = true;
4612
4660
  }
4613
4661
  }
4614
4662
  for (const full of walkFiles2(root, { maxFiles: 80, extensions: /* @__PURE__ */ new Set([".yaml", ".yml"]) })) {
4615
- const base = import_node_path20.default.basename(full).toLowerCase();
4663
+ const base = import_node_path21.default.basename(full).toLowerCase();
4616
4664
  if (base.includes("deploy") || base.includes("helm") || base.includes("k8s") || base.includes("values")) {
4617
4665
  signals.flags.has_k8s_yaml = true;
4618
- addKey(import_node_path20.default.relative(root, full).replace(/\\/g, "/"));
4666
+ addKey(import_node_path21.default.relative(root, full).replace(/\\/g, "/"));
4619
4667
  break;
4620
4668
  }
4621
4669
  }
4622
- if (import_node_fs13.default.existsSync(import_node_path20.default.join(root, ".github", "workflows"))) {
4670
+ if (import_node_fs14.default.existsSync(import_node_path21.default.join(root, ".github", "workflows"))) {
4623
4671
  signals.flags.has_github_workflows = true;
4624
4672
  }
4625
4673
  for (const pkg of signals.syft_packages) {
@@ -4651,12 +4699,12 @@ ${context}
4651
4699
  ${repoFp}`).digest("hex");
4652
4700
  }
4653
4701
  function loadThreatModelCache(workspaceRoot) {
4654
- const p = import_node_path20.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
4655
- if (!import_node_fs13.default.existsSync(p)) {
4702
+ const p = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
4703
+ if (!import_node_fs14.default.existsSync(p)) {
4656
4704
  return { schema_version: THREAT_MODEL_CACHE_SCHEMA_VERSION, entries: {} };
4657
4705
  }
4658
4706
  try {
4659
- const data = JSON.parse(import_node_fs13.default.readFileSync(p, "utf-8"));
4707
+ const data = JSON.parse(import_node_fs14.default.readFileSync(p, "utf-8"));
4660
4708
  if (data.schema_version !== THREAT_MODEL_CACHE_SCHEMA_VERSION) {
4661
4709
  return { schema_version: THREAT_MODEL_CACHE_SCHEMA_VERSION, entries: {} };
4662
4710
  }
@@ -4667,7 +4715,7 @@ function loadThreatModelCache(workspaceRoot) {
4667
4715
  }
4668
4716
  }
4669
4717
  function saveThreatModelCache(workspaceRoot, cacheKey, markdown, baseline) {
4670
- const p = import_node_path20.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
4718
+ const p = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
4671
4719
  const payload = loadThreatModelCache(workspaceRoot);
4672
4720
  payload.entries[cacheKey] = {
4673
4721
  markdown,
@@ -4676,7 +4724,7 @@ function saveThreatModelCache(workspaceRoot, cacheKey, markdown, baseline) {
4676
4724
  repo_fingerprint: String(baseline.repo_fingerprint ?? "")
4677
4725
  };
4678
4726
  payload.baseline = baseline;
4679
- import_node_fs13.default.writeFileSync(p, JSON.stringify(payload, null, 2), "utf-8");
4727
+ import_node_fs14.default.writeFileSync(p, JSON.stringify(payload, null, 2), "utf-8");
4680
4728
  return p;
4681
4729
  }
4682
4730
  function strideClassifyClause(clause) {
@@ -4880,7 +4928,7 @@ function loadRepoCrosscheckHaystack(scanRoot, maxChars = 35e4) {
4880
4928
  let total = 0;
4881
4929
  const files = walkFiles2(scanRoot, { maxFiles: 220 });
4882
4930
  for (const full of files) {
4883
- const rel = import_node_path20.default.relative(scanRoot, full).replace(/\\/g, "/");
4931
+ const rel = import_node_path21.default.relative(scanRoot, full).replace(/\\/g, "/");
4884
4932
  const snippet = readTextSafe(full, 14e3);
4885
4933
  const block = `
4886
4934
  --- ${rel} ---
@@ -5090,8 +5138,8 @@ function generateThreatModelMarkdown(profile, baseline, signals, ragKeywords, ca
5090
5138
  `;
5091
5139
  }
5092
5140
  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";
5141
+ const workspaceRoot = import_node_path21.default.resolve(opts.workspace_path);
5142
+ const projectName = (opts.project_name ?? import_node_path21.default.basename(workspaceRoot)).trim() || "project";
5095
5143
  const userContext = (opts.context ?? "").trim();
5096
5144
  const profile = detectSecurityProfile(projectName, userContext);
5097
5145
  const baseline = ARCHITECTURE_BASELINES[profile];
@@ -5106,13 +5154,13 @@ Project: ${projectName}`;
5106
5154
  const cache = loadThreatModelCache(workspaceRoot);
5107
5155
  const cached = cache.entries[cacheKey];
5108
5156
  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");
5157
+ const reportPath2 = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_REPORT_FILE);
5158
+ import_node_fs14.default.writeFileSync(reportPath2, cached.markdown, "utf-8");
5111
5159
  return {
5112
5160
  profile,
5113
5161
  markdown: cached.markdown,
5114
5162
  report_path: reportPath2,
5115
- cache_path: import_node_path20.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE),
5163
+ cache_path: import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE),
5116
5164
  cache_hit: true,
5117
5165
  cache_key: cacheKey,
5118
5166
  repo_fingerprint: repoFp,
@@ -5144,8 +5192,8 @@ Project: ${projectName}`;
5144
5192
  rag_keywords: [...ragKeywords].sort().slice(0, 40)
5145
5193
  };
5146
5194
  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");
5195
+ const reportPath = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_REPORT_FILE);
5196
+ import_node_fs14.default.writeFileSync(reportPath, markdown, "utf-8");
5149
5197
  return {
5150
5198
  profile,
5151
5199
  markdown,
@@ -5360,18 +5408,18 @@ function buildSecurityReviewMarkdown(opts) {
5360
5408
  return out.join("\n");
5361
5409
  }
5362
5410
  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);
5411
+ const base = workspacePath?.trim() ? import_node_path22.default.resolve(workspacePath) : process.cwd();
5412
+ return import_node_path22.default.join(base, SECURITY_REVIEW_REPORT_FILE);
5365
5413
  }
5366
5414
  function writeSecurityReviewReport(markdown, workspacePath) {
5367
5415
  const reportPath = resolveSecurityReviewPath(workspacePath);
5368
- import_node_fs14.default.writeFileSync(reportPath, markdown, "utf-8");
5416
+ import_node_fs15.default.writeFileSync(reportPath, markdown, "utf-8");
5369
5417
  console.error(`[runsec] wrote security review to: ${reportPath}`);
5370
5418
  return reportPath;
5371
5419
  }
5372
5420
  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);
5421
+ const workspaceRoot = import_node_path22.default.resolve(opts.workspace_path);
5422
+ const projectName = (opts.project_name ?? import_node_path22.default.basename(workspaceRoot)).trim() || import_node_path22.default.basename(workspaceRoot);
5375
5423
  console.error("[runsec] security review: starting unified scan + STRIDE threat model");
5376
5424
  const [audit, threatModel] = await Promise.all([
5377
5425
  executeAudit("runsec_audit_general", {
@@ -5404,20 +5452,20 @@ async function executeSecurityReview(opts) {
5404
5452
  }
5405
5453
 
5406
5454
  // src/engine/remediation.ts
5407
- var import_node_fs16 = __toESM(require("fs"));
5408
- var import_node_path23 = __toESM(require("path"));
5455
+ var import_node_fs17 = __toESM(require("fs"));
5456
+ var import_node_path24 = __toESM(require("path"));
5409
5457
 
5410
5458
  // src/skills/metricLookup.ts
5411
- var import_node_fs15 = __toESM(require("fs"));
5412
- var import_node_path22 = __toESM(require("path"));
5459
+ var import_node_fs16 = __toESM(require("fs"));
5460
+ var import_node_path23 = __toESM(require("path"));
5413
5461
  function findMetricRow(metricId) {
5414
5462
  const mid = metricId.trim().toUpperCase();
5415
5463
  if (!mid) return null;
5416
5464
  const manifests = loadSkillManifests();
5417
5465
  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"));
5466
+ const patternsPath = import_node_path23.default.join(skillDirectory(manifest), "patterns.md");
5467
+ if (!import_node_fs16.default.existsSync(patternsPath)) continue;
5468
+ const rows = parsePatternRows(import_node_fs16.default.readFileSync(patternsPath, "utf-8"));
5421
5469
  const row = rows.find((r) => r.metric_id.toUpperCase() === mid);
5422
5470
  if (row) return row;
5423
5471
  }
@@ -5430,12 +5478,12 @@ function normalizeRelPath2(p) {
5430
5478
  return p.replace(/\\/g, "/").replace(/^\.\/+/, "");
5431
5479
  }
5432
5480
  function resolveTargetFile(workspaceRoot, filePath) {
5433
- const root = import_node_path23.default.resolve(workspaceRoot);
5481
+ const root = import_node_path24.default.resolve(workspaceRoot);
5434
5482
  const rel = normalizeRelPath2(filePath.trim());
5435
5483
  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)) {
5484
+ const abs = import_node_path24.default.resolve(root, rel);
5485
+ const relFromRoot = import_node_path24.default.relative(root, abs).replace(/\\/g, "/");
5486
+ if (relFromRoot.startsWith("..") || import_node_path24.default.isAbsolute(relFromRoot)) {
5439
5487
  return { error: `file_path must be inside workspace: ${root}` };
5440
5488
  }
5441
5489
  for (const seg of BLOCKED_PATH_SEGMENTS) {
@@ -5443,8 +5491,8 @@ function resolveTargetFile(workspaceRoot, filePath) {
5443
5491
  return { error: `refusing to modify path under blocked segment: ${seg}` };
5444
5492
  }
5445
5493
  }
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}` };
5494
+ if (!import_node_fs17.default.existsSync(abs)) return { error: `path does not exist: ${relFromRoot}` };
5495
+ if (!import_node_fs17.default.statSync(abs).isFile()) return { error: `path is not a file: ${relFromRoot}` };
5448
5496
  return { abs, rel: relFromRoot };
5449
5497
  }
5450
5498
  function normalizeNewlines(text) {
@@ -5511,12 +5559,12 @@ function locateTargetInFile(content, target) {
5511
5559
  }
5512
5560
  function writeBackup(absPath) {
5513
5561
  const backupPath = `${absPath}.bak`;
5514
- import_node_fs16.default.copyFileSync(absPath, backupPath);
5562
+ import_node_fs17.default.copyFileSync(absPath, backupPath);
5515
5563
  return backupPath;
5516
5564
  }
5517
5565
  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/);
5566
+ const rel = import_node_path24.default.basename(absPath);
5567
+ const lines = import_node_fs17.default.readFileSync(absPath, "utf-8").split(/\r?\n/);
5520
5568
  const newLines = lines.filter((ln) => !/^\s*user\s+root\s*$/i.test(ln.trim()));
5521
5569
  const hasNonRootUser = newLines.some(
5522
5570
  (ln) => /^\s*user\s+\S+\s*$/i.test(ln.trim()) && !/^\s*user\s+root\s*$/i.test(ln.trim())
@@ -5542,7 +5590,7 @@ function applyDvs001Fix(absPath) {
5542
5590
  };
5543
5591
  }
5544
5592
  const backupPath = writeBackup(absPath);
5545
- import_node_fs16.default.writeFileSync(absPath, `${newLines.join("\n")}
5593
+ import_node_fs17.default.writeFileSync(absPath, `${newLines.join("\n")}
5546
5594
  `, "utf-8");
5547
5595
  return {
5548
5596
  status: "fixed",
@@ -5619,7 +5667,7 @@ function applyRemediation(args) {
5619
5667
  fix_template: row.fix_template
5620
5668
  };
5621
5669
  }
5622
- const original = import_node_fs16.default.readFileSync(resolved.abs, "utf-8");
5670
+ const original = import_node_fs17.default.readFileSync(resolved.abs, "utf-8");
5623
5671
  const { index, count } = locateTargetInFile(original, target);
5624
5672
  if (count === 0) {
5625
5673
  return {
@@ -5646,7 +5694,7 @@ function applyRemediation(args) {
5646
5694
  const backupPath = writeBackup(resolved.abs);
5647
5695
  const ending = original.includes("\r\n") ? "\r\n" : "\n";
5648
5696
  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");
5697
+ import_node_fs17.default.writeFileSync(resolved.abs, outText.endsWith(ending) || !original.endsWith(ending) ? outText : outText + ending, "utf-8");
5650
5698
  return {
5651
5699
  status: "fixed",
5652
5700
  message: "Remediation applied after exact target verification; backup created",
@@ -5848,7 +5896,7 @@ function isRemediationTool(name) {
5848
5896
  }
5849
5897
 
5850
5898
  // src/hubUploadUrl.ts
5851
- var import_node_fs17 = __toESM(require("fs"));
5899
+ var import_node_fs18 = __toESM(require("fs"));
5852
5900
  var HUB_UPLOAD_REPORT_PATH = "/api/mcp/upload-report";
5853
5901
  var DEFAULT_PRODUCTION_HUB_ORIGIN = "https://runsec.io";
5854
5902
  var DEFAULT_DOCKER_INTERNAL_HUB_ORIGIN = "http://frontend:3000";
@@ -5885,7 +5933,7 @@ function colocatedHubOrigin() {
5885
5933
  }
5886
5934
  function isDockerRuntime() {
5887
5935
  try {
5888
- return import_node_fs17.default.existsSync("/.dockerenv");
5936
+ return import_node_fs18.default.existsSync("/.dockerenv");
5889
5937
  } catch {
5890
5938
  return false;
5891
5939
  }
@@ -6134,7 +6182,15 @@ function postHubUploadNodeHttp(url, apiKey, payload) {
6134
6182
  req.end();
6135
6183
  });
6136
6184
  }
6185
+ function preferNodeHttpUpload() {
6186
+ if (process.platform === "win32") return true;
6187
+ if (process.env.RUNSEC_HUB_USE_NODE_HTTP === "1") return true;
6188
+ return false;
6189
+ }
6137
6190
  async function postHubUpload(url, apiKey, payload) {
6191
+ if (preferNodeHttpUpload()) {
6192
+ return postHubUploadNodeHttp(url, apiKey, payload);
6193
+ }
6138
6194
  try {
6139
6195
  return await fetch(url, {
6140
6196
  method: "POST",
@@ -6601,7 +6657,9 @@ CRITICAL INSTRUCTIONS FOR LLM:
6601
6657
  async function main() {
6602
6658
  const key = getApiKey();
6603
6659
  process.env.RUNSEC_API_KEY = key;
6604
- console.error(`[runsec] MCP @runsec/mcp starting \u2014 Hub upload target: ${resolveHubUploadUrl()}`);
6660
+ console.error(
6661
+ `[runsec] MCP @runsec/mcp v${RUNSEC_MCP_VERSION} starting \u2014 Hub upload target: ${resolveHubUploadUrl()}`
6662
+ );
6605
6663
  await verifyApiKey(key);
6606
6664
  const summary = validateRules();
6607
6665
  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.83",
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",