guardvibe 1.9.5 → 2.0.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/build/cli.js CHANGED
@@ -50,6 +50,9 @@ function setupPlatform(name) {
50
50
  }
51
51
  if (existing.mcpServers["guardvibe"]) {
52
52
  console.log(` [OK] GuardVibe already configured in ${platform.description}`);
53
+ // Still ensure CLAUDE.md and .gitignore are set up
54
+ if (name === "claude")
55
+ setupClaudeHooksAndGuide();
53
56
  return true;
54
57
  }
55
58
  existing.mcpServers["guardvibe"] = GUARDVIBE_MCP_CONFIG;
@@ -225,9 +228,6 @@ jobs:
225
228
  with:
226
229
  node-version: "22"
227
230
 
228
- - name: Install dependencies
229
- run: npm ci
230
-
231
231
  - name: Run GuardVibe security scan
232
232
  run: npx -y guardvibe-scan --format sarif --output guardvibe-results.sarif
233
233
 
@@ -33,7 +33,7 @@ export const apiSecurityRules = [
33
33
  severity: "high",
34
34
  owasp: "API2:2023 Broken Authentication",
35
35
  description: "Next.js Route Handler that performs data operations without any authentication check. API routes are publicly accessible by default.",
36
- pattern: /export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)\s*\([^)]*\)\s*\{(?:(?!auth\s*\(|getServerSession|currentUser|getUser|requireAuth|requireAdmin|requireRole|isAuthenticated|verifyToken|checkAuth|clerkClient|getToken|session|protect|withAuth)[\s\S]){10,}?(?:prisma|db|supabase|query|fetch|sql)\.\w+/g,
36
+ pattern: /export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)\s*\([^)]*\)\s*\{(?:(?!auth\s*\(|getServerSession|currentUser|getUser|requireAuth|requireAdmin|requireRole|isAuthenticated|verifyToken|checkAuth|clerkClient|getToken|session|protect|withAuth|verifyAuth|checkPermission|assertAuth|ensureAuth|guardAuth|validateAuth|authorize|checkSession|verifySession|validateSession|ensureAuthenticated)[\s\S]){10,}?(?:prisma|db|supabase|query|fetch|sql)\.\w+/g,
37
37
  languages: ["javascript", "typescript"],
38
38
  fix: "Add authentication at the start of every Route Handler that reads or writes data.",
39
39
  fixCode: 'import { auth } from "@clerk/nextjs/server";\n\nexport async function GET() {\n const { userId } = await auth();\n if (!userId) return new Response("Unauthorized", { status: 401 });\n // ... data access\n}',
@@ -96,7 +96,7 @@ export const apiSecurityRules = [
96
96
  severity: "high",
97
97
  owasp: "API5:2023 Broken Function Level Authorization",
98
98
  description: "Endpoint in /admin or /api/admin path performs operations without verifying admin role or permissions. Any authenticated user could access admin functionality.",
99
- pattern: /(?:\/api\/admin|\/admin)[\s\S]*?export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)\s*\([^)]*\)\s*\{(?:(?!role|isAdmin|orgRole|permission|requireAdmin|checkRole|adminOnly|org:admin)[\s\S]){10,}?(?:prisma|db|supabase|sql)\.\w+/g,
99
+ pattern: /(?:\/api\/admin|\/admin)[\s\S]*?export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)\s*\([^)]*\)\s*\{(?:(?!role|isAdmin|orgRole|permission|requireAdmin|checkRole|adminOnly|org:admin|verifyAuth|checkPermission|assertAuth|ensureAuth|authorize)[\s\S]){10,}?(?:prisma|db|supabase|sql)\.\w+/g,
100
100
  languages: ["javascript", "typescript"],
101
101
  fix: "Always verify admin role/permissions in admin endpoints.",
102
102
  fixCode: 'const { userId, orgRole } = await auth();\nif (orgRole !== "org:admin") {\n return new Response("Forbidden", { status: 403 });\n}',
@@ -6,7 +6,7 @@ export const authRules = [
6
6
  severity: "high",
7
7
  owasp: "A01:2025 Broken Access Control",
8
8
  description: "Route handler accesses database without authentication check. Anyone can call this endpoint.",
9
- pattern: /export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)\s*\([^)]*\)\s*\{(?:(?!auth\s*\(|getServerSession|currentUser|getUser|requireAuth|requireAdmin|isAuthenticated|verifyToken|checkAuth|protect)[\s\S])*?(?:prisma|db|supabase)\.\w+/g,
9
+ pattern: /export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)\s*\([^)]*\)\s*\{(?:(?!auth\s*\(|getServerSession|currentUser|getUser|requireAuth|requireAdmin|isAuthenticated|verifyToken|checkAuth|protect|verifyAuth|checkPermission|assertAuth|ensureAuth|guardAuth|validateAuth|authorize|checkSession|verifySession|validateSession|ensureAuthenticated|withAuth)[\s\S])*?(?:prisma|db|supabase)\.\w+/g,
10
10
  languages: ["javascript", "typescript"],
11
11
  fix: "Add authentication check at the start of every route handler that accesses data.",
12
12
  fixCode: 'import { auth } from "@clerk/nextjs/server";\n\nexport async function GET() {\n const { userId } = await auth();\n if (!userId) return new Response("Unauthorized", { status: 401 });\n const data = await db.query(...);\n}',
@@ -69,7 +69,7 @@ export const authRules = [
69
69
  severity: "high",
70
70
  owasp: "A01:2025 Broken Access Control",
71
71
  description: "Admin or dashboard route handler does not verify user role or permissions.",
72
- pattern: /(?:\/admin|\/dashboard)[\s\S]*?export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH|default)\s*\([^)]*\)\s*\{(?:(?!role|permission|isAdmin|orgRole|checkRole|requireAdmin|requireRole|adminOnly)[\s\S])*?\}/g,
72
+ pattern: /(?:\/admin|\/dashboard)[\s\S]*?export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH|default)\s*\([^)]*\)\s*\{(?:(?!role|permission|isAdmin|orgRole|checkRole|requireAdmin|requireRole|adminOnly|verifyAuth|checkPermission|assertAuth|ensureAuth|authorize)[\s\S])*?\}/g,
73
73
  languages: ["javascript", "typescript"],
74
74
  fix: "Always verify user roles and permissions in admin routes.",
75
75
  fixCode: 'import { auth } from "@clerk/nextjs/server";\n\nexport async function GET() {\n const { userId, orgRole } = await auth();\n if (orgRole !== "org:admin") {\n return new Response("Forbidden", { status: 403 });\n }\n}',
@@ -19,7 +19,7 @@ export const coreRules = [
19
19
  severity: "critical",
20
20
  owasp: "A01:2025 Broken Access Control",
21
21
  description: "Cloud provider API key or token pattern detected in source code (AWS, GitHub, OpenAI, Stripe).",
22
- pattern: /(?:AKIA[0-9A-Z]{16}|(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}|sk-[A-Za-z0-9]{20,}|sk_live_[A-Za-z0-9]{5,}|rk_live_[A-Za-z0-9]{5,})/g,
22
+ pattern: /(?:AKIA[0-9A-Z]{16}|(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}|sk-(?:proj-)?[A-Za-z0-9\-_]{20,}|sk_live_[A-Za-z0-9]{5,}|rk_live_[A-Za-z0-9]{5,})/g,
23
23
  languages: ["javascript", "typescript", "python", "go", "html", "shell"],
24
24
  fix: "Remove hardcoded keys immediately. Use environment variables or a secrets manager (AWS Secrets Manager, Vault). Rotate any compromised keys.",
25
25
  fixCode: "// Store keys in environment variables\nconst awsKey = process.env.AWS_ACCESS_KEY_ID;\nconst githubToken = process.env.GITHUB_TOKEN;",
@@ -386,4 +386,52 @@ export const coreRules = [
386
386
  fixCode: '// BAD: user input in event handler\n// `<img onerror="${userInput}">`\n\n// GOOD: use addEventListener\nconst img = document.createElement("img");\nimg.addEventListener("error", () => handleError(sanitizedInput));',
387
387
  compliance: ["SOC2:CC7.1"],
388
388
  },
389
+ {
390
+ id: "VG111",
391
+ name: "SSRF via User-Controlled URL",
392
+ severity: "high",
393
+ owasp: "A10:2025 Server-Side Request Forgery",
394
+ description: "User-controlled input is passed directly to fetch(), axios, or http.request() as the URL. Attackers can make the server request internal services (169.254.169.254 for cloud metadata, localhost admin panels, internal APIs) leading to data exfiltration or remote code execution.",
395
+ pattern: /(?:fetch|axios\.(?:get|post|put|delete|patch|request)|got(?:\.(?:get|post|put|delete|patch))?|request|http\.(?:get|request)|https\.(?:get|request)|urllib\.request\.urlopen)\s*\(\s*(?:url|uri|href|endpoint|target|destination|webhook[Uu]rl|callback[Uu]rl|redirect[Uu]rl|image[Uu]rl|proxy[Uu]rl|api[Uu]rl|base[Uu]rl|link|src|source|remoteUrl|externalUrl|userUrl|inputUrl|requestUrl)\b/gi,
396
+ languages: ["javascript", "typescript", "python"],
397
+ fix: "Validate URLs against an allowlist of trusted domains. Block private/internal IP ranges (10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x, ::1). Use a URL parser to check the hostname before making the request.",
398
+ fixCode: '// Validate URL before fetching\nconst ALLOWED_HOSTS = ["api.example.com", "cdn.example.com"];\nconst parsed = new URL(userUrl);\nif (!ALLOWED_HOSTS.includes(parsed.hostname)) {\n throw new Error("Blocked: untrusted host");\n}\n// Also block private IPs\nconst ip = await dns.resolve(parsed.hostname);\nif (isPrivateIP(ip)) throw new Error("Blocked: internal host");\nawait fetch(parsed.href);',
399
+ compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
400
+ },
401
+ {
402
+ id: "VG112",
403
+ name: "XSS via insertAdjacentHTML",
404
+ severity: "high",
405
+ owasp: "A02:2025 Injection",
406
+ description: "insertAdjacentHTML() renders raw HTML strings into the DOM without sanitization, enabling Cross-Site Scripting (XSS). Unlike textContent, this method parses and executes HTML including script tags and event handlers.",
407
+ pattern: /\.insertAdjacentHTML\s*\(\s*["'](?:beforebegin|afterbegin|beforeend|afterend)["']\s*,/gi,
408
+ languages: ["javascript", "typescript"],
409
+ fix: "Use textContent or innerText for plain text. If HTML is needed, sanitize with DOMPurify first.",
410
+ fixCode: '// BAD: XSS risk\nel.insertAdjacentHTML("beforeend", userInput);\n\n// GOOD: plain text\nel.insertAdjacentText("beforeend", userInput);\n\n// GOOD: sanitized HTML\nimport DOMPurify from "dompurify";\nel.insertAdjacentHTML("beforeend", DOMPurify.sanitize(userInput));',
411
+ compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.7"],
412
+ },
413
+ {
414
+ id: "VG113",
415
+ name: "XSS/SSRF via XMLHttpRequest",
416
+ severity: "high",
417
+ owasp: "A02:2025 Injection",
418
+ description: "XMLHttpRequest.open() is called with a user-controlled URL. This can lead to SSRF (server-side) or data exfiltration (client-side) if the URL is not validated.",
419
+ pattern: /\.open\s*\(\s*["'](?:GET|POST|PUT|DELETE|PATCH)["']\s*,\s*(?:url|uri|href|endpoint|target|userUrl|inputUrl|src)\b/gi,
420
+ languages: ["javascript", "typescript"],
421
+ fix: "Validate the URL against an allowlist before passing to XMLHttpRequest.open(). Prefer fetch() with URL validation over raw XHR.",
422
+ fixCode: '// Validate URL before XHR\nconst allowed = ["https://api.example.com"];\nconst parsed = new URL(userUrl);\nif (!allowed.some(a => userUrl.startsWith(a))) throw new Error("Blocked");\nxhr.open("GET", parsed.href);',
423
+ compliance: ["SOC2:CC7.1"],
424
+ },
425
+ {
426
+ id: "VG114",
427
+ name: "SQL Injection via Template Literal",
428
+ severity: "critical",
429
+ owasp: "A02:2025 Injection",
430
+ description: "SQL query constructed using JavaScript template literals with interpolated variables. Template literal SQL is just as dangerous as string concatenation — any user-controlled value can break out of the SQL context and inject arbitrary queries.",
431
+ pattern: /(?:query|execute|raw|sql|prepare|QueryRow|QueryContext|db\.run|db\.all|db\.get|connection\.query)\s*\(\s*`[^`]*\$\{/gi,
432
+ languages: ["javascript", "typescript"],
433
+ fix: "Use parameterized queries. For tagged template literals (e.g. Prisma sql``, Drizzle sql``), the tagged version IS safe — only raw template literals passed to query() are dangerous.",
434
+ fixCode: '// BAD: template literal SQL\ndb.query(`SELECT * FROM users WHERE id = \'${userId}\'`);\n\n// GOOD: parameterized query\ndb.query("SELECT * FROM users WHERE id = $1", [userId]);\n\n// ALSO SAFE: tagged template literal (Prisma/Drizzle)\nimport { sql } from "drizzle-orm";\ndb.execute(sql`SELECT * FROM users WHERE id = ${userId}`);',
435
+ compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
436
+ },
389
437
  ];
@@ -30,7 +30,7 @@ export const nextjsRules = [
30
30
  severity: "critical",
31
31
  owasp: "A01:2025 Broken Access Control",
32
32
  description: "Server Action performs data mutations without verifying user authentication. Anyone can invoke Server Actions directly via POST request.",
33
- pattern: /["']use server["'][\s\S]{0,500}?export\s+async\s+function\s+\w+\s*\([^)]*\)\s*\{(?![\s\S]{0,800}?(?:auth\s*\(|getServerSession|currentUser|getUser|requireAuth|clerkClient))/g,
33
+ pattern: /["']use server["'][\s\S]{0,500}?export\s+async\s+function\s+\w+\s*\([^)]*\)\s*\{(?![\s\S]{0,800}?(?:auth\s*\(|getServerSession|currentUser|getUser|requireAuth|requireAdmin|clerkClient|verifyAuth|checkPermission|assertAuth|ensureAuth|authorize|withAuth))/g,
34
34
  languages: ["javascript", "typescript"],
35
35
  fix: "Always verify authentication at the start of every Server Action.",
36
36
  fixCode: '"use server";\nimport { auth } from "@clerk/nextjs/server";\n\nexport async function deleteItem(id: string) {\n const { userId } = await auth();\n if (!userId) throw new Error("Unauthorized");\n}',
@@ -114,7 +114,7 @@ export const nextjsRules = [
114
114
  severity: "medium",
115
115
  owasp: "A01:2025 Broken Access Control",
116
116
  description: "redirect() or NextResponse.redirect() uses user-controlled input (searchParams, query) which can redirect users to malicious sites.",
117
- pattern: /redirect\s*\(\s*(?:searchParams|params|req\.query|request\.url|url|query)\s*[\.\[]/g,
117
+ pattern: /(?:redirect|NextResponse\.redirect|res\.redirect|Response\.redirect)\s*\(\s*(?:searchParams|params|req\.query|request\.url|url|query|returnTo|callbackUrl|next|goto|returnUrl|redirectUrl|destination)\b/gi,
118
118
  languages: ["javascript", "typescript"],
119
119
  fix: "Validate redirect URLs against an allowlist of trusted domains.",
120
120
  fixCode: '// Validate redirect URL\nconst ALLOWED_HOSTS = ["example.com"];\nconst target = searchParams.get("next") ?? "/";\ntry {\n const url = new URL(target, request.url);\n if (!ALLOWED_HOSTS.includes(url.hostname)) redirect("/");\n redirect(url.pathname);\n} catch {\n redirect("/");\n}',
@@ -169,7 +169,7 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
169
169
  continue;
170
170
  // Context-aware: skip rate limiting rules for admin routes that have admin auth
171
171
  const isAdminRoute = filePath && /\/admin\//i.test(filePath);
172
- const hasAdminAuth = isAdminRoute && /(?:requireAdmin|adminOnly|orgRole|org:admin|isAdmin|checkRole|requireRole)/i.test(code);
172
+ const hasAdminAuth = isAdminRoute && /(?:requireAdmin|adminOnly|orgRole|org:admin|isAdmin|checkRole|requireRole|verifyAuth|checkPermission|assertAuth|ensureAuth|authorize)/i.test(code);
173
173
  if (hasAdminAuth && rateLimitRuleIds.has(rule.id))
174
174
  continue;
175
175
  // Skip npm package rules (VG863/VG864/VG865): only apply to package.json files
@@ -344,6 +344,7 @@ export function formatFindingsJson(findings, extra) {
344
344
  return JSON.stringify({
345
345
  summary: {
346
346
  total: findings.length, critical, high, medium, low,
347
+ // blocked: true when critical or high findings exist (would fail --fail-on high)
347
348
  blocked: critical > 0 || high > 0,
348
349
  ...extra,
349
350
  },
@@ -366,19 +367,68 @@ export function checkCode(code, language, framework, filePath, configDir, format
366
367
  }
367
368
  function formatCleanReport(language, framework) {
368
369
  const ctx = framework ? ` (${framework})` : "";
370
+ const tips = getLanguageTips(language, framework);
369
371
  return [
370
372
  `# GuardVibe Security Report`,
371
373
  ``,
372
374
  `**Language:** ${language}${ctx}`,
373
375
  `**Status:** No security issues detected`,
374
376
  ``,
375
- `The code looks clean! Here are some general tips:`,
376
- `- Keep dependencies updated (\`npm audit\`)`,
377
- `- Validate all user input with schemas (zod, joi)`,
378
- `- Use environment variables for secrets`,
379
- `- Add rate limiting to API endpoints`,
377
+ `Tips for ${language}${ctx}:`,
378
+ ...tips.map(t => `- ${t}`),
380
379
  ].join("\n");
381
380
  }
381
+ function getLanguageTips(language, framework) {
382
+ if (framework === "nextjs" || framework === "next")
383
+ return [
384
+ "Use `server-only` imports in files with secrets or DB access",
385
+ "Validate Server Action inputs with zod schemas",
386
+ "Set `serverActions.allowedOrigins` in next.config",
387
+ "Add security headers via `headers()` in next.config",
388
+ ];
389
+ if (framework === "express" || framework === "fastify" || framework === "hono")
390
+ return [
391
+ "Add rate limiting middleware to auth and write endpoints",
392
+ "Use helmet() for security headers",
393
+ "Validate request body with zod or joi before processing",
394
+ "Never reflect user input in error responses",
395
+ ];
396
+ if (language === "python")
397
+ return [
398
+ "Use parameterized queries — never f-strings in SQL",
399
+ "Add `Depends(get_current_user)` to protected routes",
400
+ "Pin dependency versions in requirements.txt",
401
+ "Use `secrets.compare_digest()` for token comparison",
402
+ ];
403
+ if (language === "sql")
404
+ return [
405
+ "Use `SECURITY INVOKER` on views to respect RLS",
406
+ "Avoid `GRANT ALL` — use least-privilege permissions",
407
+ "Add `IF EXISTS` to destructive DDL for safety",
408
+ "Use parameterized queries in application code",
409
+ ];
410
+ if (language === "dockerfile")
411
+ return [
412
+ "Use specific image tags, never `latest`",
413
+ "Run as non-root user with `USER` directive",
414
+ "Use multi-stage builds to minimize attack surface",
415
+ "Don't copy `.env` or secrets into the image",
416
+ ];
417
+ if (language === "yaml" || language === "terraform")
418
+ return [
419
+ "Never hardcode secrets in config — use env vars or secrets manager",
420
+ "Pin action/provider versions to specific SHA or tag",
421
+ "Use least-privilege IAM policies",
422
+ "Enable audit logging for infrastructure changes",
423
+ ];
424
+ // Default for JS/TS
425
+ return [
426
+ "Keep dependencies updated (`npm audit`)",
427
+ "Validate all user input with schemas (zod, joi)",
428
+ "Use environment variables for secrets",
429
+ "Use `textContent` instead of `innerHTML` for user data",
430
+ ];
431
+ }
382
432
  function formatReport(findings, language, framework) {
383
433
  const ctx = framework ? ` (${framework})` : "";
384
434
  // Severity ordering
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "1.9.5",
3
+ "version": "2.0.0",
4
4
  "description": "Security MCP for vibe coding. 277 rules, 24 tools for Next.js, Supabase, Clerk, Stripe, Prisma, tRPC, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK, and the full AI-generated stack.",
5
5
  "type": "module",
6
6
  "bin": {