@runsec/mcp 1.0.3 → 1.0.7

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 +59 -11
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -62,10 +62,16 @@ var STANDARD_TOOL_MAP = {
62
62
  var cachedRegistry = null;
63
63
  var cachedAllRules = null;
64
64
  function toSeverity(value) {
65
- const normalized = (value || "medium").toLowerCase();
66
- if (normalized === "error" || normalized === "critical") return "critical";
67
- if (normalized === "warning" || normalized === "high") return "high";
68
- if (normalized === "low" || normalized === "info") return "low";
65
+ const upper = String(value || "").trim().toUpperCase();
66
+ if (upper === "ERROR" || upper === "CRITICAL") return "critical";
67
+ if (upper === "WARNING") return "medium";
68
+ if (upper === "HIGH") return "high";
69
+ if (upper === "LOW" || upper === "INFO") return "low";
70
+ const lower = String(value || "medium").toLowerCase();
71
+ if (lower === "critical" || lower === "error") return "critical";
72
+ if (lower === "warning") return "medium";
73
+ if (lower === "high") return "high";
74
+ if (lower === "low" || lower === "info") return "low";
69
75
  return "medium";
70
76
  }
71
77
  function escapeRegexLiteral(text) {
@@ -117,17 +123,19 @@ function parseSemgrepRuleFiles() {
117
123
  const id = String(row.id || "").trim();
118
124
  if (!id) continue;
119
125
  const message = String(row.message || "").trim();
120
- const metricId = extractMetricId(id, message);
126
+ const descField = String(row.description || "").trim();
127
+ const metricId = extractMetricId(id, message || descField);
121
128
  const map = compliance[metricId] || {};
122
129
  const cwe = Array.isArray(map.cwe) && map.cwe.length ? map.cwe[0] : "UNKNOWN";
123
130
  const patterns = collectRulePatterns(row);
124
131
  if (patterns.length === 0) continue;
132
+ const description = (message || descField || `RunSec detection ${metricId}`).trim();
125
133
  out.push({
126
134
  id,
127
135
  metricId,
128
136
  cwe,
129
137
  severity: toSeverity(String(row.severity || "")),
130
- description: message || `RunSec detection ${metricId}`,
138
+ description,
131
139
  patterns,
132
140
  sourceFile: fileName
133
141
  });
@@ -205,7 +213,7 @@ function validateRules() {
205
213
  };
206
214
  Object.keys(RULES_REGISTRY).forEach((standard) => {
207
215
  const count = RULES_REGISTRY[standard].length;
208
- console.log(`Loaded ${count} rules for ${standard}`);
216
+ console.error(`Loaded ${count} rules for ${standard}`);
209
217
  if (count <= 0) {
210
218
  throw new Error(`Rules pack for ${standard} is empty`);
211
219
  }
@@ -326,6 +334,21 @@ function findLineByOffset(content, offset) {
326
334
  }
327
335
  return line;
328
336
  }
337
+ var SNIPPET_MAX_LEN = 150;
338
+ function buildMatchSnippet(content, matchIndex) {
339
+ const lines = content.split(/\r?\n/);
340
+ const lineNo1Based = findLineByOffset(content, matchIndex);
341
+ const lineIdx = lineNo1Based - 1;
342
+ const parts = [];
343
+ if (lineIdx > 0) parts.push(lines[lineIdx - 1] ?? "");
344
+ parts.push(lines[lineIdx] ?? "");
345
+ if (lineIdx + 1 < lines.length) parts.push(lines[lineIdx + 1] ?? "");
346
+ let snippet = parts.join("\n");
347
+ if (snippet.length > SNIPPET_MAX_LEN) {
348
+ snippet = `${snippet.slice(0, SNIPPET_MAX_LEN - 1)}\u2026`;
349
+ }
350
+ return snippet;
351
+ }
329
352
  function scanContentWithRules(content, file, workspacePath, rules) {
330
353
  const localFindings = [];
331
354
  for (const rule of rules) {
@@ -339,6 +362,8 @@ function scanContentWithRules(content, file, workspacePath, rules) {
339
362
  let match;
340
363
  while ((match = regex.exec(content)) !== null) {
341
364
  const line = findLineByOffset(content, match.index);
365
+ const snippetMatch = match[0] || "";
366
+ const snippet = buildMatchSnippet(content, match.index);
342
367
  localFindings.push({
343
368
  rule_id: rule.id,
344
369
  cwe: rule.cwe,
@@ -346,7 +371,8 @@ function scanContentWithRules(content, file, workspacePath, rules) {
346
371
  description: rule.description,
347
372
  file_path: import_node_path2.default.relative(import_node_path2.default.resolve(workspacePath), file).replace(/\\/g, "/"),
348
373
  line,
349
- match_text: (match[0] || "").slice(0, 200)
374
+ match_text: snippetMatch.slice(0, 200),
375
+ snippet
350
376
  });
351
377
  }
352
378
  }
@@ -413,10 +439,19 @@ async function executeAudit(toolName, args) {
413
439
  function normalizeSeverity(value) {
414
440
  const s = (value || "").toLowerCase();
415
441
  if (s === "critical" || s === "error") return "critical";
416
- if (s === "high" || s === "warning") return "high";
442
+ if (s === "high") return "high";
443
+ if (s === "warning") return "medium";
417
444
  if (s === "low" || s === "info") return "low";
418
445
  return "medium";
419
446
  }
447
+ function fenceLang(filePath) {
448
+ const ext = (filePath.split(".").pop() || "text").toLowerCase();
449
+ if (!ext || ext === filePath.toLowerCase()) return "text";
450
+ return ext.replace(/[^a-z0-9+#-]/gi, "") || "text";
451
+ }
452
+ function snippetForMarkdownFence(snippet) {
453
+ return snippet.replace(/```/g, "``\\`");
454
+ }
420
455
  function safeText(value) {
421
456
  return String(value ?? "").replace(/`/g, "'");
422
457
  }
@@ -453,7 +488,9 @@ function generateMarkdownReport(standard, findings, metrics) {
453
488
  out.push("");
454
489
  out.push("---");
455
490
  out.push("#### 2. Compliance Matrix");
456
- out.push(`- **Critical:** ${critical.length} | **Medium:** ${medium.length} | **Low:** ${low.length}`);
491
+ out.push(
492
+ `- **Critical:** ${critical.length} | **High:** ${high.length} | **Medium:** ${medium.length} | **Low:** ${low.length}`
493
+ );
457
494
  out.push(`- **Files Scanned:** ${Number(m.scanned_files_count || 0)} | **Skipped:** ${skippedFiles}`);
458
495
  out.push("");
459
496
  out.push("---");
@@ -467,8 +504,19 @@ function generateMarkdownReport(standard, findings, metrics) {
467
504
  const file = safeText(finding.file_path || "unknown_file");
468
505
  const line = Number(finding.line || 0);
469
506
  const description = safeText(finding.description || "No description");
507
+ const sev = safeText((finding.severity || "unknown").toUpperCase());
508
+ const rawSnippet = String(finding.snippet || finding.match_text || "").trim() || "(no snippet)";
509
+ const fenced = snippetForMarkdownFence(rawSnippet);
510
+ const lang = fenceLang(finding.file_path || "");
470
511
  out.push(`**[${cwe}] ${rule} in \`${file}:${line}\`**`);
512
+ out.push(`- **Severity:** ${sev}`);
471
513
  out.push(`- **Description:** ${description}`);
514
+ out.push("> **PoC Snippet:**");
515
+ out.push(`> \`\`\`${lang}`);
516
+ for (const ln of fenced.split("\n")) {
517
+ out.push(`> ${ln}`);
518
+ }
519
+ out.push("> ```");
472
520
  out.push(
473
521
  `- \u{1F4A1} *Prompt to fix:* \`@RunSec Fix the ${rule} vulnerability in ${file}. Ensure it complies with ${safeText(standard)}.\``
474
522
  );
@@ -582,7 +630,7 @@ server.setRequestHandler(import_types.CallToolRequestSchema, async (request) =>
582
630
  });
583
631
  async function main() {
584
632
  const summary = validateRules();
585
- console.log("Rules registry validated:", summary);
633
+ console.error("Rules registry validated:", summary);
586
634
  const transport = new import_stdio.StdioServerTransport();
587
635
  await server.connect(transport);
588
636
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runsec/mcp",
3
- "version": "1.0.3",
3
+ "version": "1.0.7",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
6
  "dist",