guardvibe 3.1.34 → 3.1.36

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,27 @@ 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.36] - 2026-06-07
9
+
10
+ ### Added — high-value recall rules (436 → 438, 36 tools)
11
+ - **VG1083** JWT verification bypass — flags `jwt.decode()` of a request-supplied token used without a real signature check, and `jwt.verify(..., { algorithms: ['none'] })` (algorithm-confusion / signature stripping). The decode branch is suppressed when the same file also verifies the token (decode-then-verify is legitimate).
12
+ - **VG1084** DOM XSS via jQuery HTML insertion — `.html()/.append()/.prepend()/.after()/.before()/.replaceWith()` with user-controlled or concatenated/interpolated content (skips `.text()` and static literals).
13
+
14
+ Both validated against the real-world corpus: zero false positives (the one borderline juice-shop `jwt.decode`-then-`verify` hit is correctly suppressed). The ReDoS guard now re-measures any over-budget pattern and uses the minimum across runs, so CPU/GC load spikes can no longer cause a flaky failure while genuine backtracking (consistently slow) is still caught.
15
+
16
+ ## [3.1.35] - 2026-06-07
17
+
18
+ ### Fixed — false-positive precision on real production apps (no rule-count change, 436 / 36)
19
+ 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.
20
+ - **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).
21
+ - **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`.
22
+ - **VG1005** (Supabase `.or()` filter injection) now requires actual Supabase usage in the file, ending the collision with Zod's `.or()` schema combinator.
23
+ - **VG968** (cron `CRON_SECRET`) recognizes Vercel/QStash signature verification (`verifyVercelSignature`, `verifyQstashSignature`, `Receiver`) as valid cron auth.
24
+ - **VG951** (BOLA) recognizes tenant compound-where ownership (`where: { id, projectId | workspaceId | teamId }`).
25
+ - **VG601** (Stripe webhook) recognizes non-Stripe signature verification (QStash/Vercel/generic `verifySignature`).
26
+
27
+ Self-audit PASS / A / 0, gate green.
28
+
8
29
  ## [3.1.34] - 2026-06-07
9
30
 
10
31
  ### Added — recall (false-negative) improvements (433 → 436 rules, 36 tools)
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.** 436 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.** 438 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
- - **436 security rules, 36 tools** purpose-built for the stacks AI agents generate
17
+ - **438 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 | 436 (focused, 68 AI-native) | 5000+ (broad) | N/A |
55
+ | Rule count | 438 (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 (436 rules across 25 modules)
245
+ ## Security Rules (438 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.34`) 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.36`) 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});',
@@ -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}',
@@ -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);",
@@ -221,4 +221,28 @@ export const webSecurityRules = [
221
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
222
  compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
223
223
  },
224
+ {
225
+ id: "VG1083",
226
+ name: "JWT Verification Bypass (decode/none-algorithm)",
227
+ severity: "critical",
228
+ owasp: "A07:2025 Auth Failures",
229
+ description: "A JWT is trusted without a real signature check: jwt.decode() of a request-supplied token returns the payload WITHOUT verifying the signature (any forged token is accepted), or jwt.verify() is called with algorithms including 'none' (algorithm-confusion / signature-stripping). Either lets an attacker mint arbitrary identities/claims.",
230
+ pattern: /(?:jwt\.verify\s*\([^;]{0,200}?algorithms\s*:\s*\[[^\]]*["']none["']|(?:jwt|jsonwebtoken|jose)\s*\.\s*decode\s*\(\s*(?:req\.|request\.|token\b|authToken|bearerToken|accessToken|authorization\b|headers\b))/gi,
231
+ languages: ["javascript", "typescript"],
232
+ fix: "Always verify the signature with an explicit algorithm allowlist: jwt.verify(token, secret, { algorithms: ['HS256'] }). Never use jwt.decode() for authentication/authorization, and never include 'none' in the algorithms list.",
233
+ fixCode: "// BAD: const user = jwt.decode(req.headers.authorization);\n// GOOD:\nconst user = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] });",
234
+ compliance: ["SOC2:CC6.6", "PCI-DSS:Req6.5.10", "HIPAA:§164.312(d)"],
235
+ },
236
+ {
237
+ id: "VG1084",
238
+ name: "DOM XSS via jQuery HTML insertion",
239
+ severity: "high",
240
+ owasp: "A03:2025 Injection",
241
+ description: "jQuery DOM-insertion methods (.html(), .append(), .prepend(), .after(), .before(), .replaceWith(), .wrap*) parse their argument as HTML. Passing user-controlled or concatenated/interpolated content (location, query params, .val(), .data()) into them causes DOM-based cross-site scripting.",
242
+ pattern: /\$\([^)]*\)(?:\.\w+\([^)]*\))*?\.(?:html|append|prepend|after|before|replaceWith|wrap|wrapAll|wrapInner)\s*\(\s*(?:[^)]*?(?:location|document\.(?:URL|cookie|referrer)|searchParams|req\.|request\.|params\.|query\.|window\.name|\.val\s*\(\s*\)|\.data\s*\()|`[^`]*\$\{|["'][^"']*["']\s*\+)/gi,
243
+ languages: ["javascript", "typescript"],
244
+ fix: "Use .text() instead of .html() for untrusted content, or sanitize with DOMPurify before insertion. Build elements with $('<div>').text(value) rather than concatenating HTML strings.",
245
+ fixCode: "// BAD: $('#out').html(location.hash)\n// GOOD:\n$('#out').text(userValue); // auto-escaped\n// or: $('#out').html(DOMPurify.sanitize(html));",
246
+ compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.7"],
247
+ },
224
248
  ];
@@ -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.
@@ -948,6 +958,11 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
948
958
  && /^[A-Za-z][A-Za-z .,!?'’()-]*\s[A-Za-z .,!?'’()-]+$/.test(msgPair[2]))
949
959
  continue;
950
960
  }
961
+ // VG1083 (JWT verification bypass): jwt.decode() is fine when used only to peek at a
962
+ // token that is ALSO verified (decode-then-verify). Skip the decode branch when a real
963
+ // signature verification exists in the file. (The none-algorithm branch always fires.)
964
+ if (rule.id === "VG1083" && /\.decode\s*\(/.test(match[0]) && /jwt\.verify\s*\(|jwtVerify\s*\(|jose[\s\S]{0,60}?(?:jwtVerify|verify)/i.test(code))
965
+ continue;
951
966
  // VG138 (Plaintext Password Comparison): skip benign non-credential comparisons.
952
967
  // (1) Confirm-password match: `req.body.password == req.body.cpassword` compares two
953
968
  // user inputs from the same form, not a submission against a stored secret.
@@ -1000,7 +1015,11 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
1000
1015
  const isParameterized = /\b(?:bind|replacements)\s*:/.test(callCtx) || /[=\s](?:\$\d+|:[a-zA-Z_]\w*)\b/.test(tpl);
1001
1016
  const interps = tpl.match(/\$\{[^}]*\}/g) || [];
1002
1017
  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)
1018
+ // Placeholder generation for a parameterized IN-clause: `id IN (${ids.map(()=>'?').join(',')})`
1019
+ // — the interpolation produces only `?` positional placeholders, values pass via the
1020
+ // params array. Inherently parameterized; not injectable.
1021
+ const allPlaceholderGen = interps.length > 0 && interps.every(s => /\.map\s*\(\s*\(?[\w,\s]*\)?\s*=>\s*["']\?["']/.test(s) || /^\$\{\s*["']\?["']\s*\}$/.test(s));
1022
+ if ((isParameterized && allSafe) || allPlaceholderGen)
1004
1023
  continue;
1005
1024
  }
1006
1025
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "3.1.34",
3
+ "version": "3.1.36",
4
4
  "mcpName": "io.github.goklab/guardvibe",
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).",
5
+ "description": "Security MCP for vibe coding. 438 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",