@runsec/mcp 1.0.26 → 1.0.28

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.
@@ -18,4 +18,4 @@ rules:
18
18
  - pattern: child_process.exec($X, ...)
19
19
  ```
20
20
 
21
- Map to MITRE **T1059.007** (JavaScript) / **T1204** where applicable; combine with `server/cognitive_engine.py` to downgrade confidence when matches are constant-only.
21
+ Map to MITRE **T1059.007** (JavaScript) / **T1204** where applicable; combine with RunSec cognitive scoring in the Hub pipeline when matches are constant-only.
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ var import_types = require("@modelcontextprotocol/sdk/types.js");
29
29
 
30
30
  // src/engine/ruleEngine.ts
31
31
  var import_node_fs2 = require("fs");
32
+ var import_node_fs3 = require("fs");
32
33
  var import_node_path2 = __toESM(require("path"));
33
34
  var import_ignore = __toESM(require("ignore"));
34
35
 
@@ -250,7 +251,7 @@ async function buildIgnoreMatcher(workspaceRoot) {
250
251
  ]);
251
252
  const runsecIgnorePath = import_node_path2.default.join(workspaceRoot, RUNSEC_IGNORE_FILE);
252
253
  try {
253
- const content = await import_node_fs2.promises.readFile(runsecIgnorePath, "utf-8");
254
+ const content = await import_node_fs3.promises.readFile(runsecIgnorePath, "utf-8");
254
255
  matcher.add(content);
255
256
  } catch {
256
257
  }
@@ -265,7 +266,7 @@ async function collectFilesWithStats(workspacePath, targetFiles) {
265
266
  let skippedByIgnore = 0;
266
267
  let stat;
267
268
  try {
268
- stat = await import_node_fs2.promises.stat(root);
269
+ stat = await import_node_fs3.promises.stat(root);
269
270
  } catch {
270
271
  stat = null;
271
272
  }
@@ -282,7 +283,7 @@ async function collectFilesWithStats(workspacePath, targetFiles) {
282
283
  continue;
283
284
  }
284
285
  try {
285
- const s = await import_node_fs2.promises.stat(candidate);
286
+ const s = await import_node_fs3.promises.stat(candidate);
286
287
  if (s.isFile()) out.push(candidate);
287
288
  } catch {
288
289
  }
@@ -293,7 +294,7 @@ async function collectFilesWithStats(workspacePath, targetFiles) {
293
294
  const stack = [root];
294
295
  while (stack.length) {
295
296
  const dir = stack.pop();
296
- const entries = await import_node_fs2.promises.readdir(dir, { withFileTypes: true });
297
+ const entries = await import_node_fs3.promises.readdir(dir, { withFileTypes: true });
297
298
  for (const entry of entries) {
298
299
  const full = import_node_path2.default.join(dir, entry.name);
299
300
  const relative = normalizeRelativePath(import_node_path2.default.relative(root, full));
@@ -329,14 +330,16 @@ function findLineByOffset(content, offset) {
329
330
  }
330
331
  return line;
331
332
  }
332
- function extractSnippetByLine(fileContent, line) {
333
+ function extractSnippetFromDisk(absoluteFilePath, line) {
333
334
  try {
334
- const lines = fileContent.split("\n");
335
+ const actualContent = (0, import_node_fs2.readFileSync)(absoluteFilePath, "utf-8");
336
+ const lines = actualContent.split("\n");
335
337
  const start = Math.max(0, line - 2);
336
338
  const end = Math.min(lines.length, line + 2);
337
- return lines.slice(start, end).join("\n").trim() || "// Snippet extraction failed";
338
- } catch {
339
- return "// Error reading code snippet";
339
+ return lines.slice(start, end).join("\n").trim() || "SNIPPET_IS_EMPTY";
340
+ } catch (error) {
341
+ console.error(`Failed to read snippet for ${absoluteFilePath}`, error);
342
+ return "ERROR_READING_FILE";
340
343
  }
341
344
  }
342
345
  function scanContentWithRules(content, file, workspacePath, rules) {
@@ -353,7 +356,7 @@ function scanContentWithRules(content, file, workspacePath, rules) {
353
356
  while ((match = regex.exec(content)) !== null) {
354
357
  const line = findLineByOffset(content, match.index);
355
358
  const snippetMatch = match[0] || "";
356
- const snippet = extractSnippetByLine(content, line);
359
+ const snippet = extractSnippetFromDisk(file, line);
357
360
  localFindings.push({
358
361
  rule_id: rule.id,
359
362
  cwe: rule.cwe,
@@ -384,7 +387,7 @@ async function executeAudit(toolName, args) {
384
387
  fileBatch.map(async (file) => {
385
388
  let fileStat;
386
389
  try {
387
- fileStat = await import_node_fs2.promises.stat(file);
390
+ fileStat = await import_node_fs3.promises.stat(file);
388
391
  } catch {
389
392
  return [];
390
393
  }
@@ -394,7 +397,7 @@ async function executeAudit(toolName, args) {
394
397
  }
395
398
  let content = "";
396
399
  try {
397
- content = await import_node_fs2.promises.readFile(file, "utf-8");
400
+ content = await import_node_fs3.promises.readFile(file, "utf-8");
398
401
  } catch {
399
402
  return [];
400
403
  }
@@ -426,17 +429,19 @@ async function executeAudit(toolName, args) {
426
429
  }
427
430
 
428
431
  // src/engine/reportFormatter.ts
429
- var import_node_fs3 = __toESM(require("fs"));
432
+ var import_node_fs4 = __toESM(require("fs"));
430
433
  var import_node_path3 = __toESM(require("path"));
431
434
  function safeText(value) {
432
435
  return String(value ?? "").replace(/`/g, "'");
433
436
  }
434
- function shouldExcludeFindingFilePath(filePath) {
435
- const n = filePath.replace(/\\/g, "/");
436
- const u = n.toUpperCase();
437
- if (u.includes("SECURITY_AUDIT")) return true;
438
- if (n.includes("/tests/") || n.startsWith("tests/")) return true;
439
- return false;
437
+ function cleanReportFindings(findings) {
438
+ return findings.filter((f) => {
439
+ const ruleId = String(f.rule_id || "").toLowerCase();
440
+ const fp = String(f.file_path || "").replace(/\\/g, "/");
441
+ const isK8sInPython = ruleId.includes("k8s") && fp.toLowerCase().endsWith(".py");
442
+ const isTestOrDoc = fp.includes("SECURITY_AUDIT") || fp.includes("tests/");
443
+ return !isK8sInPython && !isTestOrDoc;
444
+ });
440
445
  }
441
446
  function tierCriticalHighLow(severity) {
442
447
  const x = (severity || "HIGH").toUpperCase();
@@ -448,7 +453,7 @@ function escapeSnippetForBlockquoteFenced(snippet) {
448
453
  return snippet.replace(/```/g, "```");
449
454
  }
450
455
  function buildServerSideReportMarkdown(standard, findings, metrics) {
451
- const rows = findings.filter((f) => !shouldExcludeFindingFilePath(String(f.file_path || "")));
456
+ const rows = cleanReportFindings(findings);
452
457
  let critical = 0;
453
458
  let high = 0;
454
459
  let low = 0;
@@ -467,7 +472,7 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
467
472
  out.push("---");
468
473
  out.push("### Compliance Matrix");
469
474
  out.push(`- **CRITICAL:** ${critical} | **HIGH:** ${high} | **LOW:** ${low}`);
470
- out.push(`- **Reported findings (after excluding SECURITY_AUDIT* paths and tests/):** ${rows.length}`);
475
+ out.push(`- **Reported findings (after excluding K8s-on-.py, SECURITY_AUDIT, tests/):** ${rows.length}`);
471
476
  out.push("");
472
477
  out.push("---");
473
478
  out.push("### Findings");
@@ -481,7 +486,7 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
481
486
  const sev = safeText(finding.severity || "HIGH");
482
487
  const cwe = safeText(finding.cwe || "UNKNOWN");
483
488
  const desc = String(finding.description || "").trim();
484
- const rawSnippet = String(finding.snippet || "").trim() || "// (empty snippet)";
489
+ const rawSnippet = String(finding.snippet ?? "").trim() || "SNIPPET_IS_EMPTY";
485
490
  const sn = escapeSnippetForBlockquoteFenced(rawSnippet);
486
491
  out.push(`#### \`${fp}:${line}\` \u2014 ${rule}`);
487
492
  if (desc) out.push(safeText(desc));
@@ -520,7 +525,7 @@ function generateMarkdownReport(standard, findings, metrics, workspacePath) {
520
525
  const rows = Array.isArray(findings) ? findings : [];
521
526
  const reportContent = buildServerSideReportMarkdown(standard, rows, m);
522
527
  const reportPath = import_node_path3.default.join(process.cwd(), "runsec-report.md");
523
- import_node_fs3.default.writeFileSync(reportPath, reportContent, "utf-8");
528
+ import_node_fs4.default.writeFileSync(reportPath, reportContent, "utf-8");
524
529
  console.error(`[runsec] wrote server-side report to: ${reportPath}`);
525
530
  return `
526
531
  <system_directive>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runsec/mcp",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
6
  "dist",