guardvibe 2.3.9 → 2.4.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/build/cli.js CHANGED
@@ -136,7 +136,7 @@ function setupClaudeGuide() {
136
136
  matcher: "Edit|Write",
137
137
  hooks: [{
138
138
  type: "command",
139
- command: "jq -r '.tool_input.file_path' | xargs npx -y guardvibe check --format markdown 2>/dev/null || true"
139
+ command: "jq -r '.tool_input.file_path' | xargs npx -y guardvibe check --format buddy 2>/dev/null || true"
140
140
  }]
141
141
  }
142
142
  ];
@@ -560,7 +560,8 @@ async function runFileCheck(filePath, flags) {
560
560
  process.exit(1);
561
561
  }
562
562
  const format = flags.format ?? "markdown";
563
- const result = checkCode(content, language, undefined, resolved, undefined, format === "json" ? "json" : "markdown");
563
+ const formatArg = format === "json" ? "json" : format === "buddy" ? "buddy" : "markdown";
564
+ const result = checkCode(content, language, undefined, resolved, undefined, formatArg);
564
565
  const outputFile = flags.output ?? null;
565
566
  if (outputFile) {
566
567
  writeFileSync(outputFile, result, "utf-8");
@@ -592,7 +593,7 @@ function printUsage() {
592
593
  npx guardvibe-scan --format sarif --output results.sarif
593
594
 
594
595
  Options:
595
- --format <type> Output format: markdown (default), json, sarif
596
+ --format <type> Output format: markdown (default), json, sarif, buddy
596
597
  --output <file> Write results to file instead of stdout
597
598
  --fail-on <level> Exit 1 when findings at this level or above exist
598
599
  critical (default) | high | medium | low | none
@@ -123,4 +123,16 @@ export const databaseRules = [
123
123
  fixCode: '-- PostgreSQL: use SECURITY INVOKER\nCREATE OR REPLACE FUNCTION get_user_data(user_id uuid)\nRETURNS TABLE (id uuid, name text)\nLANGUAGE sql\nSECURITY INVOKER -- respects RLS!\nAS $$\n SELECT id, name FROM users WHERE id = user_id;\n$$;\n\n// TypeScript: call with anon key (not service role)\nconst { data } = await supabase.rpc("get_user_data", { user_id: userId });',
124
124
  compliance: ["SOC2:CC6.1"],
125
125
  },
126
+ {
127
+ id: "VG912",
128
+ name: "MongoDB NoSQL Injection via Query Operators",
129
+ severity: "high",
130
+ owasp: "A02:2025 Injection",
131
+ description: "MongoDB query operators ($where, $regex, $gt, $ne, $nin) used in database queries may be vulnerable to NoSQL injection if user input is passed directly without validation. Attackers can manipulate query logic to bypass authentication or extract data.",
132
+ pattern: /\.(find|findOne|updateOne|deleteOne|aggregate)\(\s*\{[^}]*(\$where|\$regex|\$gt|\$ne|\$nin)/g,
133
+ languages: ["javascript", "typescript"],
134
+ fix: "Validate and sanitize all user input before using it in MongoDB queries. Use a schema validation library (zod, joi) to ensure query parameters match expected types. Never pass raw request body directly to MongoDB queries.",
135
+ fixCode: 'import { z } from "zod";\n\n// Validate input before query\nconst schema = z.object({ id: z.string().regex(/^[a-f0-9]{24}$/) });\nconst { id } = schema.parse(req.body);\n\n// Safe query — no raw operators from user input\nconst user = await db.collection("users").findOne({ _id: new ObjectId(id) });',
136
+ compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
137
+ },
126
138
  ];
@@ -244,4 +244,16 @@ export const deploymentRules = [
244
244
  fixCode: '// Validate URL scheme\nfunction isSafeUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return ["https:", "http:"].includes(parsed.protocol);\n } catch {\n return false;\n }\n}\n\n// Usage\n<img src={isSafeUrl(userUrl) ? userUrl : "/placeholder.png"} />',
245
245
  compliance: ["SOC2:CC7.1"],
246
246
  },
247
+ {
248
+ id: "VG911",
249
+ name: "Kubernetes Secret Hardcoded Value",
250
+ severity: "critical",
251
+ owasp: "A07:2025 Identification and Authentication Failures",
252
+ description: "Kubernetes Secret manifest contains hardcoded base64-encoded values in the data field. These secrets are only base64-encoded (not encrypted) and will be exposed in version control. Use External Secrets, Sealed Secrets, or a secrets manager instead.",
253
+ pattern: /kind:\s*Secret[\s\S]*?data:[\s\S]*?(?!\s*\{\{)[\w+/=]{8,}/g,
254
+ languages: ["yaml"],
255
+ fix: "Never commit Secret manifests with hardcoded values. Use External Secrets Operator, Sealed Secrets, or inject secrets via CI/CD pipeline. Store sensitive values in a secrets manager (Vault, AWS Secrets Manager, etc.).",
256
+ fixCode: '# Use External Secrets Operator\napiVersion: external-secrets.io/v1beta1\nkind: ExternalSecret\nmetadata:\n name: my-secret\nspec:\n refreshInterval: 1h\n secretStoreRef:\n name: vault-backend\n kind: SecretStore\n target:\n name: my-secret\n data:\n - secretKey: password\n remoteRef:\n key: secret/data/myapp\n property: password',
257
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req6.5.3", "HIPAA:§164.312(a)"],
258
+ },
247
259
  ];
@@ -482,4 +482,16 @@ export const modernStackRules = [
482
482
  fixCode: 'import { randomUUID } from "crypto";\nimport path from "path";\n\n// Generate safe filename\nconst ext = path.extname(file.name).toLowerCase();\nconst ALLOWED_EXT = [".jpg", ".jpeg", ".png", ".webp", ".pdf"];\nif (!ALLOWED_EXT.includes(ext)) throw new Error("Invalid file type");\nconst safeName = `${randomUUID()}${ext}`;\nawait fs.writeFile(`/uploads/${safeName}`, buffer);',
483
483
  compliance: ["SOC2:CC7.1"],
484
484
  },
485
+ {
486
+ id: "VG910",
487
+ name: "Hono SSE Injection via streamSSE",
488
+ severity: "medium",
489
+ owasp: "A02:2025 Injection",
490
+ description: "Hono's streamSSE() sends Server-Sent Events to clients. If event, id, or retry fields contain unsanitized user input, attackers can inject CR/LF characters to forge SSE messages, hijack event streams, or trigger client-side actions. Related to CVE-2026-29085.",
491
+ pattern: /streamSSE\s*\(/g,
492
+ languages: ["javascript", "typescript"],
493
+ fix: "Sanitize all SSE field values by stripping CR/LF characters (\\r, \\n) before passing them to streamSSE. Never use raw user input in event, id, or retry fields.",
494
+ fixCode: '// Sanitize SSE fields\nfunction sanitizeSSE(value: string): string {\n return value.replace(/[\\r\\n]/g, "");\n}\n\n// Usage with Hono streamSSE\nreturn streamSSE(c, async (stream) => {\n await stream.writeSSE({\n event: sanitizeSSE(eventName),\n data: sanitizeSSE(data),\n id: sanitizeSSE(id),\n });\n});',
495
+ compliance: ["SOC2:CC7.1"],
496
+ },
485
497
  ];
@@ -6,4 +6,4 @@ export interface Finding {
6
6
  }
7
7
  export declare function analyzeCode(code: string, language: string, framework?: string, filePath?: string, configDir?: string, rules?: SecurityRule[]): Finding[];
8
8
  export declare function formatFindingsJson(findings: Finding[], extra?: Record<string, unknown>): string;
9
- export declare function checkCode(code: string, language: string, framework?: string, filePath?: string, configDir?: string, format?: "markdown" | "json", rules?: SecurityRule[]): string;
9
+ export declare function checkCode(code: string, language: string, framework?: string, filePath?: string, configDir?: string, format?: "markdown" | "json" | "buddy", rules?: SecurityRule[]): string;
@@ -1,3 +1,4 @@
1
+ import { basename } from "path";
1
2
  import { owaspRules } from "../data/rules/index.js";
2
3
  import { loadConfig } from "../utils/config.js";
3
4
  import { loadIgnoreFile, isIgnored } from "../utils/ignore.js";
@@ -421,6 +422,9 @@ export function checkCode(code, language, framework, filePath, configDir, format
421
422
  if (format === "json") {
422
423
  return formatFindingsJson(findings);
423
424
  }
425
+ if (format === "buddy") {
426
+ return formatBuddyOutput(findings, filePath);
427
+ }
424
428
  if (findings.length === 0) {
425
429
  return formatCleanReport(language, framework);
426
430
  }
@@ -547,3 +551,49 @@ function formatReport(findings, language, framework) {
547
551
  }
548
552
  return lines.join("\n");
549
553
  }
554
+ // ─── Buddy Format ────────────────────────────────────────────────
555
+ function severityWeight(s) {
556
+ return s === "critical" ? 4 : s === "high" ? 3 : s === "medium" ? 2 : 1;
557
+ }
558
+ function formatBuddyOutput(findings, filePath) {
559
+ const counts = { critical: 0, high: 0, medium: 0, low: 0 };
560
+ for (const f of findings) {
561
+ const sev = f.rule.severity;
562
+ if (sev in counts)
563
+ counts[sev]++;
564
+ }
565
+ let score = 100;
566
+ score -= counts.critical * 15;
567
+ score -= counts.high * 8;
568
+ score -= counts.medium * 3;
569
+ score -= counts.low * 1;
570
+ score = Math.max(0, Math.min(100, score));
571
+ const grade = score >= 90 ? "A" : score >= 75 ? "B" : score >= 60 ? "C" : score >= 40 ? "D" : "F";
572
+ const faces = {
573
+ A: "\\[^_^]/",
574
+ B: " [^_^]b",
575
+ C: " [o_o] ",
576
+ D: " [>_<] ",
577
+ F: " [X_X]!",
578
+ };
579
+ const face = faces[grade] || faces.C;
580
+ const messages = {
581
+ A: ["All clear, captain!", "Fort Knox level!", "Zero issues. Nice!", "Secure & clean!"],
582
+ B: ["Looking good!", "Almost perfect!", "Solid work!", "Just minor things."],
583
+ C: ["Some issues here...", "Needs attention.", "Review recommended."],
584
+ D: ["Multiple issues!", "Fix these ASAP.", "Getting risky..."],
585
+ F: ["Red alert!", "Critical issues!", "Stop and fix now!", "Danger zone!"],
586
+ };
587
+ const pool = messages[grade] || messages.C;
588
+ const msg = pool[Math.floor(Math.random() * pool.length)];
589
+ if (findings.length === 0) {
590
+ return `🛡️ ${face} GuardVibe: ${grade} [${score}] ✓ ${msg}`;
591
+ }
592
+ const sorted = [...findings].sort((a, b) => severityWeight(b.rule.severity) - severityWeight(a.rule.severity));
593
+ const top = sorted[0];
594
+ const fileName = filePath ? basename(filePath) : "unknown";
595
+ const severityIcon = counts.critical > 0 ? "🚨" : counts.high > 0 ? "⚠" : "⚡";
596
+ const total = counts.critical + counts.high + counts.medium + counts.low;
597
+ const detail = `${total} issue${total > 1 ? "s" : ""} — ${top.rule.name} (${fileName}:${top.line})`;
598
+ return `🛡️ ${face} GuardVibe: ${grade} [${score}] ${severityIcon} ${detail} — ${msg}`;
599
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "2.3.9",
3
+ "version": "2.4.1",
4
4
  "description": "Security MCP for vibe coding. 313 rules, 25 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": {