@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.
- package/dist/index.js +59 -11
- 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
|
|
66
|
-
if (
|
|
67
|
-
if (
|
|
68
|
-
if (
|
|
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
|
|
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
|
|
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.
|
|
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:
|
|
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"
|
|
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(
|
|
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.
|
|
633
|
+
console.error("Rules registry validated:", summary);
|
|
586
634
|
const transport = new import_stdio.StdioServerTransport();
|
|
587
635
|
await server.connect(transport);
|
|
588
636
|
}
|