guardvibe 3.1.37 → 3.1.38
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,17 @@ 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.38] - 2026-06-07
|
|
9
|
+
|
|
10
|
+
### Fixed — false-positive precision, verified one rule at a time (no rule-count change, 438 / 36)
|
|
11
|
+
Each claimed false positive was checked against the actual code in the real-world corpus; only genuine FP classes were narrowed, with an old-vs-new diff confirming zero true-positive loss (14 FPs removed corpus-wide, 0 TP lost).
|
|
12
|
+
- **VG434** (Drizzle) retargeted from the *safe* `sql\`${value}\`` tag (which parameterizes interpolations) to the real injection vector — `sql.raw()` interpolated into an executed query. Renamed to "Drizzle sql.raw() Injection".
|
|
13
|
+
- **VG514** (Docker Compose secret) no longer fires when the value is an env-var reference (`${VAR}` / `$VAR`); hardcoded literals still fire.
|
|
14
|
+
- **VG001** (hardcoded credentials) skips kebab-case slug values under an uppercase-led enum/constant name (e.g. `UserMissingPassword = "missing-password"`) and values explicitly marked as mock/placeholder (e.g. `"MOCK_DAILY_API_KEY"`).
|
|
15
|
+
- **VG139** (TLS verification disabled) is skipped in test files and no longer matches a `skipVerify = false` substring; real `rejectUnauthorized: false` in production code still fires (the prior "FP" claim was wrong — those are real vulnerabilities).
|
|
16
|
+
|
|
17
|
+
Gate green (build / lint / test / self-audit PASS / A / 0).
|
|
18
|
+
|
|
8
19
|
## [3.1.37] - 2026-06-07
|
|
9
20
|
|
|
10
21
|
### Added — taint + secret scanning on the `check` path (no rule-count change, 438 / 36)
|
|
@@ -127,7 +127,7 @@ export const advancedSecurityRules = [
|
|
|
127
127
|
severity: "critical",
|
|
128
128
|
owasp: "A02:2025 Cryptographic Failures",
|
|
129
129
|
description: "TLS certificate verification is disabled, allowing man-in-the-middle attacks. All HTTPS connections become insecure.",
|
|
130
|
-
pattern: /(?:NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*["']0["']|rejectUnauthorized\s*:\s*false|verify\s*=\s*False|InsecureSkipVerify\s*:\s*true)/gi,
|
|
130
|
+
pattern: /(?:NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*["']0["']|rejectUnauthorized\s*:\s*false|(?<![\w$])verify\s*=\s*False|InsecureSkipVerify\s*:\s*true)/gi,
|
|
131
131
|
languages: ["javascript", "typescript", "python", "go"],
|
|
132
132
|
fix: "Never disable TLS verification in production. Fix certificate issues instead.",
|
|
133
133
|
fixCode: '// BAD: disables all TLS verification\n// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";\n// const agent = new https.Agent({ rejectUnauthorized: false });\n\n// GOOD: fix the certificate issue\n// - Use valid certificates (Let\'s Encrypt)\n// - Add CA certificate to Node: --use-openssl-ca\n// - For self-signed dev certs: only disable in NODE_ENV=development',
|
|
@@ -41,13 +41,13 @@ export const databaseRules = [
|
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
id: "VG434",
|
|
44
|
-
name: "Drizzle
|
|
44
|
+
name: "Drizzle sql.raw() Injection",
|
|
45
45
|
severity: "critical",
|
|
46
46
|
owasp: "A03:2025 Injection",
|
|
47
|
-
description: "Drizzle
|
|
48
|
-
pattern: /(?:db\.execute|db\.run|db\.get|db\.all)\s*\(\s*sql`[^`]*\$\{/g,
|
|
47
|
+
description: "sql.raw() interpolated into an executed Drizzle query bypasses parameter binding, enabling SQL injection. Drizzle's sql`${value}` tag binds interpolations safely — only sql.raw() escapes that.",
|
|
48
|
+
pattern: /(?:db\.execute|db\.run|db\.get|db\.all)\s*\(\s*sql`[^`]*\$\{\s*sql\.raw\b/g,
|
|
49
49
|
languages: ["javascript", "typescript"],
|
|
50
|
-
fix: "
|
|
50
|
+
fix: "Avoid sql.raw() with dynamic input. Use bound ${value} interpolation, or restrict raw fragments to a hardcoded allowlist of column/table identifiers.",
|
|
51
51
|
fixCode: 'import { sql } from "drizzle-orm";\n\nconst result = await db.execute(\n sql`SELECT * FROM users WHERE id = ${sql.placeholder("id")}`,\n { id: userId }\n);',
|
|
52
52
|
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
53
53
|
},
|
|
@@ -451,7 +451,7 @@ 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", "VG137"].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", "VG139"].includes(rule.id))
|
|
455
455
|
continue;
|
|
456
456
|
// VG137 (Debug Endpoint Exposes System Information) also misfires on build/test config
|
|
457
457
|
// files: a `<rootDir>/test/` mapper or a `/test` path string near `process.env` in
|
|
@@ -944,6 +944,24 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
944
944
|
if (canonical(idValuePair[1]) === canonical(idValuePair[2]))
|
|
945
945
|
continue;
|
|
946
946
|
}
|
|
947
|
+
// Skip enum/constant members whose value is a kebab-case slug (lowercase words
|
|
948
|
+
// joined by hyphens, no digits) under an uppercase-led name — error codes like
|
|
949
|
+
// `UserMissingPassword = "missing-password"`. The uppercase-name requirement keeps a
|
|
950
|
+
// lowercase `password = "my-secret"` firing (only enum/const members get the pass).
|
|
951
|
+
// The value is captured with a flat (ReDoS-safe) char class, then validated by
|
|
952
|
+
// splitting on '-' rather than a nested-quantifier regex.
|
|
953
|
+
const slugPair = matchedLine.match(/\b([A-Za-z_][A-Za-z0-9_]*)\s*[:=]\s*["']([a-z][a-z-]*)["']/);
|
|
954
|
+
if (slugPair && /^[A-Z]/.test(slugPair[1])) {
|
|
955
|
+
const parts = slugPair[2].split("-");
|
|
956
|
+
const isKebabSlug = parts.length >= 2 && parts.every(p => p.length > 0 && /^[a-z]+$/.test(p));
|
|
957
|
+
if (isKebabSlug)
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
// Skip values explicitly marked as mock/placeholder — `DAILY_API_KEY = "MOCK_DAILY_API_KEY"`,
|
|
961
|
+
// `apiKey: "your-api-key-here"`. A value literally named as a placeholder is not a secret.
|
|
962
|
+
const valuePair = matchedLine.match(/[:=]\s*["']([^"'\n]{3,})["']/);
|
|
963
|
+
if (valuePair && /^(?:mock|example|sample|demo|fake|dummy|stub|placeholder|changeme|change-me|your[-_]|xxx|todo|replace[-_]?me)/i.test(valuePair[1]))
|
|
964
|
+
continue;
|
|
947
965
|
// Skip SCREAMING_SNAKE error/status codes whose value is digits-only.
|
|
948
966
|
// e.g. `INVALID_PASSWORD = "5020"` — error code, not a credential.
|
|
949
967
|
if (/\b[A-Z][A-Z0-9_]*\s*=\s*["']\d+["']/.test(matchedLine))
|
|
@@ -958,6 +976,16 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
958
976
|
&& /^[A-Za-z][A-Za-z .,!?'’()-]*\s[A-Za-z .,!?'’()-]+$/.test(msgPair[2]))
|
|
959
977
|
continue;
|
|
960
978
|
}
|
|
979
|
+
// VG514 (Docker Compose Hardcoded Secret): the match spans the `environment:` block,
|
|
980
|
+
// so the flagged secret VALUE is the last `KEY: value` in match[0]. When that value is
|
|
981
|
+
// an env-var reference (`${VAR}` / `$VAR`) it is NOT hardcoded — the canonical secure
|
|
982
|
+
// pattern (compose reads it from .env). Hardcoded literals (`POSTGRES_PASSWORD=magical`)
|
|
983
|
+
// still fire.
|
|
984
|
+
if (rule.id === "VG514") {
|
|
985
|
+
const secretVal = match[0].match(/(?:SECRET|PASSWORD|TOKEN|KEY|CREDENTIAL)\w*\s*[=:]\s*["']?([^"'\s]+)/i);
|
|
986
|
+
if (secretVal && /^\$\{?\w+\}?$/.test(secretVal[1]))
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
961
989
|
// VG1083 (JWT verification bypass): jwt.decode() is fine when used only to peek at a
|
|
962
990
|
// token that is ALSO verified (decode-then-verify). Skip the decode branch when a real
|
|
963
991
|
// signature verification exists in the file. (The none-algorithm branch always fires.)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.38",
|
|
4
4
|
"mcpName": "io.github.goklab/guardvibe",
|
|
5
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",
|