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 +8 -6
- package/build/index.js +107 -2
- package/build/tools/check-code.d.ts +1 -0
- package/build/tools/check-code.js +27 -0
- package/build/tools/compliance-report.js +9 -5
- package/build/tools/fix-code.d.ts +10 -0
- package/build/tools/fix-code.js +95 -0
- package/build/tools/verify-fix.d.ts +17 -0
- package/build/tools/verify-fix.js +42 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/guardvibe)
|
|
7
7
|
[](https://codecov.io/gh/goklab/guardvibe)
|
|
8
8
|
|
|
9
|
-
**The security MCP built for vibe coding.** 334 security rules,
|
|
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,
|
|
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)
|
|
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 (
|
|
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
|
|
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
|
|
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", "
|
|
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
|
|
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
|
-
`
|
|
87
|
+
`Security issues mapped to controls: ${relevant.length}`,
|
|
86
88
|
``,
|
|
87
89
|
];
|
|
88
90
|
if (controlMap.size === 0) {
|
|
89
|
-
lines.push(`## No
|
|
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
|
-
`#
|
|
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(`##
|
|
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.
|
package/build/tools/fix-code.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "2.9.1",
|
|
4
4
|
"mcpName": "io.github.goklab/guardvibe",
|
|
5
|
-
"description": "Security MCP for vibe coding. 334 rules,
|
|
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",
|