guardvibe 3.1.33 → 3.1.35

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/CHANGELOG.md CHANGED
@@ -5,6 +5,35 @@ All notable changes to GuardVibe are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.1.35] - 2026-06-07
9
+
10
+ ### Fixed — false-positive precision on real production apps (no rule-count change, 436 / 36)
11
+ Surfaced by the precision half of the quality sweep; each narrowing was verified against the cited code and confirmed (via an old-vs-new diff over the corpus) to remove only false positives, with zero true-positive loss.
12
+ - **VG123 / VG010** no longer flag a parameterized IN-clause built from placeholder generation (`id IN (${ids.map(() => '?').join(',')})` with values passed as the params array).
13
+ - **VG137** (debug endpoint) no longer fires on build/test config files (`vite.config`, `jest-e2e`, `playwright.config`, `vitest`, etc.) where a `/test` path string sits near `process.env`.
14
+ - **VG1005** (Supabase `.or()` filter injection) now requires actual Supabase usage in the file, ending the collision with Zod's `.or()` schema combinator.
15
+ - **VG968** (cron `CRON_SECRET`) recognizes Vercel/QStash signature verification (`verifyVercelSignature`, `verifyQstashSignature`, `Receiver`) as valid cron auth.
16
+ - **VG951** (BOLA) recognizes tenant compound-where ownership (`where: { id, projectId | workspaceId | teamId }`).
17
+ - **VG601** (Stripe webhook) recognizes non-Stripe signature verification (QStash/Vercel/generic `verifySignature`).
18
+
19
+ Self-audit PASS / A / 0, gate green.
20
+
21
+ ## [3.1.34] - 2026-06-07
22
+
23
+ ### Added — recall (false-negative) improvements (433 → 436 rules, 36 tools)
24
+ Surfaced by a recall battery of canonical vulnerable snippets; each gap was reproduced, fixed, and given positive + negative tests. A ReDoS regression guard for all rule patterns (`tests/meta/redos.test.ts`) was added and caught a polynomial backtrack in one of these very changes before release.
25
+ - **VG010** now catches the most common concat-SQLi style where the SQL string embeds a quote to wrap the value (`"... name = '" + name + "'"`), and Sequelize `literal()` raw fragments.
26
+ - **VG014** extended to `vm.runInNewContext`/`runInContext`/`runInThisContext`/`compileFunction` and `new vm.Script`.
27
+ - **VG070** extended to `unserialize()` (node-serialize), funcster, cryo.
28
+ - **VG103** extended to user-controlled bracket assignment (`obj[req.body.key] = …`) and lodash `_.set`/`objectPath.set`.
29
+ - **VG102** extended to `res.sendFile`/`res.download` path traversal.
30
+ - **VG409** extended to open redirect via `res.setHeader("Location", userInput)`.
31
+ - **VG1080** (new) DOM XSS via `document.write()`/`writeln()` with user input.
32
+ - **VG1081** (new) insecure block cipher mode — AES/DES ECB and the deprecated `crypto.createCipher`.
33
+ - **VG1082** (new) server-side template injection — `Handlebars.compile`/`ejs.render`/`pug`/`nunjucks`/lodash `_.template` on user-controlled template source.
34
+
35
+ Self-audit PASS / A / 0, gate green, determinism preserved across the corpus.
36
+
8
37
  ## [3.1.33] - 2026-06-07
9
38
 
10
39
  ### Fixed — false-positive precision (no rule-count change, stays 433 / 36)
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.** 433 security rules, 36 tools covering the entire AI-generated code journey — from first line to production deployment.
9
+ **The security MCP built for vibe coding.** 436 security rules, 36 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
- - **433 security rules, 36 tools** purpose-built for the stacks AI agents generate
17
+ - **436 security rules, 36 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
@@ -52,7 +52,7 @@ GuardVibe is purpose-built for the AI coding workflow. Traditional tools are exc
52
52
  | CVE version detection | 67 packages, refreshed daily | Extensive | Extensive |
53
53
  | Compliance mapping (SOC2, PCI-DSS, HIPAA) | Built-in | Paid tier | None |
54
54
  | SARIF CI/CD export | Yes | Yes | Limited |
55
- | Rule count | 433 (focused, 68 AI-native) | 5000+ (broad) | N/A |
55
+ | Rule count | 436 (focused, 68 AI-native) | 5000+ (broad) | N/A |
56
56
 
57
57
  **When to use GuardVibe:** You're building with AI agents and want security scanning integrated into your coding workflow — no dashboard, no account, no CI setup.
58
58
 
@@ -242,7 +242,7 @@ Malicious postinstall scripts, unpinned GitHub Actions, CI `npm` provenance / `-
242
242
 
243
243
  All scanning tools support `format: "json"` for machine-readable output.
244
244
 
245
- ## Security Rules (433 rules across 25 modules)
245
+ ## Security Rules (436 rules across 25 modules)
246
246
 
247
247
  | Category | Rules | Coverage |
248
248
  |----------|-------|----------|
@@ -457,7 +457,7 @@ If your AI agent cannot connect to GuardVibe:
457
457
 
458
458
  1. **Restart your IDE/agent.** MCP servers are started by the host application. After running `npx guardvibe init`, restart Claude Code, Cursor, or Gemini CLI for the config to take effect.
459
459
  2. **Check the config path.** Run `npx guardvibe init claude` again and verify the output shows the correct config file location (`.mcp.json` in your project root for Claude Code, `.cursor/mcp.json` for Cursor).
460
- 3. **Re-run `init` to upgrade.** When upgrading GuardVibe, re-run `npx guardvibe init claude` — `.mcp.json` is pinned to a specific version (e.g. `guardvibe@3.1.33`) at init time for fast deterministic startup. As of v3.1.2 the re-run also rewrites stale pins automatically (`Upgraded GuardVibe pin (3.1.27 → 3.1.28)`); since v3.1.27 the PostToolUse hook command is pinned to the same version (was `@latest`) and re-run upgrades a stale hook too. The same applies to `npx guardvibe hook install` and `npx guardvibe ci github` (since v3.1.3) — both are version-pinned at install/generate time and re-run to upgrade.
460
+ 3. **Re-run `init` to upgrade.** When upgrading GuardVibe, re-run `npx guardvibe init claude` — `.mcp.json` is pinned to a specific version (e.g. `guardvibe@3.1.35`) at init time for fast deterministic startup. As of v3.1.2 the re-run also rewrites stale pins automatically (`Upgraded GuardVibe pin (3.1.27 → 3.1.28)`); since v3.1.27 the PostToolUse hook command is pinned to the same version (was `@latest`) and re-run upgrades a stale hook too. The same applies to `npx guardvibe hook install` and `npx guardvibe ci github` (since v3.1.3) — both are version-pinned at install/generate time and re-run to upgrade.
461
461
  4. **Pre-3.1.1 users won't see the auto-update banner.** GuardVibe started writing a once-per-day "newer version available" notice to stderr in v3.1.1. If your install predates that, you'll never see it — run `npx -y guardvibe@latest init <host>` once to bake in the latest pin and start receiving banners on subsequent sessions.
462
462
  5. **Verify Node.js version.** GuardVibe requires Node.js >= 18.0.0. Check with `node --version`.
463
463
  6. **Check npx cache.** If you upgraded GuardVibe and the old version is cached, run `npx -y guardvibe@latest` to force the latest version.
@@ -20,7 +20,7 @@ export const apiSecurityRules = [
20
20
  severity: "critical",
21
21
  owasp: "API1:2023 Broken Object Level Authorization",
22
22
  description: "Delete or update operation uses user-supplied ID without verifying resource ownership. Any authenticated user can modify or delete other users' resources.",
23
- pattern: /(?:delete|update|destroy|remove)\s*\(\s*\{?\s*(?:where\s*:\s*\{)?\s*(?:id|_id)\s*:\s*(?:req\.(?:params|query|body)|params\.|args\.|input\.)(?:(?!userId|user_id|ownerId|owner_id|createdBy|created_by|author|authorId|author_id|email|userEmail|accountId|account_id|tenantId|tenant_id|orgId|org_id|organizationId)[\s\S]){0,200}?\}/gi,
23
+ pattern: /(?:delete|update|destroy|remove)\s*\(\s*\{?\s*(?:where\s*:\s*\{)?\s*(?:id|_id)\s*:\s*(?:req\.(?:params|query|body)|params\.|args\.|input\.)(?:(?!userId|user_id|ownerId|owner_id|createdBy|created_by|author|authorId|author_id|email|userEmail|accountId|account_id|tenantId|tenant_id|orgId|org_id|organizationId|projectId|project_id|workspaceId|workspace_id|teamId|team_id)[\s\S]){0,200}?\}/gi,
24
24
  languages: ["javascript", "typescript"],
25
25
  fix: "Include the authenticated user's ID in the where clause to prevent unauthorized modifications.",
26
26
  fixCode: '// Scope mutations to the authenticated user\nconst { userId } = await auth();\nawait prisma.post.delete({\n where: { id: params.id, userId }, // ownership!\n});',
@@ -55,7 +55,7 @@ export const coreRules = [
55
55
  severity: "critical",
56
56
  owasp: "A02:2025 Injection",
57
57
  description: "String concatenation, template literals, or f-strings used in SQL queries — whether inline in the DB call or assembled in a variable/return first. This allows SQL injection attacks.",
58
- pattern: /(?:query|execute|raw|sql|all|run|get|exec|prepare|QueryRow|QueryContext)\s*\(\s*(?:`[^`]*\$\{|['"][^'"]*['"]\s*\+\s*|f"[^"]*\{|f'[^']*\{|['"][^'"]*['"]\s*%\s*|['"][^'"]*['"]\s*\.format\s*\(|['"][^'"]*['"]\s*,\s*(?:req\.|request\.|params\.|body\.|args))|(?:=|return)\s*(?:`\s*(?:SELECT|INSERT|UPDATE|DELETE)\b[^`]*\b(?:FROM|INTO|SET|WHERE|VALUES)\b[^`]*\$\{|['"]\s*(?:SELECT|INSERT|UPDATE|DELETE)\b[^\n]*?\b(?:FROM|INTO|SET|WHERE|VALUES)\b[^\n]*?['"]\s*\+\s*\w)/gi,
58
+ pattern: /(?:query|execute|raw|sql|all|run|get|exec|prepare|literal|QueryRow|QueryContext)\s*\(\s*(?:`[^`]*\$\{|(?:"[^"]*"|'[^']*')\s*\+\s*|f"[^"]*\{|f'[^']*\{|['"][^'"]*['"]\s*%\s*|['"][^'"]*['"]\s*\.format\s*\(|['"][^'"]*['"]\s*,\s*(?:req\.|request\.|params\.|body\.|args))|(?:=|return)\s*(?:`\s*(?:SELECT|INSERT|UPDATE|DELETE)\b[^`]*\b(?:FROM|INTO|SET|WHERE|VALUES)\b[^`]*\$\{|['"]\s*(?:SELECT|INSERT|UPDATE|DELETE)\b[^\n]*?\b(?:FROM|INTO|SET|WHERE|VALUES)\b[^\n]*?['"]\s*\+\s*\w)/gi,
59
59
  languages: ["javascript", "typescript", "python", "go"],
60
60
  fix: "Use parameterized queries: db.query('SELECT * FROM users WHERE id = $1', [userId]). Python: cursor.execute('SELECT * FROM users WHERE id = %s', (user_id,)). Never concatenate user input into SQL strings.",
61
61
  fixCode: "// Use parameterized queries\ndb.query('SELECT * FROM users WHERE id = $1', [userId]);\n// Python: cursor.execute('SELECT * FROM users WHERE id = %s', (user_id,))",
@@ -116,7 +116,7 @@ export const coreRules = [
116
116
  severity: "critical",
117
117
  owasp: "A02:2025 Injection",
118
118
  description: "Dynamic code execution function detected. This can run arbitrary code and is a major security risk.",
119
- pattern: /(?:\beval\s*\(|new\s+Function\s*\()/gi,
119
+ pattern: /(?:\beval\s*\(|new\s+Function\s*\(|\bvm\s*\.\s*(?:runInNewContext|runInContext|runInThisContext|compileFunction)\s*\(|new\s+vm\s*\.\s*Script\s*\()/gi,
120
120
  languages: ["javascript", "typescript", "python"],
121
121
  fix: "Avoid dynamic code execution. Use JSON.parse() for JSON data. Use a sandboxed environment if absolutely required.",
122
122
  fixCode: "// Use JSON.parse for data\nconst data = JSON.parse(input);\n// Alternatives: use a proper parser for your data format\n// const fn = new " + "Function('x', 'return x * 2'); // only if absolutely needed",
@@ -224,7 +224,7 @@ export const coreRules = [
224
224
  severity: "high",
225
225
  owasp: "A08:2025 Data Integrity Failures",
226
226
  description: "Deserializing untrusted data can lead to remote code execution.",
227
- pattern: /(?:JSON\.parse\s*\(\s*(?:req\.|request\.|body)|pickle\.loads?\s*\(|yaml\.(?:load|unsafe_load)\s*\()/gi,
227
+ pattern: /(?:JSON\.parse\s*\(\s*(?:req\.|request\.|body)|pickle\.loads?\s*\(|yaml\.(?:load|unsafe_load)\s*\(|\bunserialize\s*\(|\bfuncster\s*\.|\bcryo\s*\.\s*parse\s*\()/gi,
228
228
  languages: ["javascript", "typescript", "python"],
229
229
  fix: "Validate all deserialized data with a schema (zod, joi) before processing.",
230
230
  fixCode: "// Validate with schema after parsing\nimport { z } from 'zod';\nconst schema = z.object({ name: z.string() });\nconst data = schema.parse(JSON.parse(req.body));",
@@ -284,7 +284,7 @@ export const coreRules = [
284
284
  severity: "high",
285
285
  owasp: "A01:2025 Broken Access Control",
286
286
  description: "User input used in file paths without sanitization.",
287
- pattern: /(?:readFile|readFileSync|createReadStream|open|path\.join|path\.resolve)\s*\([^)]*(?:req\.|request\.|params\.|body\.|query\.)/gi,
287
+ pattern: /(?:readFile|readFileSync|createReadStream|open|sendFile|download|path\.join|path\.resolve)\s*\([^)]*(?:req\.|request\.|params\.|body\.|query\.)/gi,
288
288
  languages: ["javascript", "typescript", "python", "go"],
289
289
  fix: "Sanitize file paths: remove ../ sequences, verify the result is within the expected directory.",
290
290
  fixCode: "import path from 'path';\nconst safePath = path.resolve('/uploads', filename);\nif (!safePath.startsWith('/uploads/')) throw new Error('Invalid path');",
@@ -296,7 +296,7 @@ export const coreRules = [
296
296
  severity: "high",
297
297
  owasp: "A02:2025 Injection",
298
298
  description: "Deep merge or object assignment from user input can lead to prototype pollution.",
299
- pattern: /(?:Object\.assign|\bmerge\b|deepMerge|\bextend\b)\s*\([^)]*(?:req\.|request\.|\bbody\b|\bparams\b)/gi,
299
+ pattern: /(?:(?:Object\.assign|\bmerge\b|deepMerge|\bextend\b|(?:_|lodash)\.set(?:With)?|objectPath\.set|dotProp\.set)\s*\([^)]*(?:req\.|request\.|\bbody\b|\bparams\b)|\[\s*(?:req|request)\.(?:body|params|query)\.[\w.]+\s*\]\s*=(?!=))/gi,
300
300
  languages: ["javascript", "typescript"],
301
301
  fix: "Use Object.create(null) for lookup objects. Validate that keys don't include __proto__, constructor, or prototype.",
302
302
  fixCode: "// Use Object.create(null) for lookups\nconst lookup = Object.create(null);\n// Validate keys\nconst forbidden = ['__proto__', 'constructor', 'prototype'];\nif (forbidden.includes(key)) throw new Error('Invalid key');",
@@ -122,7 +122,7 @@ export const modernStackRules = [
122
122
  severity: "high",
123
123
  owasp: "A01:2025 Broken Access Control",
124
124
  description: "Vercel cron job endpoint does not verify the CRON_SECRET header. Anyone can trigger the cron job by calling the endpoint directly.",
125
- pattern: /(?:\/api\/cron|cron)[\s\S]*?export\s+(?:async\s+)?function\s+GET\s*\([^)]*\)\s*\{(?:(?!CRON_SECRET|authorization|Bearer|verifySignature|x-vercel-cron)[\s\S]){10,}?(?:prisma|db|supabase|fetch|sql|resend|stripe)\.\w+/g,
125
+ pattern: /(?:\/api\/cron|cron)[\s\S]*?export\s+(?:async\s+)?function\s+GET\s*\([^)]*\)\s*\{(?:(?!CRON_SECRET|authorization|Bearer|verifySignature|x-vercel-cron|verifyVercelSignature|verifyQstash|verifyQstashSignature|verifySignatureAppRouter|Receiver|verifyCron)[\s\S]){10,}?(?:prisma|db|supabase|fetch|sql|resend|stripe)\.\w+/g,
126
126
  languages: ["javascript", "typescript"],
127
127
  fix: "Verify the CRON_SECRET header at the start of every cron endpoint.",
128
128
  fixCode: 'export async function GET(request: Request) {\n const authHeader = request.headers.get("authorization");\n if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {\n return new Response("Unauthorized", { status: 401 });\n }\n // ... cron job logic\n}',
@@ -117,7 +117,7 @@ export const nextjsRules = [
117
117
  severity: "medium",
118
118
  owasp: "A01:2025 Broken Access Control",
119
119
  description: "redirect() or NextResponse.redirect() uses user-controlled input (searchParams, query) which can redirect users to malicious sites.",
120
- 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,
120
+ 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|(?:res|reply)\.setHeader\s*\(\s*['"][Ll]ocation['"]\s*,\s*(?:req\.|request\.|searchParams|params\b|url\b|query\b|returnTo|callbackUrl|returnUrl|redirectUrl|destination))/gi,
121
121
  languages: ["javascript", "typescript"],
122
122
  fix: "Validate redirect URLs against an allowlist of trusted domains.",
123
123
  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}',
@@ -18,7 +18,7 @@ export const paymentRules = [
18
18
  severity: "critical",
19
19
  owasp: "A01:2025 Broken Access Control",
20
20
  description: "Stripe webhook endpoint processes events without verifying the webhook signature. Anyone can send fake payment events.",
21
- pattern: /(?:\/api\/webhook|\/api\/stripe|webhook.*stripe)[\s\S]*?(?:req\.body|request\.json|JSON\.parse)[\s\S]{0,300}?(?![\s\S]{0,300}?(?:constructEvent|verifyHeader|stripe\.webhooks|svix\.verify|webhook\.verify|verify\w*Webhook|verifyWebhookSignature|wh\.verify|crypto\.timingSafeEqual))/g,
21
+ pattern: /(?:\/api\/webhook|\/api\/stripe|webhook.*stripe)[\s\S]*?(?:req\.body|request\.json|JSON\.parse)[\s\S]{0,300}?(?![\s\S]{0,300}?(?:constructEvent|verifyHeader|stripe\.webhooks|svix\.verify|webhook\.verify|verify\w*Webhook|verifyWebhookSignature|wh\.verify|crypto\.timingSafeEqual|verifyQstashSignature|verifyQstash|verifySignature|verifyVercelSignature|Receiver\b|new\s+Webhook\b))/g,
22
22
  languages: ["javascript", "typescript"],
23
23
  fix: "Always verify Stripe webhook signatures using stripe.webhooks.constructEvent().",
24
24
  fixCode: "// Verify webhook signature\nconst sig = request.headers.get('stripe-signature')!;\nconst event = stripe.webhooks.constructEvent(\n body, sig, process.env.STRIPE_WEBHOOK_SECRET!\n);",
@@ -185,4 +185,40 @@ export const webSecurityRules = [
185
185
  fixCode: '// Set nosniff header for uploaded file responses\nres.setHeader("X-Content-Type-Options", "nosniff");\nres.setHeader("Content-Disposition", "attachment"); // force download for unknown types\nres.sendFile(filePath);',
186
186
  compliance: ["SOC2:CC6.1"],
187
187
  },
188
+ {
189
+ id: "VG1080",
190
+ name: "DOM XSS via document.write()",
191
+ severity: "high",
192
+ owasp: "A03:2025 Injection",
193
+ description: "document.write()/document.writeln() called with user-controlled or concatenated/interpolated content. document.write parses its argument as HTML, so attacker-influenced input (location, query params, cookies, window.name) leads to DOM-based cross-site scripting.",
194
+ pattern: /document\.write(?:ln)?\s*\(\s*(?:[^)]*?(?:location|document\.(?:URL|cookie|referrer)|searchParams|req\.|request\.|params\.|query\.|window\.name|\binput\b)|`[^`]*\$\{|["'][^"']*["']\s*\+)/gi,
195
+ languages: ["javascript", "typescript"],
196
+ fix: "Never build HTML with document.write from untrusted input. Use safe DOM APIs (textContent, createElement) or sanitize with DOMPurify before inserting.",
197
+ fixCode: "// BAD: document.write('<div>' + location.hash + '</div>')\n// GOOD:\nconst el = document.createElement('div');\nel.textContent = userValue; // auto-escaped\ncontainer.appendChild(el);",
198
+ compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.7"],
199
+ },
200
+ {
201
+ id: "VG1081",
202
+ name: "Insecure Block Cipher Mode (ECB / deprecated createCipher)",
203
+ severity: "high",
204
+ owasp: "A02:2025 Cryptographic Failures",
205
+ description: "AES/DES used in ECB mode (createCipheriv with an *-ecb algorithm), or the deprecated crypto.createCipher() which derives a key/IV insecurely. ECB encrypts identical plaintext blocks to identical ciphertext blocks, leaking structure; createCipher is password-derived and IV-less. Both are cryptographically broken for confidentiality.",
206
+ pattern: /(?:createCipheriv\s*\(\s*["'][^"']*-ecb["']|createDecipheriv\s*\(\s*["'][^"']*-ecb["']|crypto\s*\.\s*createCipher\s*\(\s*["'])/gi,
207
+ languages: ["javascript", "typescript"],
208
+ fix: "Use an authenticated mode: aes-256-gcm with a random 12-byte IV per message (crypto.randomBytes), or aes-256-cbc with a random IV and a separate MAC. Never use ECB; replace createCipher with createCipheriv.",
209
+ fixCode: "// GOOD: AES-256-GCM with a random IV\nconst iv = crypto.randomBytes(12);\nconst cipher = crypto.createCipheriv('aes-256-gcm', key, iv);\nconst enc = Buffer.concat([cipher.update(data), cipher.final()]);\nconst tag = cipher.getAuthTag();",
210
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req3.5", "HIPAA:§164.312(a)(2)(iv)"],
211
+ },
212
+ {
213
+ id: "VG1082",
214
+ name: "Server-Side Template Injection (SSTI)",
215
+ severity: "critical",
216
+ owasp: "A03:2025 Injection",
217
+ description: "A template engine compiles/renders a user-controlled template SOURCE (not just user data bound into a fixed template). Handlebars.compile, ejs.render/compile, pug, nunjucks.renderString, or lodash _.template on attacker-influenced input allows server-side template injection — often a path to remote code execution.",
218
+ pattern: /(?:Handlebars\.compile|ejs\.(?:render|compile)|pug\.(?:compile|render)|nunjucks\.(?:renderString|compile)|_\.template|lodash\.template|dot\.template)\s*\(\s*(?:[^,)]*?(?:req\.|request\.|\bbody\b|\bparams\b|\bquery\b|userInput|\binput\b)|`[^`]*\$\{|[^,)]*\+)/gi,
219
+ languages: ["javascript", "typescript"],
220
+ fix: "Never compile a template from user input. Keep template sources static/server-owned and pass user values only as DATA to a precompiled template. If user-authored templates are required, use a sandboxed engine with no access to globals.",
221
+ fixCode: "// BAD: ejs.render(req.body.template, data)\n// GOOD: fixed template, user value as data only\nconst tpl = ejs.compile(STATIC_TEMPLATE);\nres.send(tpl({ name: req.body.name }));",
222
+ compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
223
+ },
188
224
  ];
@@ -451,7 +451,17 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
451
451
  // agent.get('/?q=' + sqlPayload) which match the regex but aren't database calls
452
452
  // - VG042/VG678: HTTP-response/security-header rules (tests don't serve to real users)
453
453
  const isTestFile = filePath && /(?:\.(?:[\w-]+-)?(?:spec|test|e2e|stories|cy)\.(?:ts|tsx|js|jsx|mjs|cjs)$|_test\.go$|\/__tests__\/|\/__mocks__\/|\/tests?\/|\/cypress\/|\/playwright\/|\/dockertest\/|\/testutil\/|\/testhelpers?\/|\/testfixtures?\/)/i.test(filePath);
454
- if (isTestFile && ["VG001", "VG003", "VG062", "VG010", "VG011", "VG012", "VG013", "VG014", "VG042", "VG100", "VG130", "VG678", "VG955", "VG133", "VG1021", "VG409", "VG148", "VG424"].includes(rule.id))
454
+ if (isTestFile && ["VG001", "VG003", "VG062", "VG010", "VG011", "VG012", "VG013", "VG014", "VG042", "VG100", "VG130", "VG678", "VG955", "VG133", "VG1021", "VG409", "VG148", "VG424", "VG137"].includes(rule.id))
455
+ continue;
456
+ // VG137 (Debug Endpoint Exposes System Information) also misfires on build/test config
457
+ // files: a `<rootDir>/test/` mapper or a `/test` path string near `process.env` in
458
+ // vite/jest/playwright/vitest/rollup/webpack/react-router config is not an exposed
459
+ // debug HTTP endpoint. Skip those config files.
460
+ if (rule.id === "VG137" && filePath && /(?:\.config\.[cm]?[jt]sx?$|(?:^|\/)(?:vite|vitest|jest|playwright|cypress|rollup|webpack|esbuild|tsup|react-router|svelte|astro|nuxt|babel|tailwind|postcss|drizzle)[.-][\w.-]*\.[cm]?[jt]sx?$)/i.test(filePath))
461
+ continue;
462
+ // VG1005 (Supabase .or() Filter Injection) collides with Zod's `.or()` schema combinator
463
+ // and any other `.or(` method. Only fire when the file actually uses Supabase.
464
+ if (rule.id === "VG1005" && !/\bsupabase\b|createClient\s*\(|from\s+["']@supabase/i.test(code))
455
465
  continue;
456
466
  // VG955 (Missing Pagination on List Endpoint): only fire on actual request-handling
457
467
  // surfaces — API routes, App Router `route.{ts,tsx}`, pages/api, or Server Actions.
@@ -1000,7 +1010,11 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
1000
1010
  const isParameterized = /\b(?:bind|replacements)\s*:/.test(callCtx) || /[=\s](?:\$\d+|:[a-zA-Z_]\w*)\b/.test(tpl);
1001
1011
  const interps = tpl.match(/\$\{[^}]*\}/g) || [];
1002
1012
  const allSafe = interps.length > 0 && interps.every(s => /\$\{\s*[\w$.]*(?:hash|sha\d*|md5|bcrypt|argon2?|hmac|digest|encode|escape|encodeURIComponent|toString|String|Number|parseInt|parseFloat)\b/i.test(s));
1003
- if (isParameterized && allSafe)
1013
+ // Placeholder generation for a parameterized IN-clause: `id IN (${ids.map(()=>'?').join(',')})`
1014
+ // — the interpolation produces only `?` positional placeholders, values pass via the
1015
+ // params array. Inherently parameterized; not injectable.
1016
+ const allPlaceholderGen = interps.length > 0 && interps.every(s => /\.map\s*\(\s*\(?[\w,\s]*\)?\s*=>\s*["']\?["']/.test(s) || /^\$\{\s*["']\?["']\s*\}$/.test(s));
1017
+ if ((isParameterized && allSafe) || allPlaceholderGen)
1004
1018
  continue;
1005
1019
  }
1006
1020
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "3.1.33",
3
+ "version": "3.1.35",
4
4
  "mcpName": "io.github.goklab/guardvibe",
5
- "description": "Security MCP for vibe coding. 433 rules, 36 tools, CLI + doctor. Host security, auth coverage mapping, LLM-powered deep scan (IDOR/business logic), taint analysis. 67 CVE rules refreshed daily from GHSA/OSV/CISA KEV — Miasma @redhat-cloud-services compromise, Next.js May 2026 13-advisory cluster, Drizzle/MikroORM/Kysely SQL injection, Axios proxy-auth redirect leak, Hono setCookie attribute injection, Clerk SSRF, tRPC prototype pollution, @tanstack supply-chain, node-ipc protestware, OpenClaude sandbox bypass, plus the full AI-generated stack (Supabase, Stripe, Prisma, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK). 68 AI-native rules including OWASP MCP Top 10 tool-description prompt injection (VG1068), model-controlled sandbox-disable flag detection (VG1063), Session messenger exfil endpoint IOC (VG1075), and CI/CD supply-chain hardening (VG1070 npm --expect-provenance / --ignore-scripts enforcement).",
5
+ "description": "Security MCP for vibe coding. 436 rules, 36 tools, CLI + doctor. Host security, auth coverage mapping, LLM-powered deep scan (IDOR/business logic), taint analysis. 67 CVE rules refreshed daily from GHSA/OSV/CISA KEV — Miasma @redhat-cloud-services compromise, Next.js May 2026 13-advisory cluster, Drizzle/MikroORM/Kysely SQL injection, Axios proxy-auth redirect leak, Hono setCookie attribute injection, Clerk SSRF, tRPC prototype pollution, @tanstack supply-chain, node-ipc protestware, OpenClaude sandbox bypass, plus the full AI-generated stack (Supabase, Stripe, Prisma, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK). 68 AI-native rules including OWASP MCP Top 10 tool-description prompt injection (VG1068), model-controlled sandbox-disable flag detection (VG1063), Session messenger exfil endpoint IOC (VG1075), and CI/CD supply-chain hardening (VG1070 npm --expect-provenance / --ignore-scripts enforcement).",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "guardvibe": "build/cli.js",