guardvibe 3.0.25 → 3.0.27
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 +10 -0
- package/README.md +4 -4
- package/build/cli/audit.js +3 -0
- package/build/cli/init.js +3 -3
- package/build/data/rules/advanced-security.js +2 -2
- package/build/data/rules/core.js +2 -2
- package/build/data/rules/modern-stack.js +1 -1
- package/build/index.js +2 -2
- package/build/tools/check-code.js +13 -1
- package/build/tools/full-audit.js +17 -6
- package/build/tools/scan-secrets.js +3 -0
- package/build/utils/constants.d.ts +3 -0
- package/build/utils/constants.js +12 -0
- package/build/utils/walk-directory.js +3 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ 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.0.26] - 2026-04-25
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- `init claude` now writes `.mcp.json` (Claude Code v2.x project-scope filename) instead of `.claude.json` — fixes silent install failure where MCP server was never loaded
|
|
12
|
+
- VG964 (server-only missing) now requires Next.js context (`from 'next/...'` or `require('next/...')`) — no longer fires on plain CommonJS Node files
|
|
13
|
+
- VG138 (plaintext password compare) no longer matches `typeof password !== 'string'` type guards
|
|
14
|
+
- VG148 (login brute-force) now detects rate-limit middleware passed as positional argument before bcrypt.compare
|
|
15
|
+
- VG061 (JWT no expiry) now correctly recognizes `expiresIn` anywhere in the `jwt.sign` argument list
|
|
16
|
+
- VG030 (missing rate limit) no longer fires when route declaration includes a `*Limiter`, `*Throttle`, `*RateLimit`, `*Brute`, or `*SlowDown` middleware
|
|
17
|
+
|
|
8
18
|
## [2.7.1] - 2026-04-04
|
|
9
19
|
|
|
10
20
|
### Fixed
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/guardvibe)
|
|
7
7
|
[](https://codecov.io/gh/goklab/guardvibe)
|
|
8
8
|
|
|
9
|
-
**The security MCP built for vibe coding.**
|
|
9
|
+
**The security MCP built for vibe coding.** 365 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
|
-
- **
|
|
17
|
+
- **365 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
|
|
@@ -50,7 +50,7 @@ GuardVibe is purpose-built for the AI coding workflow. Traditional tools are exc
|
|
|
50
50
|
| CVE version detection | 23 packages | Extensive | Extensive |
|
|
51
51
|
| Compliance mapping (SOC2, PCI-DSS, HIPAA) | Built-in | Paid tier | None |
|
|
52
52
|
| SARIF CI/CD export | Yes | Yes | Limited |
|
|
53
|
-
| Rule count |
|
|
53
|
+
| Rule count | 365 (focused) | 5000+ (broad) | N/A |
|
|
54
54
|
|
|
55
55
|
**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.
|
|
56
56
|
|
|
@@ -235,7 +235,7 @@ Malicious postinstall scripts, unpinned GitHub Actions, typosquat detection
|
|
|
235
235
|
|
|
236
236
|
All scanning tools support `format: "json"` for machine-readable output.
|
|
237
237
|
|
|
238
|
-
## Security Rules (
|
|
238
|
+
## Security Rules (365 rules across 25 modules)
|
|
239
239
|
|
|
240
240
|
| Category | Rules | Coverage |
|
|
241
241
|
|----------|-------|----------|
|
package/build/cli/audit.js
CHANGED
|
@@ -6,6 +6,8 @@ import { writeFileSync, existsSync, mkdirSync } from "fs";
|
|
|
6
6
|
import { resolve, dirname } from "path";
|
|
7
7
|
import { parseArgs, getStringFlag, getOutputPath, shouldFail, validateFormat } from "./args.js";
|
|
8
8
|
import { runFullAudit, formatAuditResult } from "../tools/full-audit.js";
|
|
9
|
+
import { setRules } from "../utils/rule-registry.js";
|
|
10
|
+
import { builtinRules } from "../data/rules/index.js";
|
|
9
11
|
export async function runAudit(args) {
|
|
10
12
|
const { flags, positional } = parseArgs(args);
|
|
11
13
|
const targetPath = resolve(positional[0] ?? ".");
|
|
@@ -14,6 +16,7 @@ export async function runAudit(args) {
|
|
|
14
16
|
const failOn = getStringFlag(flags, "fail-on") ?? "critical";
|
|
15
17
|
const skipDeps = flags["skip-deps"] === true;
|
|
16
18
|
const skipSecrets = flags["skip-secrets"] === true;
|
|
19
|
+
setRules(builtinRules);
|
|
17
20
|
// Terminal format by default when outputting to TTY, unless --format is specified
|
|
18
21
|
const isTerminal = !outputFile && process.stdout.isTTY && !flags["format"];
|
|
19
22
|
const format = isTerminal ? "terminal" : rawFormat;
|
package/build/cli/init.js
CHANGED
|
@@ -11,8 +11,8 @@ const GUARDVIBE_MCP_CONFIG = {
|
|
|
11
11
|
};
|
|
12
12
|
const platforms = {
|
|
13
13
|
claude: {
|
|
14
|
-
path: join(process.cwd(), ".
|
|
15
|
-
description: "Claude Code (.
|
|
14
|
+
path: join(process.cwd(), ".mcp.json"),
|
|
15
|
+
description: "Claude Code (.mcp.json)",
|
|
16
16
|
},
|
|
17
17
|
gemini: {
|
|
18
18
|
path: join(homedir(), ".gemini", "settings.json"),
|
|
@@ -154,7 +154,7 @@ function setupSecurityGuide(platformName) {
|
|
|
154
154
|
else if (platformName === "gemini")
|
|
155
155
|
setupGeminiGuide();
|
|
156
156
|
const gitignoreEntries = {
|
|
157
|
-
claude: [".
|
|
157
|
+
claude: [".mcp.json", ".claude/", "CLAUDE.md"],
|
|
158
158
|
cursor: [".cursor/", ".cursorrules"],
|
|
159
159
|
gemini: ["GEMINI.md"],
|
|
160
160
|
};
|
|
@@ -110,7 +110,7 @@ export const advancedSecurityRules = [
|
|
|
110
110
|
severity: "critical",
|
|
111
111
|
owasp: "A02:2025 Cryptographic Failures",
|
|
112
112
|
description: "Password is compared using direct string equality (=== or ==) instead of a hashing function. This means passwords are stored or transmitted in plaintext.",
|
|
113
|
-
pattern: /(?:password|passwd|pwd)\s*(?:===|!==|==|!=)\s*(?:(?:req|request|body|input|data|form|user)[\.\[]|["'])/gi,
|
|
113
|
+
pattern: /(?<!typeof\s)(?:password|passwd|pwd)\s*(?:===|!==|==|!=)\s*(?:(?:req|request|body|input|data|form|user)[\.\[]|["'](?!(?:string|number|boolean|undefined|object|function|symbol|bigint)["']))/gi,
|
|
114
114
|
languages: ["javascript", "typescript", "python"],
|
|
115
115
|
fix: "Never compare passwords directly. Use bcrypt.compare() or argon2.verify() to compare against hashed passwords.",
|
|
116
116
|
fixCode: '// BAD: plaintext comparison\nif (user.password === inputPassword) { ... }\n\n// GOOD: hash comparison\nimport bcrypt from "bcrypt";\nconst valid = await bcrypt.compare(inputPassword, user.passwordHash);\nif (!valid) return new Response("Invalid", { status: 401 });',
|
|
@@ -240,7 +240,7 @@ export const advancedSecurityRules = [
|
|
|
240
240
|
severity: "high",
|
|
241
241
|
owasp: "A07:2025 Identification and Authentication Failures",
|
|
242
242
|
description: "Login/authentication endpoint compares passwords without rate limiting or account lockout. Attackers can try unlimited password combinations.",
|
|
243
|
-
pattern: /(?:signIn|login|authenticate|logIn)\b[\s\S]{0,500}?(?:bcrypt\.compare|argon2\.verify|compare|verify)
|
|
243
|
+
pattern: /(?:signIn|login|authenticate|logIn)\b(?:(?!rateLimit|limiter|throttle|lockout|maxAttempts|failedAttempts|loginAttempts|Ratelimit|brute)[\s\S]){0,500}?(?:bcrypt\.compare|argon2\.verify|compare|verify)(?:(?!rateLimit|limiter|throttle|lockout|maxAttempts|failedAttempts|loginAttempts|Ratelimit|brute)[\s\S]){5,300}?(?:return|Response|res\.)/gi,
|
|
244
244
|
languages: ["javascript", "typescript"],
|
|
245
245
|
fix: "Add rate limiting and account lockout to login endpoints.",
|
|
246
246
|
fixCode: '// Add rate limiting to login\nimport { Ratelimit } from "@upstash/ratelimit";\nconst loginLimiter = new Ratelimit({\n redis: Redis.fromEnv(),\n limiter: Ratelimit.slidingWindow(5, "15 m"), // 5 attempts per 15 min\n});\n\nconst { success } = await loginLimiter.limit(email);\nif (!success) return new Response("Too many attempts", { status: 429 });',
|
package/build/data/rules/core.js
CHANGED
|
@@ -140,7 +140,7 @@ export const coreRules = [
|
|
|
140
140
|
severity: "medium",
|
|
141
141
|
owasp: "A04:2025 Insecure Design",
|
|
142
142
|
description: "Authentication or API endpoints without rate limiting are vulnerable to brute force attacks.",
|
|
143
|
-
pattern: /(?:app|router)\.\s*(?:get|post|put|delete|patch|use)\s*\(\s*['"](?:\/login|\/auth|\/signin|\/register|\/signup|\/forgot-password)/gi,
|
|
143
|
+
pattern: /(?:app|router)\.\s*(?:get|post|put|delete|patch|use)\s*\(\s*['"](?:\/login|\/auth|\/signin|\/register|\/signup|\/forgot-password)['"]\s*,(?!\s*(?:\w*[Ll]imiter|\w*[Tt]hrottle|\w*[Rr]ate[Ll]imit|\w*[Bb]rute|\w*[Ss]low[Dd]own))/gi,
|
|
144
144
|
languages: ["javascript", "typescript", "python", "go"],
|
|
145
145
|
fix: "Add rate limiting middleware. Express: npm install express-rate-limit. FastAPI: use slowapi. Apply stricter limits on auth endpoints (e.g. 5 requests/minute).",
|
|
146
146
|
fixCode: "// Express rate limiting\nimport rateLimit from 'express-rate-limit';\napp.use('/api/', rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));",
|
|
@@ -200,7 +200,7 @@ export const coreRules = [
|
|
|
200
200
|
severity: "high",
|
|
201
201
|
owasp: "A07:2025 Auth Failures",
|
|
202
202
|
description: "JWT token created without expiration time.",
|
|
203
|
-
pattern: /jwt\.sign\s*\(
|
|
203
|
+
pattern: /jwt\.sign\s*\((?:(?!\bexpiresIn\b)[^)])*\)/gi,
|
|
204
204
|
languages: ["javascript", "typescript"],
|
|
205
205
|
fix: "Always set token expiration: jwt.sign(payload, secret, { expiresIn: '15m' }).",
|
|
206
206
|
fixCode: "// Always set expiration\nconst token = jwt.sign(payload, secret, { expiresIn: '15m' });",
|
|
@@ -65,7 +65,7 @@ export const modernStackRules = [
|
|
|
65
65
|
severity: "high",
|
|
66
66
|
owasp: "A01:2025 Broken Access Control",
|
|
67
67
|
description: 'File contains sensitive server-side logic (database queries, secret access) but does not import "server-only". Without this guard, the module can be accidentally imported by a Client Component, leaking server code to the browser bundle.',
|
|
68
|
-
pattern: /^(?![\s\S]*?(?:['"]server-only['"]|['"]use server['"]|['"]use client['"])[\s\S]*?)[\s\S]*?(?:process\.env\.(?!NEXT_PUBLIC_)\w+(?:_KEY|_SECRET|_TOKEN)|(?:prisma|db|supabase)\.(?:query|from|\$queryRaw))/g,
|
|
68
|
+
pattern: /^(?=[\s\S]*?(?:from\s+['"]next(?:\/[^'"]*)?['"]|require\s*\(\s*['"]next(?:\/[^'"]*)?['"]))(?![\s\S]*?(?:['"]server-only['"]|['"]use server['"]|['"]use client['"])[\s\S]*?)[\s\S]*?(?:process\.env\.(?!NEXT_PUBLIC_)\w+(?:_KEY|_SECRET|_TOKEN)|(?:prisma|db|supabase)\.(?:query|from|\$queryRaw))/g,
|
|
69
69
|
languages: ["javascript", "typescript"],
|
|
70
70
|
fix: 'Add import "server-only" at the top of files that contain server-side logic.',
|
|
71
71
|
fixCode: '// Add at the very top of server-only modules\nimport "server-only";\n\n// Now this file cannot be imported by Client Components\nexport async function getSecretData() {\n const key = process.env.SECRET_KEY;\n return prisma.user.findMany();\n}',
|
package/build/index.js
CHANGED
|
@@ -60,7 +60,7 @@ function mergeStatsIntoOutput(results, summary, format) {
|
|
|
60
60
|
const server = new McpServer({
|
|
61
61
|
name: "guardvibe",
|
|
62
62
|
version: pkg.version,
|
|
63
|
-
description: "Security MCP for vibe coding — single source of truth for AI assistants.
|
|
63
|
+
description: "Security MCP for vibe coding — single source of truth for AI assistants. 365 security rules and 36 tools. Use full_audit for a comprehensive PASS/FAIL/WARN verdict with deterministic result hash, coverage %, and unified report across code, secrets, dependencies, config, taint analysis, and auth coverage. IMPORTANT: When full_audit returns FAIL/WARN, call remediation_plan to get a mandatory section-by-section fix checklist covering ALL 6 sections (not just code). After fixing, call verify_remediation to confirm all sections were addressed. Same code = same hash = same results regardless of which AI assistant runs it. Covers OWASP, Next.js, Supabase, Stripe, Clerk, Prisma, Hono, AI SDK, MCP server security, host hardening. Maps to SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, EU AI Act. Runs 100% locally with zero configuration.",
|
|
64
64
|
});
|
|
65
65
|
// Tool 1: Analyze code for security vulnerabilities
|
|
66
66
|
server.tool("check_code", "Analyze inline code for security vulnerabilities (OWASP Top 10, XSS, SQL injection, insecure patterns). Pass code as a string parameter. For scanning files on disk, use scan_file instead. Example: check_code({code: 'app.get(...)', language: 'javascript'})", {
|
|
@@ -884,7 +884,7 @@ server.tool("deep_scan", "LLM-powered deep security analysis for vulnerabilities
|
|
|
884
884
|
return { content: [{ type: "text", text: output }] };
|
|
885
885
|
});
|
|
886
886
|
// Tool 33: Full audit — single source of truth
|
|
887
|
-
server.tool("full_audit", "Single command that runs ALL checks: code scan (
|
|
887
|
+
server.tool("full_audit", "Single command that runs ALL checks: code scan (365 rules), secret detection, dependency CVEs, config audit, taint analysis, and auth coverage. Returns PASS/FAIL/WARN verdict with deterministic hash. IMPORTANT: If verdict is FAIL or WARN, you MUST call remediation_plan next to get a section-by-section fix checklist — do NOT skip any section. After fixing, call verify_remediation to confirm ALL sections are addressed. Example: full_audit({path: '.'})", {
|
|
888
888
|
path: z.string().default(".").describe("Project root directory"),
|
|
889
889
|
format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
|
|
890
890
|
skipDeps: z.boolean().default(false).describe("Skip dependency vulnerability check"),
|
|
@@ -594,9 +594,21 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
594
594
|
* Prefers framework-specific rules (VG4xx, VG9xx) over generic core rules (VG0xx).
|
|
595
595
|
*/
|
|
596
596
|
function deduplicateFindings(findings) {
|
|
597
|
+
// First pass: drop exact duplicates — same rule firing on the same line.
|
|
598
|
+
// Happens when a rule has multiple regex variants that all match the same position,
|
|
599
|
+
// typical on minified single-line files where many patterns hit line 2 or 3.
|
|
600
|
+
const seenExact = new Set();
|
|
601
|
+
const exactDeduped = [];
|
|
602
|
+
for (const f of findings) {
|
|
603
|
+
const key = `${f.rule.id}:${f.line}`;
|
|
604
|
+
if (seenExact.has(key))
|
|
605
|
+
continue;
|
|
606
|
+
seenExact.add(key);
|
|
607
|
+
exactDeduped.push(f);
|
|
608
|
+
}
|
|
597
609
|
// Group findings by line number
|
|
598
610
|
const byLine = new Map();
|
|
599
|
-
for (const f of
|
|
611
|
+
for (const f of exactDeduped) {
|
|
600
612
|
const group = byLine.get(f.line);
|
|
601
613
|
if (group)
|
|
602
614
|
group.push(f);
|
|
@@ -210,25 +210,36 @@ export async function runFullAudit(path, options) {
|
|
|
210
210
|
const depsJson = await scanDependencies(manifestPath, "json");
|
|
211
211
|
const parsed = safeJsonParse(depsJson);
|
|
212
212
|
if (parsed) {
|
|
213
|
-
const
|
|
214
|
-
const counts = { findings: vuln, critical: parsed.summary?.critical ?? 0, high: parsed.summary?.high ?? 0, medium: parsed.summary?.medium ?? 0 };
|
|
213
|
+
const vulnPackages = parsed.summary?.vulnerable ?? 0;
|
|
215
214
|
const depFindings = [];
|
|
215
|
+
let depCritical = 0, depHigh = 0, depMedium = 0;
|
|
216
216
|
for (const pkg of parsed.packages ?? []) {
|
|
217
217
|
for (const v of pkg.vulnerabilities ?? []) {
|
|
218
218
|
const vuln2 = v;
|
|
219
|
+
const sev = (vuln2.severity ?? "high");
|
|
220
|
+
if (sev === "critical")
|
|
221
|
+
depCritical++;
|
|
222
|
+
else if (sev === "high")
|
|
223
|
+
depHigh++;
|
|
224
|
+
else
|
|
225
|
+
depMedium++;
|
|
219
226
|
depFindings.push({
|
|
220
227
|
ruleId: `DEP:${(vuln2.id ?? "CVE")}`,
|
|
221
|
-
severity:
|
|
228
|
+
severity: sev,
|
|
222
229
|
file: "package.json",
|
|
223
230
|
line: 0,
|
|
224
231
|
name: `${pkg.name ?? "unknown"}: ${(vuln2.id ?? "CVE")}`,
|
|
225
232
|
description: (vuln2.summary ?? vuln2.details ?? ""),
|
|
226
233
|
fix: `Run: npm update ${pkg.name ?? ""}`,
|
|
227
234
|
});
|
|
228
|
-
allFindings.push({ ruleId: `DEP:${vuln2.id ?? "CVE"}`, severity:
|
|
235
|
+
allFindings.push({ ruleId: `DEP:${vuln2.id ?? "CVE"}`, severity: sev, file: "package.json", line: 0 });
|
|
229
236
|
}
|
|
230
237
|
}
|
|
231
|
-
|
|
238
|
+
const counts = { findings: depFindings.length, critical: depCritical, high: depHigh, medium: depMedium };
|
|
239
|
+
const detailText = depFindings.length === 0
|
|
240
|
+
? "No known CVEs"
|
|
241
|
+
: `${depFindings.length} CVE(s) across ${vulnPackages} vulnerable package(s)`;
|
|
242
|
+
sections.push({ name: "dependencies", status: "ok", ...counts, details: detailText, sectionFindings: depFindings });
|
|
232
243
|
}
|
|
233
244
|
}
|
|
234
245
|
catch {
|
|
@@ -339,7 +350,7 @@ export async function runFullAudit(path, options) {
|
|
|
339
350
|
const totalHigh = sections.reduce((s, sec) => s + sec.high, 0);
|
|
340
351
|
const totalMedium = sections.reduce((s, sec) => s + sec.medium, 0);
|
|
341
352
|
const totalFindings = sections.reduce((s, sec) => s + sec.findings, 0);
|
|
342
|
-
const rulesApplied = rules.length > 0 ? rules.length :
|
|
353
|
+
const rulesApplied = rules.length > 0 ? rules.length : 365;
|
|
343
354
|
// Adjust score to reflect ALL sections, not just code
|
|
344
355
|
// Each critical finding deducts 5 points, high deducts 3, medium deducts 1
|
|
345
356
|
// Score from code scan is the baseline, other sections reduce it further
|
|
@@ -3,6 +3,7 @@ import { basename, dirname, extname, join, relative, resolve } from "path";
|
|
|
3
3
|
import { execFileSync } from "child_process";
|
|
4
4
|
import { secretPatterns, calculateEntropy } from "../data/secret-patterns.js";
|
|
5
5
|
import { loadConfig } from "../utils/config.js";
|
|
6
|
+
import { isExcludedFilename } from "../utils/constants.js";
|
|
6
7
|
const DEFAULT_SECRET_EXCLUDES = new Set(["node_modules", ".git", "build", "dist"]);
|
|
7
8
|
const SOURCE_FILE_EXTENSIONS = new Set([
|
|
8
9
|
".js", ".jsx", ".mjs", ".cjs",
|
|
@@ -75,6 +76,8 @@ function walkForSecrets(dir, recursive, results, excludes) {
|
|
|
75
76
|
if (!entry.isFile())
|
|
76
77
|
continue;
|
|
77
78
|
const name = entry.name;
|
|
79
|
+
if (isExcludedFilename(name))
|
|
80
|
+
continue;
|
|
78
81
|
const ext = extname(name).toLowerCase();
|
|
79
82
|
if (name.startsWith(".env") || CONFIG_FILE_EXTENSIONS.has(ext) || SOURCE_FILE_EXTENSIONS.has(ext)) {
|
|
80
83
|
results.push(fullPath);
|
|
@@ -8,3 +8,6 @@ export declare const EXTENSION_MAP: Record<string, string>;
|
|
|
8
8
|
export declare const CONFIG_FILE_MAP: Record<string, string>;
|
|
9
9
|
/** Directory names excluded from filesystem scans by default. */
|
|
10
10
|
export declare const DEFAULT_EXCLUDES: Set<string>;
|
|
11
|
+
/** File-name patterns excluded from scans — minified bundles, vendor libs, generated artifacts. */
|
|
12
|
+
export declare const DEFAULT_FILE_PATTERN_EXCLUDES: RegExp[];
|
|
13
|
+
export declare function isExcludedFilename(name: string): boolean;
|
package/build/utils/constants.js
CHANGED
|
@@ -32,3 +32,15 @@ export const DEFAULT_EXCLUDES = new Set([
|
|
|
32
32
|
".vercel", ".clerk", ".wrangler", ".netlify", ".amplify",
|
|
33
33
|
".serverless", ".firebase", ".expo", ".output",
|
|
34
34
|
]);
|
|
35
|
+
/** File-name patterns excluded from scans — minified bundles, vendor libs, generated artifacts. */
|
|
36
|
+
export const DEFAULT_FILE_PATTERN_EXCLUDES = [
|
|
37
|
+
/\.min\.(js|mjs|cjs|css)$/i,
|
|
38
|
+
/\.bundle\.(js|mjs|cjs)$/i,
|
|
39
|
+
/-bundle\.(js|mjs|cjs)$/i,
|
|
40
|
+
/\.production(\.min)?\.(js|mjs|cjs)$/i,
|
|
41
|
+
/\.umd(\.min)?\.(js|mjs|cjs)$/i,
|
|
42
|
+
/\.esm(\.min)?\.(js|mjs|cjs)$/i,
|
|
43
|
+
];
|
|
44
|
+
export function isExcludedFilename(name) {
|
|
45
|
+
return DEFAULT_FILE_PATTERN_EXCLUDES.some((pattern) => pattern.test(name));
|
|
46
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { readdirSync } from "fs";
|
|
6
6
|
import { join, extname } from "path";
|
|
7
|
-
import { EXTENSION_MAP, CONFIG_FILE_MAP } from "./constants.js";
|
|
7
|
+
import { EXTENSION_MAP, CONFIG_FILE_MAP, isExcludedFilename } from "./constants.js";
|
|
8
8
|
/**
|
|
9
9
|
* Recursively walk a directory, collecting file paths that match
|
|
10
10
|
* known source extensions, Dockerfiles, or config file names.
|
|
@@ -31,6 +31,8 @@ export function walkDirectory(dir, recursive, excludes, results, unsupportedResu
|
|
|
31
31
|
walkDirectory(fullPath, recursive, excludes, results, unsupportedResults);
|
|
32
32
|
}
|
|
33
33
|
else if (entry.isFile()) {
|
|
34
|
+
if (isExcludedFilename(entry.name))
|
|
35
|
+
continue;
|
|
34
36
|
const ext = extname(entry.name).toLowerCase();
|
|
35
37
|
let matched = false;
|
|
36
38
|
if (EXTENSION_MAP[ext]) {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.27",
|
|
4
4
|
"mcpName": "io.github.goklab/guardvibe",
|
|
5
|
-
"description": "Security MCP for vibe coding.
|
|
5
|
+
"description": "Security MCP for vibe coding. 365 rules, 36 tools, CLI + doctor. Host security, auth coverage mapping, LLM-powered deep scan (IDOR/business logic), taint analysis. Plus Next.js, Supabase, Clerk, Stripe, Prisma, tRPC, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK, and the full AI-generated stack.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"guardvibe": "build/cli.js",
|