guardvibe 2.8.0 → 2.9.0
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 +5 -3
- package/build/index.js +106 -1
- package/build/tools/check-code.d.ts +1 -0
- package/build/tools/check-code.js +27 -0
- 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
|
|
@@ -183,7 +183,7 @@ SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, EU AI Act (EUAIACT) control mapping with c
|
|
|
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
|
|------|-------------|
|
|
@@ -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. Generates compliance reports (SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, EU AI Act). Runs 100% locally with zero configuration.",
|
|
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.", {
|
|
@@ -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_audit",
|
|
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_audit",
|
|
646
|
+
description: "Generate compliance reports for regulatory frameworks.",
|
|
647
|
+
steps: [
|
|
648
|
+
{ tool: "compliance_report", params: { path: ".", framework: "<SOC2|PCI-DSS|HIPAA|GDPR|ISO27001|EUAIACT>", format: "json" }, purpose: "Generate compliance-mapped findings report." },
|
|
649
|
+
{ tool: "explain_remediation", params: { ruleId: "<id>" }, purpose: "Get audit evidence 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
|
}
|
|
@@ -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.0",
|
|
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",
|