guardvibe 3.0.3 → 3.0.6

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.
@@ -0,0 +1 @@
1
+ export declare function runCheckCmd(args: string[]): Promise<void>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * CLI: guardvibe check-cmd "<command>"
3
+ * Check if a shell command is safe to execute.
4
+ */
5
+ import { parseArgs } from "./args.js";
6
+ import { checkCommand } from "../tools/check-command.js";
7
+ export async function runCheckCmd(args) {
8
+ const { flags, positional } = parseArgs(args);
9
+ const command = positional.join(" ");
10
+ if (!command) {
11
+ console.error(' [ERR] Please specify a command: npx guardvibe check-cmd "rm -rf /"');
12
+ process.exit(1);
13
+ }
14
+ const format = (flags.format === "json" ? "json" : "markdown");
15
+ const result = checkCommand(command, process.cwd(), undefined, format);
16
+ console.log(result);
17
+ }
@@ -0,0 +1 @@
1
+ export declare function runExplain(args: string[]): Promise<void>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * CLI: guardvibe explain <ruleId>
3
+ * Get detailed remediation guidance for a security rule.
4
+ */
5
+ import { parseArgs } from "./args.js";
6
+ import { explainRemediation } from "../tools/explain-remediation.js";
7
+ export async function runExplain(args) {
8
+ const { flags, positional } = parseArgs(args);
9
+ const ruleId = positional[0];
10
+ if (!ruleId) {
11
+ console.error(" [ERR] Please specify a rule ID: npx guardvibe explain VG154");
12
+ process.exit(1);
13
+ }
14
+ const format = (flags.format === "json" ? "json" : "markdown");
15
+ const result = explainRemediation(ruleId, undefined, format);
16
+ console.log(result);
17
+ }
@@ -0,0 +1 @@
1
+ export declare function runFix(args: string[]): Promise<void>;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * CLI: guardvibe fix <file>
3
+ * Get security fix suggestions for a file.
4
+ */
5
+ import { readFileSync } from "fs";
6
+ import { resolve, extname } from "path";
7
+ import { parseArgs } from "./args.js";
8
+ import { fixCode } from "../tools/fix-code.js";
9
+ import { EXTENSION_MAP } from "../utils/constants.js";
10
+ export async function runFix(args) {
11
+ const { flags, positional } = parseArgs(args);
12
+ const filePath = positional[0];
13
+ if (!filePath) {
14
+ console.error(" [ERR] Please specify a file: npx guardvibe fix src/app/api/route.ts");
15
+ process.exit(1);
16
+ }
17
+ const resolved = resolve(filePath);
18
+ let content;
19
+ try {
20
+ content = readFileSync(resolved, "utf-8");
21
+ }
22
+ catch {
23
+ console.error(` [ERR] Could not read file: ${resolved}`);
24
+ process.exit(1);
25
+ }
26
+ const ext = extname(resolved).toLowerCase();
27
+ const language = EXTENSION_MAP[ext];
28
+ if (!language) {
29
+ console.error(` [ERR] Unsupported file type: ${ext}`);
30
+ process.exit(1);
31
+ }
32
+ const format = (flags.format === "json" ? "json" : "markdown");
33
+ const result = fixCode(content, language, undefined, resolved, format);
34
+ console.log(result);
35
+ }
package/build/cli.js CHANGED
@@ -23,6 +23,9 @@ function printUsage() {
23
23
  npx guardvibe check <file> Scan a single file for security issues
24
24
  npx guardvibe doctor [path] Run host security audit
25
25
  npx guardvibe audit [path] Full security audit with PASS/FAIL verdict
26
+ npx guardvibe explain <ruleId> Get detailed remediation guidance for a rule
27
+ npx guardvibe fix <file> Get security fix suggestions for a file
28
+ npx guardvibe check-cmd "<cmd>" Check if a shell command is safe to execute
26
29
  npx guardvibe init <platform> Setup MCP server configuration
27
30
  npx guardvibe hook install Install pre-commit security hook
28
31
  npx guardvibe hook uninstall Remove pre-commit security hook
@@ -127,6 +130,18 @@ async function main() {
127
130
  const { runAudit } = await import("./cli/audit.js");
128
131
  await runAudit(subArgs);
129
132
  }
133
+ else if (command === "explain") {
134
+ const { runExplain } = await import("./cli/explain.js");
135
+ await runExplain(subArgs);
136
+ }
137
+ else if (command === "fix") {
138
+ const { runFix } = await import("./cli/fix.js");
139
+ await runFix(subArgs);
140
+ }
141
+ else if (command === "check-cmd") {
142
+ const { runCheckCmd } = await import("./cli/check-cmd.js");
143
+ await runCheckCmd(subArgs);
144
+ }
130
145
  else {
131
146
  console.error(` Unknown command: ${command}`);
132
147
  printUsage();
package/build/index.js CHANGED
@@ -43,13 +43,25 @@ import { fixCode as fixCodeTool } from "./tools/fix-code.js";
43
43
  import { analyzeAuthCoverage, formatAuthCoverage } from "./tools/auth-coverage.js";
44
44
  import { buildDeepScanPrompt, parseDeepScanResult, formatDeepScanFindings, callLLM } from "./tools/deep-scan.js";
45
45
  import { runFullAudit, formatAuditResult } from "./tools/full-audit.js";
46
+ // Helper: merge stats summary into JSON output instead of concatenating two JSON objects
47
+ function mergeStatsIntoOutput(results, summary, format) {
48
+ if (format === "json" && summary) {
49
+ try {
50
+ const parsed = JSON.parse(results);
51
+ const stats = JSON.parse(summary);
52
+ return JSON.stringify({ ...parsed, _meta: stats.guardvibeStats ?? stats });
53
+ }
54
+ catch { /* fall through */ }
55
+ }
56
+ return results + summary;
57
+ }
46
58
  const server = new McpServer({
47
59
  name: "guardvibe",
48
60
  version: pkg.version,
49
61
  description: "Security MCP for vibe coding — single source of truth for AI assistants. 334 security rules and 34 tools. Use full_audit for a comprehensive PASS/FAIL/WARN verdict with deterministic result hash, coverage %, and unified report across code, secrets, dependencies, config, taint analysis, and auth coverage. Same code = same hash = same results regardless of which AI assistant runs it. Covers OWASP, Next.js, Supabase, Stripe, Clerk, Prisma, Hono, AI SDK, MCP server security, host hardening. Maps to SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, EU AI Act. Runs 100% locally with zero configuration.",
50
62
  });
51
63
  // Tool 1: Analyze code for security vulnerabilities
52
- server.tool("check_code", "Analyze code for security vulnerabilities (OWASP Top 10, XSS, SQL injection, insecure patterns). Use this when reviewing or writing code to catch security issues early.", {
64
+ server.tool("check_code", "Analyze inline code for security vulnerabilities (OWASP Top 10, XSS, SQL injection, insecure patterns). Pass code as a string parameter. For scanning files on disk, use scan_file instead. Example: check_code({code: 'app.get(...)', language: 'javascript'})", {
53
65
  code: z.string().describe("The code snippet to analyze"),
54
66
  language: z
55
67
  .enum(["javascript", "typescript", "python", "go", "dockerfile", "html", "sql", "shell", "yaml", "terraform", "firestore"])
@@ -67,7 +79,7 @@ server.tool("check_code", "Analyze code for security vulnerabilities (OWASP Top
67
79
  recordScan(cwd, { toolName: "check_code", filesScanned: 1, findings: findings.map(f => ({ severity: f.rule.severity, ruleId: f.rule.id })) });
68
80
  const summary = getSummaryLine(cwd, findings.length, format);
69
81
  return {
70
- content: [{ type: "text", text: results + summary }],
82
+ content: [{ type: "text", text: mergeStatsIntoOutput(results, summary, format) }],
71
83
  };
72
84
  });
73
85
  // Tool 2: Scan entire project for security vulnerabilities
@@ -100,7 +112,7 @@ server.tool("check_project", "Scan multiple files for security vulnerabilities a
100
112
  }
101
113
  const summary = getSummaryLine(cwd, findingCount, format);
102
114
  return {
103
- content: [{ type: "text", text: results + summary }],
115
+ content: [{ type: "text", text: mergeStatsIntoOutput(results, summary, format) }],
104
116
  };
105
117
  });
106
118
  // Tool 3: Get security documentation and best practices (renumbered from Tool 2)
@@ -146,7 +158,7 @@ server.tool("check_dependencies", "Check npm, PyPI, or Go packages for known sec
146
158
  }
147
159
  });
148
160
  // Tool 5: Scan directory for security vulnerabilities (filesystem-native)
149
- server.tool("scan_directory", "Scan an entire project directory for security vulnerabilities. Reads files directly from the filesystem no need to pass file contents. Returns a security score (A-F) and detailed findings. Includes scan metadata (ID, timestamp, duration, file hashes) for audit trails. Use baseline to compare with a previous scan.", {
161
+ server.tool("scan_directory", "Scan all files in a directory on disk for security vulnerabilities. Pass a directory pathreads files from filesystem. Returns security score (A-F) and findings. Results may be truncated for large projects check fileRanking in JSON output for top files. Example: scan_directory({path: './src'})", {
150
162
  path: z.string().describe("Directory path to scan (e.g. './src', '.')"),
151
163
  recursive: z.boolean().optional().default(true).describe("Scan subdirectories"),
152
164
  exclude: z.array(z.string()).optional().default([]).describe("Additional directories to exclude"),
@@ -174,7 +186,7 @@ server.tool("scan_directory", "Scan an entire project directory for security vul
174
186
  recordScan(root, { toolName: "scan_directory", filesScanned: 0, findings: [] });
175
187
  }
176
188
  const summary = getSummaryLine(root, findingCount, format);
177
- return { content: [{ type: "text", text: results + summary }] };
189
+ return { content: [{ type: "text", text: mergeStatsIntoOutput(results, summary, format) }] };
178
190
  });
179
191
  // Tool 6: Scan manifest/lockfile for dependency vulnerabilities
180
192
  server.tool("scan_dependencies", "Parse a lockfile or manifest (package.json, package-lock.json, requirements.txt, go.mod) and check all dependencies for known CVEs via the OSV database. Reads the file directly. Use this after installing dependencies, during CI, or when auditing existing projects for vulnerable packages.", {
@@ -239,10 +251,10 @@ server.tool("scan_staged", "Scan git-staged files for security vulnerabilities b
239
251
  recordScan(cwd, { toolName: "scan_staged", filesScanned: 0, findings: [] });
240
252
  }
241
253
  const summary = getSummaryLine(cwd, findingCount, format);
242
- return { content: [{ type: "text", text: results + summary }] };
254
+ return { content: [{ type: "text", text: mergeStatsIntoOutput(results, summary, format) }] };
243
255
  });
244
256
  // Tool 9: Generate compliance-focused security report
245
- server.tool("compliance_report", "Map security scan findings to compliance framework controls (SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, EUAIACT). Scans a directory and groups code-level security issues by the compliance control they relate to. Includes exploit scenarios and remediation evidence for each finding. Use mode=executive for a risk summary. Note: this maps code vulnerabilities to controls — it does not perform a compliance audit or replace professional assessment.", {
257
+ server.tool("compliance_report", "Map security findings to compliance controls (SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, EUAIACT). Scans a directory and groups issues by control. Output includes a summary section at the top; for large projects, findings are truncated to top 50. Use mode=executive for C-level summary. Example: compliance_report({path: '.', framework: 'SOC2'})", {
246
258
  path: z.string().describe("Directory to scan"),
247
259
  framework: z.enum(["SOC2", "PCI-DSS", "HIPAA", "GDPR", "ISO27001", "EUAIACT", "all"]).describe("Compliance framework"),
248
260
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (human) or json (machine-readable for agents)"),
@@ -275,7 +287,7 @@ server.tool("check_package_health", "Check npm packages for typosquat risk, main
275
287
  }
276
288
  });
277
289
  // Tool 12: Auto-fix security vulnerabilities
278
- server.tool("fix_code", "Analyze code for security vulnerabilities and return fix suggestions with concrete patches. The AI agent can apply these patches to automatically fix issues. Returns structured fix data including before/after code, severity, and line numbers.", {
290
+ server.tool("fix_code", "Pass vulnerable code as a string and get fix suggestions with before/after patches. Returns structured edit instructions (line numbers, severity, confidence). Use verify_fix afterwards to confirm the fix resolved the issue. Example: fix_code({code: '...', language: 'typescript'})", {
279
291
  code: z.string().describe("The code snippet to analyze and fix"),
280
292
  language: z
281
293
  .enum(["javascript", "typescript", "python", "go", "dockerfile", "html", "sql", "shell", "yaml", "terraform", "firestore"])
@@ -301,7 +313,7 @@ server.tool("fix_code", "Analyze code for security vulnerabilities and return fi
301
313
  };
302
314
  });
303
315
  // Tool 13: Cross-file configuration security audit
304
- server.tool("audit_config", "Audit project configuration files (next.config, middleware/proxy, .env, vercel.json) together for cross-file security issues. Detects gaps that single-file scanning misses: missing security headers, unprotected routes, exposed secrets, middleware/route mismatches.", {
316
+ server.tool("audit_config", "Audit application config files (next.config, middleware, .env, vercel.json) for cross-file security gaps: missing headers, unprotected routes, exposed secrets. NOT the same as guardvibe_doctor which checks AI host security (MCP configs, hooks). Example: audit_config({path: '.'})", {
305
317
  path: z.string().describe("Project root directory to audit"),
306
318
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
307
319
  }, async ({ path, format }) => {
@@ -362,16 +374,66 @@ server.tool("analyze_dataflow", "Track user input (request body, URL params, for
362
374
  return { content: [{ type: "text", text: results }] };
363
375
  });
364
376
  // Tool 18b: Cross-File Taint/Dataflow Analysis
365
- server.tool("analyze_cross_file_dataflow", "Track user input flowing across module boundaries — detects injection vulnerabilities that span multiple files. Resolves imports/exports, builds a module graph, and follows tainted data from HTTP handlers through helper functions to dangerous sinks (SQL, eval, redirect, file ops). Pass all related files for best results.", {
377
+ server.tool("analyze_cross_file_dataflow", "Track user input flowing across module boundaries — detects injection vulnerabilities spanning multiple files. Pass files array with file contents. For single-file analysis, use analyze_dataflow instead. Example: analyze_cross_file_dataflow({files: [{path: 'src/api.ts', content: '...'}, {path: 'src/db.ts', content: '...'}]})", {
378
+ path: z.string().optional().describe("Project directory path. When provided, auto-discovers all JS/TS files — no need to pass file contents manually."),
366
379
  files: z
367
380
  .array(z.object({
368
381
  path: z.string().describe("Relative file path (e.g. src/lib/db.ts)"),
369
382
  content: z.string().describe("File source code"),
370
383
  }))
371
- .describe("List of files to analyze: [{path, content}]"),
384
+ .default([])
385
+ .describe("List of files to analyze (ignored when path is provided)"),
372
386
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
373
- }, async ({ files, format }) => {
374
- const { crossFileFindings, perFileFindings } = analyzeCrossFileTaint(files);
387
+ }, async ({ path, files, format }) => {
388
+ let analysisFiles = files;
389
+ if (path) {
390
+ const { readdirSync, readFileSync, statSync } = await import("fs");
391
+ const { resolve: resolvePath } = await import("path");
392
+ const jsFiles = [];
393
+ const skip = new Set(["node_modules", ".git", ".next", "build", "dist", ".turbo", "coverage"]);
394
+ function walk(d) {
395
+ if (jsFiles.length >= 500)
396
+ return;
397
+ let entries;
398
+ try {
399
+ entries = readdirSync(d);
400
+ }
401
+ catch {
402
+ return;
403
+ }
404
+ for (const entry of entries) {
405
+ if (jsFiles.length >= 500)
406
+ return;
407
+ if (skip.has(entry))
408
+ continue;
409
+ const full = resolvePath(d, entry);
410
+ let stat;
411
+ try {
412
+ stat = statSync(full);
413
+ }
414
+ catch {
415
+ continue;
416
+ }
417
+ if (stat.isDirectory()) {
418
+ walk(full);
419
+ continue;
420
+ }
421
+ if (!/\.(ts|tsx|js|jsx)$/.test(entry))
422
+ continue;
423
+ if (stat.size > 100_000)
424
+ continue;
425
+ try {
426
+ const content = readFileSync(full, "utf-8");
427
+ const relPath = full.replace(resolvePath(path) + "/", "");
428
+ jsFiles.push({ path: relPath, content });
429
+ }
430
+ catch { /* skip unreadable */ }
431
+ }
432
+ }
433
+ walk(resolvePath(path));
434
+ analysisFiles = jsFiles;
435
+ }
436
+ const { crossFileFindings, perFileFindings } = analyzeCrossFileTaint(analysisFiles);
375
437
  const total = crossFileFindings.length + Array.from(perFileFindings.values()).reduce((sum, f) => sum + f.length, 0);
376
438
  if (total === 0) {
377
439
  if (format === "json")
@@ -410,7 +472,7 @@ server.tool("repo_security_posture", "Analyze a repository's overall security po
410
472
  return { content: [{ type: "text", text: results }] };
411
473
  });
412
474
  // Tool 22: Explain Remediation
413
- server.tool("explain_remediation", "Deep explanation of a security finding: why it's risky, real-world impact, exploit scenario, minimum fix, secure alternative, breaking risk assessment, and test strategy. Helps agents apply fixes correctly.", {
475
+ server.tool("explain_remediation", "Pass a GuardVibe rule ID (e.g. VG154) to get a detailed explanation: risk assessment, exploit scenario, minimum fix, secure alternative, and test strategy. Optionally pass the affected code snippet for context-aware guidance. Example: explain_remediation({rule_id: 'VG402'})", {
414
476
  rule_id: z.string().describe("GuardVibe rule ID (e.g. VG001, VG402)"),
415
477
  code: z.string().optional().describe("Affected code snippet for context"),
416
478
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
@@ -420,7 +482,7 @@ server.tool("explain_remediation", "Deep explanation of a security finding: why
420
482
  return { content: [{ type: "text", text: results }] };
421
483
  });
422
484
  // Tool 23: Quick file scan — designed for real-time integration
423
- server.tool("scan_file", "Scan a single file from disk for security vulnerabilities. Returns only findings (no boilerplate). Designed for real-time use: call this after editing a file to catch security issues immediately. Lightweight and fast — reads the file, detects language, and returns findings in JSON.", {
485
+ server.tool("scan_file", "Scan a single file on disk by path for security vulnerabilities. Pass a file path the tool reads the file itself. For inline code snippets, use check_code instead. Example: scan_file({file_path: 'src/api/route.ts'})", {
424
486
  file_path: z.string().describe("Absolute or relative path to the file to scan"),
425
487
  format: z.enum(["markdown", "json"]).default("json").describe("Output format"),
426
488
  }, async ({ file_path, format }) => {
@@ -459,9 +521,9 @@ server.tool("scan_file", "Scan a single file from disk for security vulnerabilit
459
521
  confidence: f.confidence,
460
522
  effort: f.effort,
461
523
  })).filter((f) => f.edit);
462
- return { content: [{ type: "text", text: JSON.stringify(parsed) + summary }] };
524
+ return { content: [{ type: "text", text: mergeStatsIntoOutput(JSON.stringify(parsed), summary, format) }] };
463
525
  }
464
- return { content: [{ type: "text", text: result + summary }] };
526
+ return { content: [{ type: "text", text: mergeStatsIntoOutput(result, summary, format) }] };
465
527
  });
466
528
  // Tool 24: Scan changed files only — for incremental CI/CD and PR workflows
467
529
  server.tool("scan_changed_files", "Scan only files that have changed since a given git ref (branch, commit, or HEAD~N). Ideal for PR checks, pre-push hooks, and incremental CI. Returns findings only for modified/added files.", {
@@ -522,10 +584,10 @@ server.tool("scan_changed_files", "Scan only files that have changed since a giv
522
584
  const critical = allFindings.filter(f => f.severity === "critical").length;
523
585
  const high = allFindings.filter(f => f.severity === "high").length;
524
586
  const medium = allFindings.filter(f => f.severity === "medium").length;
525
- return { content: [{ type: "text", text: JSON.stringify({
587
+ return { content: [{ type: "text", text: mergeStatsIntoOutput(JSON.stringify({
526
588
  summary: { total: allFindings.length, critical, high, medium, low: 0, blocked: critical > 0 || high > 0, changedFiles: changedFiles.length },
527
589
  findings: allFindings,
528
- }) + statsSummary }] };
590
+ }), statsSummary, format) }] };
529
591
  }
530
592
  // Markdown
531
593
  const lines = [`# GuardVibe Changed Files Report`, ``, `Base: ${base}`, `Changed files: ${changedFiles.length}`, `Issues found: ${allFindings.length}`, ``];
@@ -539,7 +601,7 @@ server.tool("scan_changed_files", "Scan only files that have changed since a giv
539
601
  lines.push(`- [${f.severity.toUpperCase()}] **${f.name}** (${f.id}) in \`${f.file}\`:${f.line} — ${f.fix}`);
540
602
  }
541
603
  }
542
- return { content: [{ type: "text", text: lines.join("\n") + statsSummary }] };
604
+ return { content: [{ type: "text", text: mergeStatsIntoOutput(lines.join("\n"), statsSummary, format) }] };
543
605
  });
544
606
  // Tool 25: Security statistics dashboard
545
607
  server.tool("security_stats", "Show cumulative security statistics, grade trend, and vulnerability fix progress for this project. Use this to demonstrate the value of GuardVibe security scanning over time. Data is stored locally in .guardvibe/stats.json.", {
@@ -576,7 +638,7 @@ server.tool("scan_host_config", "Scan host environment for AI security issues: A
576
638
  return { content: [{ type: "text", text: redactSecrets(output) }] };
577
639
  });
578
640
  // Tool 28: Unified host hardening scanner (doctor)
579
- server.tool("guardvibe_doctor", "Comprehensive AI host security audit. Scans MCP configurations, hooks, environment variables, shell profiles, and permissions for known attack vectors (CVE-2025-59536 hook injection, CVE-2026-21852 base URL hijack, tool result injection, supply chain attacks). Reports trust state, verdict, and confidence for each finding. Supports allowlists via .guardviberc. Use scope=project (default) for project-only scan, scope=host to include shell profiles and global configs.", {
641
+ server.tool("guardvibe_doctor", "Check AI host security: MCP configurations, hooks, base URL hijacking, environment variable exposure. NOT the same as audit_config which checks application config files (next.config, .env, headers). Use scope=project (default) for project-only, scope=host to include shell profiles and global AI configs. Example: guardvibe_doctor({scope: 'project'})", {
580
642
  path: z.string().default(".").describe("Project root directory"),
581
643
  scope: z.enum(["project", "host", "full"]).default("project").describe("Scan scope: project (default, .claude.json + .cursor/ + .vscode/ + .env), host (+ shell profiles + global MCP configs), full (+ home dir configs)"),
582
644
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (human) or json (machine-readable)"),
@@ -596,7 +658,7 @@ server.tool("verify_fix", "Verify that a specific security fix was applied corre
596
658
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
597
659
  });
598
660
  // Tool 30: Security workflow guide for AI agents
599
- server.tool("security_workflow", "Get the recommended GuardVibe security workflow for your current task. Returns which tools to call, in what order, and with what parameters. Use this when you're unsure which GuardVibe tool to use.", {
661
+ server.tool("security_workflow", "Get the recommended GuardVibe tool sequence for your current task. Returns which tools to call, in what order, and with what parameters. Use this when unsure which tool to use. Example: security_workflow({task: 'pre_commit'})", {
600
662
  task: z.enum([
601
663
  "writing_code",
602
664
  "pre_commit",
@@ -609,7 +671,7 @@ server.tool("security_workflow", "Get the recommended GuardVibe security workflo
609
671
  "publish_package",
610
672
  "security_audit",
611
673
  "incident_response",
612
- ]).describe("What you are currently doing"),
674
+ ]).describe("Current task: writing_code (after edits), pre_commit (before commit), pr_review (reviewing PR), new_project (initial setup), fix_vulnerabilities (fixing known issues), compliance_mapping (audit against framework), dependency_check (check deps), merge_to_main (pre-merge gate), publish_package (pre-publish checks), security_audit (comprehensive audit), incident_response (post-breach investigation)"),
613
675
  }, async ({ task }) => {
614
676
  const workflows = {
615
677
  writing_code: {
@@ -715,14 +777,67 @@ server.tool("security_workflow", "Get the recommended GuardVibe security workflo
715
777
  return { content: [{ type: "text", text: JSON.stringify(workflows[task]) }] };
716
778
  });
717
779
  // Tool 31: Auth coverage map
718
- server.tool("auth_coverage", "Analyze authentication coverage across all Next.js App Router routes. Enumerates API routes and pages, parses middleware matchers, detects auth guards (Clerk, NextAuth, Supabase, custom), and reports which routes are protected vs unprotected. Use this to find gaps in your auth layer.", {
780
+ server.tool("auth_coverage", "Analyze authentication coverage across Next.js App Router routes. Detects auth guards (Clerk, NextAuth, Supabase, custom) and reports protected vs unprotected routes. Pass files array with route file contents and middleware content. Example: auth_coverage({files: [{path: 'app/api/users/route.ts', content: '...'}], middleware: '...'})", {
781
+ path: z.string().optional().describe("Project directory path. When provided, auto-discovers all route, page, layout, and middleware files — no need to pass file contents manually."),
719
782
  files: z.array(z.object({
720
783
  path: z.string().describe("File path relative to project root (e.g. app/api/users/route.ts)"),
721
784
  content: z.string().describe("File source code"),
722
- })).describe("Route and page files from app/ directory"),
723
- middleware: z.string().default("").describe("Content of middleware.ts file (empty if none)"),
785
+ })).default([]).describe("Route and page files from app/ directory (ignored when path is provided)"),
786
+ middleware: z.string().default("").describe("Content of middleware.ts file (ignored when path is provided)"),
724
787
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
725
- }, async ({ files, middleware, format }) => {
788
+ }, async ({ path, files, middleware, format }) => {
789
+ if (path) {
790
+ const { readdirSync, readFileSync, statSync } = await import("fs");
791
+ const { resolve: resolvePath } = await import("path");
792
+ const jsFiles = [];
793
+ const skip = new Set(["node_modules", ".git", ".next", "build", "dist", ".turbo", "coverage"]);
794
+ function walk(d) {
795
+ if (jsFiles.length >= 500)
796
+ return;
797
+ let entries;
798
+ try {
799
+ entries = readdirSync(d);
800
+ }
801
+ catch {
802
+ return;
803
+ }
804
+ for (const entry of entries) {
805
+ if (jsFiles.length >= 500)
806
+ return;
807
+ if (skip.has(entry))
808
+ continue;
809
+ const full = resolvePath(d, entry);
810
+ let stat;
811
+ try {
812
+ stat = statSync(full);
813
+ }
814
+ catch {
815
+ continue;
816
+ }
817
+ if (stat.isDirectory()) {
818
+ walk(full);
819
+ continue;
820
+ }
821
+ if (!/\.(ts|tsx|js|jsx)$/.test(entry))
822
+ continue;
823
+ if (stat.size > 100_000)
824
+ continue;
825
+ try {
826
+ const content = readFileSync(full, "utf-8");
827
+ const relPath = full.replace(resolvePath(path) + "/", "");
828
+ jsFiles.push({ path: relPath, content });
829
+ }
830
+ catch { /* skip unreadable */ }
831
+ }
832
+ }
833
+ walk(resolvePath(path));
834
+ const routeFiles = jsFiles.filter(f => /\/(route|page)\.(ts|tsx|js|jsx)$/.test(f.path));
835
+ const layoutFiles = jsFiles.filter(f => /\/layout\.(ts|tsx|js|jsx)$/.test(f.path));
836
+ const middlewareFile = jsFiles.find(f => /middleware\.(ts|js)$/.test(f.path));
837
+ const report = analyzeAuthCoverage(routeFiles, middlewareFile?.content ?? "", layoutFiles);
838
+ const output = formatAuthCoverage(report, format);
839
+ return { content: [{ type: "text", text: output }] };
840
+ }
726
841
  const report = analyzeAuthCoverage(files, middleware);
727
842
  const output = formatAuthCoverage(report, format);
728
843
  return { content: [{ type: "text", text: output }] };
@@ -750,7 +865,7 @@ server.tool("deep_scan", "LLM-powered deep security analysis for vulnerabilities
750
865
  return { content: [{ type: "text", text: output }] };
751
866
  });
752
867
  // Tool 33: Full audit — single source of truth
753
- server.tool("full_audit", "Single source of truth security audit. Runs ALL security checks in one call: code scan (334 rules), secret detection, dependency CVE check, config audit, cross-file taint analysis, and auth coverage mapping. Returns a deterministic PASS/FAIL/WARN verdict, unified score, coverage %, and a result hash (same code = same hash, always). Use this instead of running individual tools when you need a comprehensive security assessment. Trust the verdict do not add speculative security concerns beyond what this tool reports.", {
868
+ server.tool("full_audit", "Single command that runs ALL checks: code scan (334 rules), secret detection, dependency CVEs, config audit, taint analysis, and auth coverage. Returns PASS/FAIL/WARN verdict with deterministic hash. Use this for comprehensive security assessment — no need to call individual tools separately. Example: full_audit({path: '.'})", {
754
869
  path: z.string().default(".").describe("Project root directory"),
755
870
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
756
871
  skipDeps: z.boolean().default(false).describe("Skip dependency vulnerability check"),
@@ -79,10 +79,12 @@ export function enumerateRoutes(files) {
79
79
  * Returns array of matcher patterns.
80
80
  */
81
81
  export function parseMiddlewareMatchers(content) {
82
- const stringMatch = /matcher\s*:\s*"([^"]+)"/.exec(content);
82
+ // Normalize literal escape sequences that AI assistants may pass
83
+ const normalized = content.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
84
+ const stringMatch = /matcher\s*:\s*"([^"]+)"/.exec(normalized);
83
85
  if (stringMatch)
84
86
  return [stringMatch[1]];
85
- const arrayMatch = /matcher\s*:\s*\[([^\]]+)\]/.exec(content);
87
+ const arrayMatch = /matcher\s*:\s*\[([^\]]+)\]/.exec(normalized);
86
88
  if (arrayMatch) {
87
89
  return arrayMatch[1]
88
90
  .split(",")
@@ -63,12 +63,37 @@ export function complianceReport(path, framework, format = "markdown", rules, mo
63
63
  const critical = relevant.filter(f => f.rule.severity === "critical").length;
64
64
  const high = relevant.filter(f => f.rule.severity === "high").length;
65
65
  const medium = relevant.filter(f => f.rule.severity === "medium").length;
66
+ // Count controls that have zero findings as "passed"
67
+ const totalControls = controlMap.size;
68
+ const failedControls = totalControls; // all mapped controls have findings
69
+ const passedControls = 0; // controls without findings are not in the map
70
+ const complianceScore = totalControls === 0 ? 100 : Math.round((passedControls / totalControls) * 100);
71
+ // Flatten all findings, sort by severity, limit to top 50
72
+ const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
73
+ const allFindingsFlat = relevant
74
+ .map(f => ({
75
+ id: f.rule.id, name: f.rule.name, severity: f.rule.severity,
76
+ file: f.filePath, line: f.line,
77
+ exploit: f.rule.exploit, audit: f.rule.audit,
78
+ }))
79
+ .sort((a, b) => (severityOrder[a.severity] ?? 4) - (severityOrder[b.severity] ?? 4));
80
+ const MAX_JSON_FINDINGS = 50;
81
+ const truncated = allFindingsFlat.length > MAX_JSON_FINDINGS;
82
+ const topFindings = allFindingsFlat.slice(0, MAX_JSON_FINDINGS);
66
83
  return JSON.stringify({
67
84
  summary: {
68
- framework, total: relevant.length, controls: controlMap.size,
69
- critical, high, medium, mode,
85
+ framework,
86
+ totalControls,
87
+ passedControls,
88
+ failedControls,
89
+ totalFindings: relevant.length,
90
+ critical, high, medium,
91
+ complianceScore,
92
+ mode,
70
93
  },
71
94
  controls,
95
+ findings: topFindings,
96
+ ...(truncated ? { truncation: { shown: MAX_JSON_FINDINGS, total: allFindingsFlat.length, note: "Sorted by severity. Use scan_file on individual files for full details." } } : {}),
72
97
  });
73
98
  }
74
99
  // --- EXECUTIVE SUMMARY MODE ---
@@ -73,7 +73,13 @@ export async function scanDependencies(manifestPath, format = "markdown") {
73
73
  }
74
74
  }
75
75
  return JSON.stringify({
76
- summary: { total: packages.length, vulnerable: criticalPackages.length, ...sevCounts },
76
+ summary: {
77
+ total: packages.length,
78
+ vulnerable: criticalPackages.length,
79
+ vulnerablePackages: criticalPackages.length,
80
+ totalAdvisories: totalVulns,
81
+ ...sevCounts,
82
+ },
77
83
  packages: pkgResults,
78
84
  });
79
85
  }
@@ -95,6 +95,15 @@ export function scanDirectory(path, recursive = true, exclude = [], format = "ma
95
95
  filesSkipped: skippedFiles.length,
96
96
  fileHashes,
97
97
  };
98
+ // File ranking — always included in JSON, even when truncated
99
+ const fileRankingMap = new Map();
100
+ for (const r of scanResults) {
101
+ fileRankingMap.set(r.path, r.findings.length);
102
+ }
103
+ const fileRanking = Array.from(fileRankingMap.entries())
104
+ .sort((a, b) => b[1] - a[1])
105
+ .slice(0, 10)
106
+ .map(([file, count]) => ({ file, findingCount: count }));
98
107
  // Scoring
99
108
  const allFindings = scanResults.flatMap(r => r.findings);
100
109
  const totalCritical = allFindings.filter(f => f.rule.severity === "critical").length;
@@ -155,6 +164,7 @@ export function scanDirectory(path, recursive = true, exclude = [], format = "ma
155
164
  },
156
165
  },
157
166
  metadata,
167
+ fileRanking,
158
168
  findings: limitedFindings.map(f => ({
159
169
  id: f.rule.id, name: f.rule.name, severity: f.rule.severity,
160
170
  owasp: f.rule.owasp, line: f.line, match: f.match, file: f.file,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "3.0.3",
3
+ "version": "3.0.6",
4
4
  "mcpName": "io.github.goklab/guardvibe",
5
5
  "description": "Security MCP for vibe coding. 334 rules, 34 tools, CLI + doctor. Host security, auth coverage mapping, LLM-powered deep scan (IDOR/business logic), taint analysis. Plus Next.js, Supabase, Clerk, Stripe, Prisma, tRPC, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK, and the full AI-generated stack.",
6
6
  "type": "module",