guardvibe 2.0.0 → 2.0.2
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 +25 -3
- package/build/data/rules/core.js +1 -1
- package/build/tools/check-code.js +73 -12
- package/build/utils/config.d.ts +4 -0
- package/build/utils/config.js +1 -0
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -83,7 +83,8 @@ function setupClaudeHooksAndGuide() {
|
|
|
83
83
|
if (!existingSettings.hooks) {
|
|
84
84
|
existingSettings.hooks = {};
|
|
85
85
|
}
|
|
86
|
-
//
|
|
86
|
+
// Real security hook: scan edited files automatically after every Edit/Write
|
|
87
|
+
// This makes GuardVibe an active guardian, not a passive tool
|
|
87
88
|
if (!existingSettings.hooks.PostToolUse) {
|
|
88
89
|
existingSettings.hooks.PostToolUse = [
|
|
89
90
|
{
|
|
@@ -91,7 +92,7 @@ function setupClaudeHooksAndGuide() {
|
|
|
91
92
|
hooks: [
|
|
92
93
|
{
|
|
93
94
|
type: "command",
|
|
94
|
-
command: "
|
|
95
|
+
command: "npx -y guardvibe check $CLAUDE_FILE_PATH --format markdown 2>/dev/null || true"
|
|
95
96
|
}
|
|
96
97
|
]
|
|
97
98
|
}
|
|
@@ -101,7 +102,28 @@ function setupClaudeHooksAndGuide() {
|
|
|
101
102
|
console.log(` [OK] Claude Code hooks configured (.claude/settings.json)`);
|
|
102
103
|
// Add CLAUDE.md guidance if not present
|
|
103
104
|
const claudeMdPath = join(process.cwd(), "CLAUDE.md");
|
|
104
|
-
const guardvibeBlock =
|
|
105
|
+
const guardvibeBlock = `
|
|
106
|
+
## Security Rules (enforced by GuardVibe MCP)
|
|
107
|
+
|
|
108
|
+
### Mandatory checks
|
|
109
|
+
- BEFORE writing any API route, Server Action, or database query: call \`scan_file\` on the file after editing
|
|
110
|
+
- BEFORE committing: call \`scan_staged\` to catch issues in staged files
|
|
111
|
+
- When GuardVibe reports HIGH or CRITICAL: fix immediately before moving on
|
|
112
|
+
|
|
113
|
+
### Writing secure code
|
|
114
|
+
- Every API route handler MUST have auth check before DB access
|
|
115
|
+
- Every POST endpoint MUST have input validation (zod/joi schema)
|
|
116
|
+
- Never pass user input to \`innerHTML\`, \`eval\`, \`exec\`, or raw SQL
|
|
117
|
+
- Never pass user-controlled URLs to \`fetch()\` without allowlist validation
|
|
118
|
+
- Always use \`select:\` in Prisma queries from Server Actions (never return full objects)
|
|
119
|
+
- Always validate redirect URLs against trusted domain allowlist
|
|
120
|
+
- Set security headers in next.config.ts (CSP, HSTS, X-Frame-Options)
|
|
121
|
+
|
|
122
|
+
### When in doubt
|
|
123
|
+
- Use \`explain_remediation\` with the rule ID for detailed fix guidance
|
|
124
|
+
- Use \`scan_changed_files\` with base branch before creating PR
|
|
125
|
+
- Use \`check_code\` to verify a code snippet is secure before applying
|
|
126
|
+
`;
|
|
105
127
|
if (existsSync(claudeMdPath)) {
|
|
106
128
|
const content = readFileSync(claudeMdPath, "utf-8");
|
|
107
129
|
if (!content.includes("GuardVibe")) {
|
package/build/data/rules/core.js
CHANGED
|
@@ -392,7 +392,7 @@ export const coreRules = [
|
|
|
392
392
|
severity: "high",
|
|
393
393
|
owasp: "A10:2025 Server-Side Request Forgery",
|
|
394
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))?|
|
|
395
|
+
pattern: /(?:fetch|axios\.(?:get|post|put|delete|patch|request)|got(?:\.(?:get|post|put|delete|patch))?|http\.(?:get|request)|https\.(?:get|request)|urllib\.request\.urlopen)\s*\(\s*(?!["'`]https?:\/\/|["'`]\/|`\$\{process\.env)(?:[a-zA-Z_$]\w*)\s*[,)]/gi,
|
|
396
396
|
languages: ["javascript", "typescript", "python"],
|
|
397
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
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);',
|
|
@@ -126,6 +126,53 @@ function isRuleDefinitionFile(code, filePath) {
|
|
|
126
126
|
}
|
|
127
127
|
return false;
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Detect if code contains an auth guard pattern — regardless of function name.
|
|
131
|
+
* Matches patterns like:
|
|
132
|
+
* const { userId } = await someFunction(); if (!userId) return/throw;
|
|
133
|
+
* const { error } = await someFunction(); if (error) return error;
|
|
134
|
+
* const session = await someFunction(); if (!session) throw/return;
|
|
135
|
+
* await someFunction(); // + early return pattern
|
|
136
|
+
*
|
|
137
|
+
* This is naming-agnostic: works for requireAdmin, verifyAuth, checkPermission,
|
|
138
|
+
* ensureLoggedIn, or any custom auth wrapper.
|
|
139
|
+
*/
|
|
140
|
+
function hasAuthGuardPattern(code) {
|
|
141
|
+
// Pattern 1: destructured result checked with early return/throw
|
|
142
|
+
// e.g., const { userId } = await xxx(); if (!userId) return;
|
|
143
|
+
// e.g., const { error } = await xxx(); if (error) return error;
|
|
144
|
+
if (/(?:const|let)\s+\{[^}]*\}\s*=\s*await\s+\w+\s*\([^)]*\)\s*;?\s*\n\s*if\s*\(\s*!?\w+/.test(code)) {
|
|
145
|
+
if (/if\s*\([^)]*\)\s*(?:return|throw)\b/.test(code))
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
// Pattern 2: result assigned then checked
|
|
149
|
+
// e.g., const session = await xxx(); if (!session) return;
|
|
150
|
+
if (/(?:const|let)\s+\w+\s*=\s*await\s+\w+\s*\([^)]*\)\s*;?\s*\n\s*if\s*\(\s*!\w+/.test(code)) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
// Pattern 3: function called with await that contains auth-like keywords in name
|
|
154
|
+
// Broad catch: any function name containing auth/session/permission/guard/verify/protect
|
|
155
|
+
if (/await\s+(?:\w+\.)*\w*(?:auth|Auth|session|Session|permission|Permission|guard|Guard|verify|Verify|protect|Protect|check|Check|ensure|Ensure|require|Require|assert|Assert|authorize|Authorize)\w*\s*\(/i.test(code)) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Detect if code has a role/permission check — regardless of function name.
|
|
162
|
+
* Matches: role === "admin", permission check, role-based condition.
|
|
163
|
+
*/
|
|
164
|
+
function hasRoleCheckPattern(code) {
|
|
165
|
+
// Direct role/permission comparison
|
|
166
|
+
if (/(?:role|permission|isAdmin|access|level)\s*(?:===|!==|==|!=)\s*["']/i.test(code))
|
|
167
|
+
return true;
|
|
168
|
+
// Function call with role/permission-like args
|
|
169
|
+
if (/(?:check|require|verify|ensure|assert|has|can)\w*\s*\(\s*["'](?:admin|manager|editor|owner|moderator|superadmin)/i.test(code))
|
|
170
|
+
return true;
|
|
171
|
+
// Destructured role check: const { role } = ...; if (role !== "admin")
|
|
172
|
+
if (/\brole\b[\s\S]{0,100}?(?:!==|===)\s*["']/i.test(code))
|
|
173
|
+
return true;
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
129
176
|
export function analyzeCode(code, language, framework, filePath, configDir, rules) {
|
|
130
177
|
// Skip files that are security rule definitions (they intentionally contain
|
|
131
178
|
// vulnerable code patterns as regex matchers and fixCode examples)
|
|
@@ -136,6 +183,15 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
136
183
|
const findings = [];
|
|
137
184
|
const lines = code.split("\n");
|
|
138
185
|
const suppressions = parseSuppressionsFromCode(lines);
|
|
186
|
+
// Pre-analyze: detect auth guards and role checks pattern-agnostically
|
|
187
|
+
let codeHasAuthGuard = hasAuthGuardPattern(code);
|
|
188
|
+
const codeHasRoleCheck = hasRoleCheckPattern(code);
|
|
189
|
+
// Config: check custom auth function names from .guardviberc
|
|
190
|
+
if (!codeHasAuthGuard && config.authFunctions && config.authFunctions.length > 0) {
|
|
191
|
+
const customPattern = new RegExp(`(?:${config.authFunctions.join("|")})\\s*\\(`, "i");
|
|
192
|
+
if (customPattern.test(code))
|
|
193
|
+
codeHasAuthGuard = true;
|
|
194
|
+
}
|
|
139
195
|
const effectiveRules = rules ?? owaspRules;
|
|
140
196
|
for (const rule of effectiveRules) {
|
|
141
197
|
if (!rule.languages.includes(language))
|
|
@@ -152,25 +208,30 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
152
208
|
continue;
|
|
153
209
|
if (rule.id.startsWith("VG21") && !filePath && language !== "yaml")
|
|
154
210
|
continue;
|
|
155
|
-
// Context-aware
|
|
211
|
+
// ── Context-aware rule skipping (pattern-agnostic) ──────────────
|
|
212
|
+
const authRuleIds = new Set(["VG420", "VG952", "VG002", "VG402"]);
|
|
213
|
+
const adminRoleRuleIds = new Set(["VG426", "VG957"]);
|
|
214
|
+
const rateLimitRuleIds = new Set(["VG956", "VG030"]);
|
|
156
215
|
const isWebhookRoute = filePath && /webhook/i.test(filePath);
|
|
216
|
+
const isCronRoute = filePath && /(?:cron|scheduled|jobs?)\//i.test(filePath);
|
|
217
|
+
const isAdminRoute = filePath && /\/admin\//i.test(filePath);
|
|
218
|
+
// Skip auth rules when code has any auth guard pattern (naming-agnostic)
|
|
219
|
+
if (codeHasAuthGuard && authRuleIds.has(rule.id))
|
|
220
|
+
continue;
|
|
221
|
+
// Skip admin role rules when code has any role/permission check
|
|
222
|
+
if (codeHasRoleCheck && adminRoleRuleIds.has(rule.id))
|
|
223
|
+
continue;
|
|
224
|
+
// Skip auth rules for webhook routes with signature verification
|
|
157
225
|
const hasSignatureVerification = isWebhookRoute && /(?:verify|signature|hmac|constructEvent|svix|webhookSecret|createHmac|X-Signature|stripe-signature)/i.test(code);
|
|
158
|
-
const authRuleIds = new Set(["VG420", "VG952", "VG002"]);
|
|
159
226
|
if (hasSignatureVerification && authRuleIds.has(rule.id))
|
|
160
227
|
continue;
|
|
161
|
-
//
|
|
162
|
-
const isCronRoute = filePath && /(?:cron|scheduled|jobs?)\//i.test(filePath);
|
|
163
|
-
const rateLimitRuleIds = new Set(["VG956", "VG030"]);
|
|
228
|
+
// Skip rate limiting for cron and webhook routes
|
|
164
229
|
if (isCronRoute && rateLimitRuleIds.has(rule.id))
|
|
165
230
|
continue;
|
|
166
|
-
// Context-aware: skip rate limiting rules for webhook routes
|
|
167
|
-
// Webhooks are called by external services, not users — rate limiting is irrelevant
|
|
168
231
|
if (isWebhookRoute && rateLimitRuleIds.has(rule.id))
|
|
169
232
|
continue;
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
const hasAdminAuth = isAdminRoute && /(?:requireAdmin|adminOnly|orgRole|org:admin|isAdmin|checkRole|requireRole|verifyAuth|checkPermission|assertAuth|ensureAuth|authorize)/i.test(code);
|
|
173
|
-
if (hasAdminAuth && rateLimitRuleIds.has(rule.id))
|
|
233
|
+
// Skip rate limiting for admin routes with auth guard
|
|
234
|
+
if (isAdminRoute && codeHasAuthGuard && rateLimitRuleIds.has(rule.id))
|
|
174
235
|
continue;
|
|
175
236
|
// Skip npm package rules (VG863/VG864/VG865): only apply to package.json files
|
|
176
237
|
if ((rule.id === "VG863" || rule.id === "VG864" || rule.id === "VG865") && filePath && !filePath.endsWith("package.json"))
|
|
@@ -199,7 +260,7 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
199
260
|
: rule;
|
|
200
261
|
// Context-aware severity: downgrade rate limiting/pagination issues in admin routes
|
|
201
262
|
// Admin routes behind requireAdmin have lower brute-force risk
|
|
202
|
-
if (isAdminRoute &&
|
|
263
|
+
if (isAdminRoute && codeHasAuthGuard) {
|
|
203
264
|
const downgradeInAdmin = new Set(["VG955"]); // pagination in admin is less critical
|
|
204
265
|
if (downgradeInAdmin.has(rule.id) && effectiveRule.severity === "medium") {
|
|
205
266
|
effectiveRule = { ...effectiveRule, severity: "low" };
|
package/build/utils/config.d.ts
CHANGED
|
@@ -22,6 +22,10 @@ export interface GuardVibeConfig {
|
|
|
22
22
|
};
|
|
23
23
|
plugins: string[];
|
|
24
24
|
compliance?: CompliancePolicy;
|
|
25
|
+
/** Custom auth function names that GuardVibe should recognize as auth guards.
|
|
26
|
+
* e.g. ["requireAdmin", "verifyUser", "ensureLoggedIn"]
|
|
27
|
+
* These are added ON TOP of the built-in pattern-agnostic detection. */
|
|
28
|
+
authFunctions?: string[];
|
|
25
29
|
}
|
|
26
30
|
export declare function loadConfig(dir?: string): GuardVibeConfig;
|
|
27
31
|
export declare function resetConfigCache(): void;
|
package/build/utils/config.js
CHANGED
|
@@ -67,6 +67,7 @@ export function loadConfig(dir) {
|
|
|
67
67
|
exceptions: Array.isArray(parsed.compliance.exceptions) ? parsed.compliance.exceptions : [],
|
|
68
68
|
requiredControls: Array.isArray(parsed.compliance.requiredControls) ? parsed.compliance.requiredControls : undefined,
|
|
69
69
|
} : undefined,
|
|
70
|
+
authFunctions: Array.isArray(parsed.authFunctions) ? parsed.authFunctions : undefined,
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
73
|
catch { }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
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": {
|