guardvibe 2.8.0 → 2.9.1

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/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![npm provenance](https://img.shields.io/badge/provenance-verified-brightgreen)](https://www.npmjs.com/package/guardvibe)
7
7
  [![codecov](https://codecov.io/gh/goklab/guardvibe/graph/badge.svg)](https://codecov.io/gh/goklab/guardvibe)
8
8
 
9
- **The security MCP built for vibe coding.** 334 security rules, 29 tools covering the entire AI-generated code journey — from first line to production deployment.
9
+ **The security MCP built for vibe coding.** 334 security rules, 31 tools covering the entire AI-generated code journey — from first line to production deployment.
10
10
 
11
11
  Works with **Claude Code, Cursor, Gemini CLI, Codex, VS Code (Copilot), Windsurf**, and any MCP-compatible coding agent.
12
12
 
@@ -14,7 +14,7 @@ Works with **Claude Code, Cursor, Gemini CLI, Codex, VS Code (Copilot), Windsurf
14
14
 
15
15
  Most security tools are built for enterprise security teams. GuardVibe is built for **you** — the developer using AI to build and ship web apps fast.
16
16
 
17
- - **334 security rules, 29 tools** purpose-built for the stacks AI agents generate
17
+ - **334 security rules, 31 tools** purpose-built for the stacks AI agents generate
18
18
  - **Zero setup friction** — `npx guardvibe` and you're scanning
19
19
  - **No account required** — runs 100% locally, no API keys, no cloud
20
20
  - **Understands your stack** — not generic SAST, but rules that know Next.js, Supabase, Stripe, Clerk, and the tools you actually use
@@ -177,13 +177,13 @@ Dockerfile security, GitHub Actions CI/CD, Terraform (S3, IAM, RDS, security gro
177
177
  ### Secrets & Environment
178
178
  API keys (AWS, GitHub, Stripe, OpenAI, Resend, Turso), .env management, .gitignore coverage, high-entropy detection, NEXT_PUBLIC exposure
179
179
 
180
- ### Compliance
181
- SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, EU AI Act (EUAIACT) control mapping with compliance reports
180
+ ### Compliance Control Mapping
181
+ Maps security findings to SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, and EU AI Act (EUAIACT) controls. Identifies which code-level vulnerabilities are relevant to specific compliance requirements. **Not a substitute for professional compliance audits.**
182
182
 
183
183
  ### Supply Chain
184
184
  Malicious postinstall scripts, unpinned GitHub Actions, typosquat detection
185
185
 
186
- ## Tools (29 MCP tools)
186
+ ## Tools (31 MCP tools)
187
187
 
188
188
  | Tool | What it does |
189
189
  |------|-------------|
@@ -195,7 +195,7 @@ Malicious postinstall scripts, unpinned GitHub Actions, typosquat detection
195
195
  | `scan_secrets` | Detect leaked secrets, API keys, tokens |
196
196
  | `check_dependencies` | Check individual packages against OSV |
197
197
  | `check_package_health` | Typosquat detection, maintenance status, adoption metrics |
198
- | `compliance_report` | SOC2 / PCI-DSS / HIPAA / GDPR / ISO27001 / EU AI Act compliance mapping |
198
+ | `compliance_report` | Map security findings to compliance controls (SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, EU AI Act) |
199
199
  | `export_sarif` | SARIF v2.1.0 export for CI/CD integration |
200
200
  | `get_security_docs` | Security best practices and guides |
201
201
  | `fix_code` | **Auto-fix suggestions** with concrete patches for AI agents |
@@ -216,6 +216,8 @@ Malicious postinstall scripts, unpinned GitHub Actions, typosquat detection
216
216
  | `guardvibe_doctor` | **Host security audit** — CVE-2025-59536, CVE-2026-21852, MCP config, env scanner |
217
217
  | `audit_mcp_config` | Audit MCP server configurations for hook injection, file:// abuse, sensitive paths |
218
218
  | `scan_host_config` | Scan shell profiles, .env files for base URL hijack and credential sniffing |
219
+ | `verify_fix` | Verify a security fix was applied correctly — returns fixed/still_vulnerable/new_issues |
220
+ | `security_workflow` | Get recommended tool workflow for your current task (writing, pre-commit, PR review, etc.) |
219
221
 
220
222
  All scanning tools support `format: "json"` for machine-readable output.
221
223
 
package/build/index.js CHANGED
@@ -38,10 +38,12 @@ import { auditMcpConfig } from "./tools/audit-mcp-config.js";
38
38
  import { scanHostConfig } from "./tools/scan-host-config.js";
39
39
  import { doctor } from "./tools/doctor.js";
40
40
  import { formatHostFindings, redactSecrets } from "./server/types.js";
41
+ import { verifyFix } from "./tools/verify-fix.js";
42
+ import { fixCode as fixCodeTool } from "./tools/fix-code.js";
41
43
  const server = new McpServer({
42
44
  name: "guardvibe",
43
45
  version: pkg.version,
44
- description: "Security MCP for vibe coding. 334 security rules and 29 tools covering OWASP, Next.js, Supabase, Stripe, Clerk, Prisma, Hono, AI SDK, MCP server security, and host environment hardening. Scans code, dependencies, secrets, configs, and git history. Generates compliance reports (SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, EU AI Act). Runs 100% locally with zero configuration.",
46
+ description: "Security MCP for vibe coding. 334 security rules and 31 tools covering OWASP, Next.js, Supabase, Stripe, Clerk, Prisma, Hono, AI SDK, MCP server security, and host environment hardening. Scans code, dependencies, secrets, configs, and git history. Maps security findings to compliance controls (SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, EU AI Act). Runs 100% locally with zero configuration. Note: GuardVibe is a security scanner, not a compliance auditor — it helps identify code-level issues relevant to compliance frameworks but does not replace professional compliance audits.",
45
47
  });
46
48
  // Tool 1: Analyze code for security vulnerabilities
47
49
  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.", {
@@ -233,7 +235,7 @@ server.tool("scan_staged", "Scan git-staged files for security vulnerabilities b
233
235
  return { content: [{ type: "text", text: results + summary }] };
234
236
  });
235
237
  // Tool 9: Generate compliance-focused security report
236
- server.tool("compliance_report", "Generate a compliance-focused security report mapped to SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, or EUAIACT (EU AI Act) controls. Scans a directory and groups findings by compliance control. Includes exploit scenarios and audit evidence for each finding. Use mode=executive for a C-level summary.", {
238
+ 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.", {
237
239
  path: z.string().describe("Directory to scan"),
238
240
  framework: z.enum(["SOC2", "PCI-DSS", "HIPAA", "GDPR", "ISO27001", "EUAIACT", "all"]).describe("Compliance framework"),
239
241
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (human) or json (machine-readable for agents)"),
@@ -432,6 +434,20 @@ server.tool("scan_file", "Scan a single file from disk for security vulnerabilit
432
434
  const cwd = dirname(resolved);
433
435
  recordScan(cwd, { toolName: "scan_file", filesScanned: 1, findings: findings.map(f => ({ severity: f.rule.severity, ruleId: f.rule.id })) });
434
436
  const summary = getSummaryLine(cwd, findings.length, format);
437
+ // Append suggested fixes in JSON mode for agent auto-apply
438
+ if (format === "json" && findings.length > 0) {
439
+ const fixResult = fixCodeTool(content, language, undefined, resolved, "json", rules);
440
+ const fixes = JSON.parse(fixResult);
441
+ const parsed = JSON.parse(result);
442
+ parsed.suggested_fixes = (fixes.fixes || []).map((f) => ({
443
+ ruleId: f.ruleId,
444
+ line: f.line,
445
+ edit: f.edit,
446
+ confidence: f.confidence,
447
+ effort: f.effort,
448
+ })).filter((f) => f.edit);
449
+ return { content: [{ type: "text", text: JSON.stringify(parsed) + summary }] };
450
+ }
435
451
  return { content: [{ type: "text", text: result + summary }] };
436
452
  });
437
453
  // Tool 24: Scan changed files only — for incremental CI/CD and PR workflows
@@ -555,6 +571,95 @@ server.tool("guardvibe_doctor", "Comprehensive AI host security audit. Scans MCP
555
571
  const result = doctor(projectPath, scope, format);
556
572
  return { content: [{ type: "text", text: result }] };
557
573
  });
574
+ // Tool 29: Verify a specific fix was applied correctly
575
+ server.tool("verify_fix", "Verify that a specific security fix was applied correctly. Re-scans the updated code and checks if the target vulnerability (by rule ID) is resolved. Returns 'fixed', 'still_vulnerable', or 'new_issues' status with details.", {
576
+ code: z.string().describe("Updated code after applying the fix"),
577
+ language: z.string().describe("Programming language"),
578
+ ruleId: z.string().describe("Rule ID to verify (e.g. VG402)"),
579
+ filePath: z.string().optional().describe("File path for context-aware analysis"),
580
+ }, async ({ code, language, ruleId, filePath }) => {
581
+ const rules = getRules();
582
+ const result = verifyFix(code, language, ruleId, filePath, rules);
583
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
584
+ });
585
+ // Tool 30: Security workflow guide for AI agents
586
+ 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.", {
587
+ task: z.enum([
588
+ "writing_code",
589
+ "pre_commit",
590
+ "pr_review",
591
+ "new_project",
592
+ "fix_vulnerabilities",
593
+ "compliance_mapping",
594
+ "dependency_check",
595
+ ]).describe("What you are currently doing"),
596
+ }, async ({ task }) => {
597
+ const workflows = {
598
+ writing_code: {
599
+ task: "writing_code",
600
+ description: "Scan code after each significant edit to catch vulnerabilities early.",
601
+ steps: [
602
+ { tool: "scan_file", params: { file_path: "<edited_file>", format: "json" }, purpose: "Scan the file you just edited. Returns findings + suggested_fixes with structured edits." },
603
+ { tool: "verify_fix", params: { code: "<updated_code>", language: "<lang>", ruleId: "<id>" }, purpose: "After applying a fix, verify it resolved the issue.", condition: "if suggested_fixes returned" },
604
+ ],
605
+ },
606
+ pre_commit: {
607
+ task: "pre_commit",
608
+ description: "Security gate before committing code.",
609
+ steps: [
610
+ { tool: "scan_staged", params: { format: "json" }, purpose: "Scan all staged files for vulnerabilities." },
611
+ { tool: "fix_code", params: { code: "<vulnerable_code>", language: "<lang>", format: "json" }, purpose: "Get structured fix suggestions with edit instructions.", condition: "if critical/high findings" },
612
+ { tool: "verify_fix", params: { code: "<fixed_code>", language: "<lang>", ruleId: "<id>" }, purpose: "Verify each fix was applied correctly.", condition: "after applying fixes" },
613
+ { tool: "scan_staged", params: { format: "json" }, purpose: "Final verification — confirm all issues resolved.", condition: "after all fixes applied" },
614
+ ],
615
+ },
616
+ pr_review: {
617
+ task: "pr_review",
618
+ description: "Review a pull request for security issues.",
619
+ steps: [
620
+ { tool: "scan_changed_files", params: { path: ".", format: "json" }, purpose: "Scan only git-changed files." },
621
+ { tool: "review_pr", params: { path: ".", format: "json" }, purpose: "Review PR diff with severity gating." },
622
+ { tool: "explain_remediation", params: { ruleId: "<id>" }, purpose: "Get detailed fix guidance for critical findings.", condition: "for each critical/high finding" },
623
+ ],
624
+ },
625
+ new_project: {
626
+ task: "new_project",
627
+ description: "Set up security for a new project.",
628
+ steps: [
629
+ { tool: "scan_directory", params: { path: ".", format: "json" }, purpose: "Full project scan to establish baseline." },
630
+ { tool: "generate_policy", params: { path: "." }, purpose: "Auto-detect stack and generate security policies (CSP, CORS, RLS)." },
631
+ { tool: "audit_config", params: { path: "." }, purpose: "Audit config files for security misconfigurations." },
632
+ { tool: "guardvibe_doctor", params: { scope: "project" }, purpose: "Audit AI host security (hooks, MCP configs, env)." },
633
+ ],
634
+ },
635
+ fix_vulnerabilities: {
636
+ task: "fix_vulnerabilities",
637
+ description: "Fix known vulnerabilities in existing code.",
638
+ steps: [
639
+ { tool: "fix_code", params: { code: "<code>", language: "<lang>", format: "json" }, purpose: "Get structured fix suggestions with edit instructions and confidence scores." },
640
+ { tool: "verify_fix", params: { code: "<fixed_code>", language: "<lang>", ruleId: "<id>" }, purpose: "Verify each fix resolved the target vulnerability.", condition: "after applying each fix" },
641
+ { tool: "scan_file", params: { file_path: "<file>", format: "json" }, purpose: "Final scan to confirm file is clean.", condition: "after all fixes" },
642
+ ],
643
+ },
644
+ compliance_audit: {
645
+ task: "compliance_mapping",
646
+ description: "Map security findings to compliance framework controls. This identifies code-level issues relevant to specific controls — it does not replace professional compliance audits.",
647
+ steps: [
648
+ { tool: "compliance_report", params: { path: ".", framework: "<SOC2|PCI-DSS|HIPAA|GDPR|ISO27001|EUAIACT>", format: "json" }, purpose: "Scan code and map findings to compliance controls." },
649
+ { tool: "explain_remediation", params: { ruleId: "<id>" }, purpose: "Get remediation guidance and fix strategies for each finding.", condition: "for each finding" },
650
+ ],
651
+ },
652
+ dependency_check: {
653
+ task: "dependency_check",
654
+ description: "Check dependencies for vulnerabilities and supply chain risks.",
655
+ steps: [
656
+ { tool: "scan_dependencies", params: { manifest_path: "package.json" }, purpose: "Check all dependencies against OSV database." },
657
+ { tool: "check_package_health", params: { name: "<pkg>" }, purpose: "Check individual packages for typosquatting and maintenance status.", condition: "for suspicious packages" },
658
+ ],
659
+ },
660
+ };
661
+ return { content: [{ type: "text", text: JSON.stringify(workflows[task]) }] };
662
+ });
558
663
  export async function startMcpServer() {
559
664
  return main();
560
665
  }
@@ -3,6 +3,7 @@ export interface Finding {
3
3
  rule: SecurityRule;
4
4
  match: string;
5
5
  line: number;
6
+ confidence: "high" | "medium" | "low";
6
7
  }
7
8
  export declare function analyzeCode(code: string, language: string, framework?: string, filePath?: string, configDir?: string, rules?: SecurityRule[]): Finding[];
8
9
  export declare function formatFindingsJson(findings: Finding[], extra?: Record<string, unknown>): string;
@@ -175,6 +175,32 @@ function hasRoleCheckPattern(code) {
175
175
  return true;
176
176
  return false;
177
177
  }
178
+ /**
179
+ * Calculate confidence level for a finding based on file context and match quality.
180
+ */
181
+ function calculateConfidence(rule, matchText, lineNumber, lines, filePath) {
182
+ // Test/fixture/example files → low confidence
183
+ if (filePath && /(?:\/tests?\/|__tests__|\.test\.|\.spec\.|\/fixtures?\/|\/examples?\/|\/mocks?\/)/.test(filePath)) {
184
+ return "low";
185
+ }
186
+ // CVE version rules in package.json → always high
187
+ if (rule.id.startsWith("VG9") && filePath?.endsWith("package.json")) {
188
+ return "high";
189
+ }
190
+ // Secret detection with known prefixes → high
191
+ if (["VG001", "VG062"].includes(rule.id)) {
192
+ if (/(?:sk-live-|sk_live_|ghp_|gho_|github_pat_|AKIA[0-9A-Z]{16}|xoxb-|xoxp-|whsec_|rk_live_)/.test(matchText)) {
193
+ return "high";
194
+ }
195
+ return "medium";
196
+ }
197
+ // Match is on a comment-only line → low
198
+ const line = lines[lineNumber - 1] || "";
199
+ if (/^\s*(?:\/\/|#|\*|\/\*)/.test(line)) {
200
+ return "low";
201
+ }
202
+ return "medium";
203
+ }
178
204
  export function analyzeCode(code, language, framework, filePath, configDir, rules) {
179
205
  // Skip files that are security rule definitions (they intentionally contain
180
206
  // vulnerable code patterns as regex matchers and fixCode examples)
@@ -302,6 +328,7 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
302
328
  rule: effectiveRule,
303
329
  match: match[0].substring(0, 80),
304
330
  line: lineNumber,
331
+ confidence: calculateConfidence(effectiveRule, match[0], lineNumber, lines, filePath),
305
332
  });
306
333
  }
307
334
  }
@@ -77,16 +77,18 @@ export function complianceReport(path, framework, format = "markdown", rules, mo
77
77
  }
78
78
  // --- FULL MODE ---
79
79
  const lines = [
80
- `# GuardVibe Compliance Report`,
80
+ `# GuardVibe Compliance Control Mapping`,
81
+ ``,
82
+ `> This report maps code-level security findings to ${framework} controls. It identifies vulnerabilities relevant to compliance requirements but is not a substitute for professional compliance audits.`,
81
83
  ``,
82
84
  `Framework: ${framework}`,
83
85
  `Directory: ${scanRoot}`,
84
86
  `Files scanned: ${filePaths.length}`,
85
- `Compliance issues: ${relevant.length}`,
87
+ `Security issues mapped to controls: ${relevant.length}`,
86
88
  ``,
87
89
  ];
88
90
  if (controlMap.size === 0) {
89
- lines.push(`## No Compliance Issues`, ``, `No issues mapped to ${framework} controls were found.`);
91
+ lines.push(`## No Issues Found`, ``, `No code-level security issues mapped to ${framework} controls were found. This does not constitute a compliance certification.`);
90
92
  return lines.join("\n");
91
93
  }
92
94
  // Sort controls
@@ -120,7 +122,9 @@ function formatExecutiveSummary(framework, scanRoot, filesScanned, relevant, con
120
122
  const total = critical + high + medium;
121
123
  const riskLevel = critical > 0 ? "HIGH" : high > 0 ? "MEDIUM" : total > 0 ? "LOW" : "MINIMAL";
122
124
  const lines = [
123
- `# Executive Security Summary`,
125
+ `# Security Findings — ${framework} Control Mapping`,
126
+ ``,
127
+ `> Maps code-level security findings to ${framework} controls. Not a compliance audit or certification.`,
124
128
  ``,
125
129
  `**Framework:** ${framework} | **Date:** ${new Date().toISOString().split("T")[0]}`,
126
130
  `**Directory:** ${scanRoot}`,
@@ -167,7 +171,7 @@ function formatExecutiveSummary(framework, scanRoot, filesScanned, relevant, con
167
171
  }
168
172
  }
169
173
  // Compliance coverage
170
- lines.push(`## Compliance Coverage`, ``, `| Control | Status | Issues |`, `|---------|--------|--------|`);
174
+ lines.push(`## Controls with Findings`, ``, `| Control | Status | Issues |`, `|---------|--------|--------|`);
171
175
  const sortedControls = [...controlMap.entries()].sort((a, b) => a[0].localeCompare(b[0]));
172
176
  for (const [control, items] of sortedControls) {
173
177
  const hasCritical = items.some(i => i.finding.rule.severity === "critical");
@@ -1,4 +1,11 @@
1
1
  import { type SecurityRule } from "../data/rules/index.js";
2
+ export interface StructuredEdit {
3
+ startLine: number;
4
+ endLine: number;
5
+ oldText: string;
6
+ newText: string;
7
+ imports?: string[];
8
+ }
2
9
  export interface FixSuggestion {
3
10
  ruleId: string;
4
11
  ruleName: string;
@@ -9,6 +16,9 @@ export interface FixSuggestion {
9
16
  fix: string;
10
17
  fixCode?: string;
11
18
  patch?: string;
19
+ edit?: StructuredEdit;
20
+ confidence: "high" | "medium" | "low";
21
+ effort: 1 | 2 | 3;
12
22
  }
13
23
  /**
14
24
  * Analyze code and return structured fix suggestions that an AI agent can apply.
@@ -34,6 +34,8 @@ function generateFixSuggestions(findings, code) {
34
34
  seen.add(key);
35
35
  const sourceLine = lines[finding.line - 1] || "";
36
36
  const patch = generatePatch(finding, sourceLine);
37
+ const edit = generateStructuredEdit(finding, sourceLine, lines);
38
+ const effort = estimateEffort(finding.rule.id);
37
39
  suggestions.push({
38
40
  ruleId: finding.rule.id,
39
41
  ruleName: finding.rule.name,
@@ -44,6 +46,9 @@ function generateFixSuggestions(findings, code) {
44
46
  fix: finding.rule.fix,
45
47
  fixCode: finding.rule.fixCode,
46
48
  patch,
49
+ edit,
50
+ confidence: finding.confidence,
51
+ effort,
47
52
  });
48
53
  }
49
54
  // Sort by severity (critical first)
@@ -206,6 +211,96 @@ function generatePatch(finding, sourceLine) {
206
211
  }
207
212
  return undefined;
208
213
  }
214
+ /**
215
+ * Generate a structured edit that an AI agent can apply directly.
216
+ */
217
+ function generateStructuredEdit(finding, sourceLine, _lines) {
218
+ const { rule, line } = finding;
219
+ const trimmed = sourceLine.trim();
220
+ if (!trimmed)
221
+ return undefined;
222
+ // --- Hardcoded credentials → env var ---
223
+ if (["VG001", "VG062", "VG060", "VG506", "VG514", "VG517", "VG603",
224
+ "VG621", "VG626", "VG651", "VG665", "VG677"].includes(rule.id)) {
225
+ const m = /(\w+)\s*[:=]\s*['"][^'"]+['"]/.exec(sourceLine);
226
+ if (m) {
227
+ const envName = m[1].replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase();
228
+ return {
229
+ startLine: line, endLine: line,
230
+ oldText: sourceLine,
231
+ newText: sourceLine.replace(/['"][^'"]+['"]/, `process.env.${envName}`),
232
+ };
233
+ }
234
+ }
235
+ // --- NEXT_PUBLIC_ exposure → remove prefix ---
236
+ if (["VG411", "VG604", "VG627", "VG631", "VG655", "VG671", "VG676", "VG755"].includes(rule.id)) {
237
+ const m = /(NEXT_PUBLIC_)(\w+)/.exec(sourceLine);
238
+ if (m) {
239
+ return {
240
+ startLine: line, endLine: line,
241
+ oldText: sourceLine, newText: sourceLine.replace(`NEXT_PUBLIC_${m[2]}`, m[2]),
242
+ };
243
+ }
244
+ }
245
+ // --- innerHTML → textContent ---
246
+ if (["VG012", "VG042", "VG408"].includes(rule.id) && sourceLine.includes("innerHTML")) {
247
+ return {
248
+ startLine: line, endLine: line,
249
+ oldText: sourceLine, newText: sourceLine.replace("innerHTML", "textContent"),
250
+ };
251
+ }
252
+ // --- CORS wildcard → env var ---
253
+ if (["VG040", "VG403", "VG500", "VG510"].includes(rule.id) && /['"]\*['"]/.test(sourceLine)) {
254
+ return {
255
+ startLine: line, endLine: line,
256
+ oldText: sourceLine, newText: sourceLine.replace(/['"]\*['"]/, "process.env.ALLOWED_ORIGIN"),
257
+ };
258
+ }
259
+ // --- dangerouslyAllowBrowser: true → remove ---
260
+ if (rule.id === "VG998" && /dangerouslyAllowBrowser\s*:\s*true/.test(sourceLine)) {
261
+ return {
262
+ startLine: line, endLine: line,
263
+ oldText: sourceLine, newText: sourceLine.replace(/,?\s*dangerouslyAllowBrowser\s*:\s*true\s*,?/, ""),
264
+ };
265
+ }
266
+ // --- Source maps → disable ---
267
+ if (["VG512", "VG662"].includes(rule.id) && /true/.test(sourceLine)) {
268
+ return {
269
+ startLine: line, endLine: line,
270
+ oldText: sourceLine, newText: sourceLine.replace(/true/, "false"),
271
+ };
272
+ }
273
+ // --- Missing auth → add auth check before the line ---
274
+ if (["VG002", "VG402", "VG420", "VG952"].includes(rule.id)) {
275
+ const indent = sourceLine.match(/^(\s*)/)?.[1] || " ";
276
+ return {
277
+ startLine: line, endLine: line,
278
+ oldText: sourceLine,
279
+ newText: `${indent}const { userId } = await auth();\n${indent}if (!userId) return new Response("Unauthorized", { status: 401 });\n${sourceLine}`,
280
+ imports: ['import { auth } from "@clerk/nextjs/server"'],
281
+ };
282
+ }
283
+ return undefined;
284
+ }
285
+ /**
286
+ * Estimate fix effort: 1 = single line, 2 = few lines, 3 = structural change
287
+ */
288
+ function estimateEffort(ruleId) {
289
+ const effort1 = new Set([
290
+ "VG001", "VG062", "VG060", "VG012", "VG042", "VG040", "VG411",
291
+ "VG506", "VG507", "VG512", "VG514", "VG517", "VG656", "VG662",
292
+ "VG874", "VG875", "VG978", "VG998",
293
+ ]);
294
+ if (effort1.has(ruleId))
295
+ return 1;
296
+ const effort3 = new Set([
297
+ "VG402", "VG404", "VG405", "VG407", "VG432", "VG440", "VG441",
298
+ "VG859", "VG953", "VG964",
299
+ ]);
300
+ if (effort3.has(ruleId))
301
+ return 3;
302
+ return 2;
303
+ }
209
304
  function formatFixMarkdown(suggestions) {
210
305
  const lines = [
211
306
  "# GuardVibe Auto-Fix Suggestions",
@@ -0,0 +1,17 @@
1
+ import type { SecurityRule } from "../data/rules/types.js";
2
+ export interface VerifyResult {
3
+ ruleId: string;
4
+ status: "fixed" | "still_vulnerable" | "new_issues";
5
+ details: string;
6
+ remainingFindings: Array<{
7
+ id: string;
8
+ name: string;
9
+ severity: string;
10
+ line: number;
11
+ }>;
12
+ }
13
+ /**
14
+ * Verify that a specific security fix was applied correctly.
15
+ * Re-scans the code and checks if the target rule is resolved.
16
+ */
17
+ export declare function verifyFix(code: string, language: string, ruleId: string, filePath?: string, rules?: SecurityRule[]): VerifyResult;
@@ -0,0 +1,42 @@
1
+ import { analyzeCode } from "./check-code.js";
2
+ /**
3
+ * Verify that a specific security fix was applied correctly.
4
+ * Re-scans the code and checks if the target rule is resolved.
5
+ */
6
+ export function verifyFix(code, language, ruleId, filePath, rules) {
7
+ const findings = analyzeCode(code, language, undefined, filePath, undefined, rules);
8
+ const targetStillPresent = findings.filter(f => f.rule.id === ruleId);
9
+ const otherFindings = findings.filter(f => f.rule.id !== ruleId);
10
+ if (targetStillPresent.length > 0) {
11
+ return {
12
+ ruleId,
13
+ status: "still_vulnerable",
14
+ details: `${ruleId} still detected on line(s) ${targetStillPresent.map(f => f.line).join(", ")}. Fix was not applied correctly.`,
15
+ remainingFindings: targetStillPresent.map(f => ({
16
+ id: f.rule.id,
17
+ name: f.rule.name,
18
+ severity: f.rule.severity,
19
+ line: f.line,
20
+ })),
21
+ };
22
+ }
23
+ if (otherFindings.length > 0) {
24
+ return {
25
+ ruleId,
26
+ status: "new_issues",
27
+ details: `${ruleId} resolved, but ${otherFindings.length} other issue(s) found. Review before proceeding.`,
28
+ remainingFindings: otherFindings.map(f => ({
29
+ id: f.rule.id,
30
+ name: f.rule.name,
31
+ severity: f.rule.severity,
32
+ line: f.line,
33
+ })),
34
+ };
35
+ }
36
+ return {
37
+ ruleId,
38
+ status: "fixed",
39
+ details: `${ruleId} resolved. No remaining security issues.`,
40
+ remainingFindings: [],
41
+ };
42
+ }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "2.8.0",
3
+ "version": "2.9.1",
4
4
  "mcpName": "io.github.goklab/guardvibe",
5
- "description": "Security MCP for vibe coding. 334 rules, 29 tools, CLI + doctor. Host security: CVE-2025-59536 hook injection, CVE-2026-21852 base URL hijack, MCP config audit, AI host hardening. Plus Next.js, Supabase, Clerk, Stripe, Prisma, tRPC, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK, and the full AI-generated stack.",
5
+ "description": "Security MCP for vibe coding. 334 rules, 31 tools, CLI + doctor. Host security: CVE-2025-59536 hook injection, CVE-2026-21852 base URL hijack, MCP config audit, AI host hardening. 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",
7
7
  "bin": {
8
8
  "guardvibe": "build/cli.js",