guardvibe 3.0.10 → 3.0.12
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/auth-coverage.js +3 -1
- package/build/data/rules/advanced-security.js +4 -4
- package/build/data/rules/modern-stack.js +2 -2
- package/build/data/rules/nextjs.js +1 -1
- package/build/index.js +2 -1
- package/build/tools/auth-coverage.d.ts +4 -1
- package/build/tools/auth-coverage.js +17 -1
- package/build/tools/full-audit.js +42 -25
- package/build/utils/config.d.ts +7 -0
- package/build/utils/config.js +1 -0
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import { readdirSync, readFileSync, statSync, writeFileSync, existsSync, mkdirSy
|
|
|
6
6
|
import { resolve, dirname } from "path";
|
|
7
7
|
import { parseArgs, validateFormat, getOutputPath } from "./args.js";
|
|
8
8
|
import { analyzeAuthCoverage, formatAuthCoverage } from "../tools/auth-coverage.js";
|
|
9
|
+
import { loadConfig } from "../utils/config.js";
|
|
9
10
|
export async function runAuthCoverage(args) {
|
|
10
11
|
const { flags, positional } = parseArgs(args);
|
|
11
12
|
const targetPath = resolve(positional[0] ?? ".");
|
|
@@ -57,7 +58,8 @@ export async function runAuthCoverage(args) {
|
|
|
57
58
|
const routeFiles = jsFiles.filter(f => /\/(route|page)\.(ts|tsx|js|jsx)$/.test(f.path));
|
|
58
59
|
const layoutFiles = jsFiles.filter(f => /\/layout\.(ts|tsx|js|jsx)$/.test(f.path));
|
|
59
60
|
const middlewareFile = jsFiles.find(f => /middleware\.(ts|js)$/.test(f.path));
|
|
60
|
-
const
|
|
61
|
+
const config = loadConfig(targetPath);
|
|
62
|
+
const report = analyzeAuthCoverage(routeFiles, middlewareFile?.content ?? "", layoutFiles, config.authExceptions);
|
|
61
63
|
const formatArg = format === "json" ? "json" : "markdown";
|
|
62
64
|
const result = formatAuthCoverage(report, formatArg);
|
|
63
65
|
if (outputFile) {
|
|
@@ -34,7 +34,7 @@ export const advancedSecurityRules = [
|
|
|
34
34
|
severity: "low",
|
|
35
35
|
owasp: "API4:2023 Unrestricted Resource Consumption",
|
|
36
36
|
description: "API endpoint reads request body without explicit size limit. Note: Next.js/Vercel applies a default 4.5MB limit, so this is informational for those platforms. For custom servers, attackers can send large payloads to exhaust memory.",
|
|
37
|
-
pattern: /export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH)\s*\([^)]*\)\s*\{(?:(?!content-length|maxBodySize|limit|MAX_)[\s\S]){5,}?(?:req\.json|req\.text|req\.body|req\.formData|request\.json|request\.text)\s*\(\s*\)/g,
|
|
37
|
+
pattern: /export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH)\s*\([^)]*\)\s*\{(?:(?!content-length|maxBodySize|limit|MAX_|parseBody|checkBodySize|bodyParser|bodyLimit|sizeLimit)[\s\S]){5,}?(?:req\.json|req\.text|req\.body|req\.formData|request\.json|request\.text)\s*\(\s*\)/g,
|
|
38
38
|
languages: ["javascript", "typescript"],
|
|
39
39
|
fix: "Check Content-Length header before parsing body, or use a body parser with size limit.",
|
|
40
40
|
fixCode: '// Check body size before parsing\nexport async function POST(req: Request) {\n const contentLength = parseInt(req.headers.get("content-length") || "0");\n if (contentLength > 1024 * 1024) { // 1MB limit\n return new Response("Payload too large", { status: 413 });\n }\n const body = await req.json();\n}',
|
|
@@ -188,7 +188,7 @@ export const advancedSecurityRules = [
|
|
|
188
188
|
severity: "medium",
|
|
189
189
|
owasp: "A05:2025 Security Misconfiguration",
|
|
190
190
|
description: "Security headers are configured but Referrer-Policy is missing. Without it, the full URL (including query parameters with tokens/IDs) is sent to external sites in the Referer header.",
|
|
191
|
-
pattern: /(?:async\s+)?headers\s*\(\s*\)
|
|
191
|
+
pattern: /(?:async\s+)?headers\s*\(\s*\)(?=[\s\S]*(?:X-Frame-Options|Strict-Transport-Security|Content-Security-Policy))(?![\s\S]*Referrer-Policy)/g,
|
|
192
192
|
languages: ["javascript", "typescript"],
|
|
193
193
|
fix: "Add Referrer-Policy: strict-origin-when-cross-origin to your security headers.",
|
|
194
194
|
fixCode: '{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }',
|
|
@@ -201,7 +201,7 @@ export const advancedSecurityRules = [
|
|
|
201
201
|
severity: "medium",
|
|
202
202
|
owasp: "A05:2025 Security Misconfiguration",
|
|
203
203
|
description: "Security headers are configured but Permissions-Policy is missing. Without it, embedded iframes and scripts can access camera, microphone, geolocation, and other sensitive browser APIs.",
|
|
204
|
-
pattern: /(?:async\s+)?headers\s*\(\s*\)
|
|
204
|
+
pattern: /(?:async\s+)?headers\s*\(\s*\)(?=[\s\S]*(?:X-Frame-Options|Strict-Transport-Security|Content-Security-Policy))(?![\s\S]*Permissions-Policy)/g,
|
|
205
205
|
languages: ["javascript", "typescript"],
|
|
206
206
|
fix: "Add Permissions-Policy header to restrict browser API access.",
|
|
207
207
|
fixCode: '{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" }',
|
|
@@ -330,7 +330,7 @@ export const advancedSecurityRules = [
|
|
|
330
330
|
severity: "medium",
|
|
331
331
|
owasp: "A01:2025 Broken Access Control",
|
|
332
332
|
description: "POST/PUT/PATCH/DELETE route handler performs database mutations without CSRF token verification. Cross-site requests from malicious pages can trick authenticated users into performing unwanted actions.",
|
|
333
|
-
pattern: /export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH|DELETE)\s*\([^)]*\)\s*\{(?:(?!csrf|csrfToken|CSRF|x-csrf|verifyCsrf|validateCsrf|anti.?forgery)[\s\S]){10,}?(?:\.create\s*\(|\.update\s*\(|\.delete\s*\(|\.insert\s*\(|\.upsert\s*\()/g,
|
|
333
|
+
pattern: /export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH|DELETE)\s*\([^)]*\)\s*\{(?:(?!csrf|csrfToken|CSRF|x-csrf|verifyCsrf|validateCsrf|anti.?forgery|requireAdmin|requireAuth|checkAuth|withAuth|protectRoute|authenticate|x-csrf-protection)[\s\S]){10,}?(?:\.create\s*\(|\.update\s*\(|\.delete\s*\(|\.insert\s*\(|\.upsert\s*\()/g,
|
|
334
334
|
languages: ["javascript", "typescript"],
|
|
335
335
|
fix: "Add CSRF token verification to state-changing endpoints.",
|
|
336
336
|
fixCode: '// Verify CSRF token from header\nexport async function POST(req: Request) {\n const csrfToken = req.headers.get("x-csrf-token");\n if (!verifyCsrfToken(csrfToken)) {\n return new Response("CSRF validation failed", { status: 403 });\n }\n}',
|
|
@@ -269,7 +269,7 @@ export const modernStackRules = [
|
|
|
269
269
|
severity: "high",
|
|
270
270
|
owasp: "A05:2025 Security Misconfiguration",
|
|
271
271
|
description: "Next.js app does not set a Content-Security-Policy header. CSP is the strongest defense against XSS — without it, injected scripts run freely in your users' browsers.",
|
|
272
|
-
pattern: /(?:async\s+)?headers\s*\(\s*\)\s
|
|
272
|
+
pattern: /(?:async\s+)?headers\s*\(\s*\)(?=[\s\S]*(?:X-Frame-Options|Strict-Transport-Security|X-Content-Type-Options))(?![\s\S]*Content-Security-Policy)/g,
|
|
273
273
|
languages: ["javascript", "typescript"],
|
|
274
274
|
fix: "Add a Content-Security-Policy header in next.config.ts headers().",
|
|
275
275
|
fixCode: "// next.config.ts\nasync headers() {\n return [{\n source: '/(.*)',\n headers: [{\n key: 'Content-Security-Policy',\n value: \"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;\"\n }]\n }];\n}",
|
|
@@ -476,7 +476,7 @@ export const modernStackRules = [
|
|
|
476
476
|
severity: "high",
|
|
477
477
|
owasp: "A01:2025 Broken Access Control",
|
|
478
478
|
description: "User-uploaded file's original filename is used directly for storage without sanitization. Attackers can use directory traversal (../../etc/passwd), null bytes (file.php%00.jpg), double extensions (file.jpg.exe), or Unicode tricks to overwrite files, bypass type checks, or achieve remote code execution.",
|
|
479
|
-
pattern: /(?:file\.name|originalname|
|
|
479
|
+
pattern: /(?:file\.name|originalname|req\.file\.originalname|formData\.get\s*\(\s*['"]file['"])(?:(?!randomUUID|uuid|nanoid|sanitize|safeFilename|safeName|allowedExt|allowedExtensions|\.replace\s*\(\s*\/\[)[\s\S]){0,100}?(?:writeFile|createWriteStream|save|upload|putObject|mv\s*\(|rename|storage)/gi,
|
|
480
480
|
languages: ["javascript", "typescript"],
|
|
481
481
|
fix: "Generate a random filename (UUID/nanoid) and validate the extension against an allowlist. Never use the original filename for storage.",
|
|
482
482
|
fixCode: 'import { randomUUID } from "crypto";\nimport path from "path";\n\n// Generate safe filename\nconst ext = path.extname(file.name).toLowerCase();\nconst ALLOWED_EXT = [".jpg", ".jpeg", ".png", ".webp", ".pdf"];\nif (!ALLOWED_EXT.includes(ext)) throw new Error("Invalid file type");\nconst safeName = `${randomUUID()}${ext}`;\nawait fs.writeFile(`/uploads/${safeName}`, buffer);',
|
|
@@ -66,7 +66,7 @@ export const nextjsRules = [
|
|
|
66
66
|
severity: "medium",
|
|
67
67
|
owasp: "A05:2025 Security Misconfiguration",
|
|
68
68
|
description: "next.config is missing important security headers (Content-Security-Policy, Strict-Transport-Security, X-Frame-Options).",
|
|
69
|
-
pattern: /(?:async\s+)?headers\s*\(\s*\)\s
|
|
69
|
+
pattern: /(?:async\s+)?headers\s*\(\s*\)(?![\s\S]*(?:X-Frame-Options|Strict-Transport-Security|Content-Security-Policy))/g,
|
|
70
70
|
languages: ["javascript", "typescript"],
|
|
71
71
|
fix: "Add security headers in next.config.ts headers() function.",
|
|
72
72
|
fixCode: '// next.config.ts\nasync headers() {\n return [{\n source: "/(.*)",\n headers: [\n { key: "X-Frame-Options", value: "DENY" },\n { key: "X-Content-Type-Options", value: "nosniff" },\n { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },\n ]\n }];\n}',
|
package/build/index.js
CHANGED
|
@@ -852,7 +852,8 @@ server.tool("auth_coverage", "Analyze authentication coverage across Next.js App
|
|
|
852
852
|
const routeFiles = jsFiles.filter(f => /\/(route|page)\.(ts|tsx|js|jsx)$/.test(f.path));
|
|
853
853
|
const layoutFiles = jsFiles.filter(f => /\/layout\.(ts|tsx|js|jsx)$/.test(f.path));
|
|
854
854
|
const middlewareFile = jsFiles.find(f => /middleware\.(ts|js)$/.test(f.path));
|
|
855
|
-
const
|
|
855
|
+
const cfg = loadConfig(path);
|
|
856
|
+
const report = analyzeAuthCoverage(routeFiles, middlewareFile?.content ?? "", layoutFiles, cfg.authExceptions);
|
|
856
857
|
const output = formatAuthCoverage(report, format);
|
|
857
858
|
return { content: [{ type: "text", text: output }] };
|
|
858
859
|
}
|
|
@@ -39,7 +39,10 @@ export interface AuthCoverageReport {
|
|
|
39
39
|
/**
|
|
40
40
|
* Analyze auth coverage across all route files.
|
|
41
41
|
*/
|
|
42
|
-
export declare function analyzeAuthCoverage(routeFiles: FileEntry[], middlewareContent: string, layoutFiles?: FileEntry[]
|
|
42
|
+
export declare function analyzeAuthCoverage(routeFiles: FileEntry[], middlewareContent: string, layoutFiles?: FileEntry[], authExceptions?: Array<{
|
|
43
|
+
path: string;
|
|
44
|
+
reason: string;
|
|
45
|
+
}>): AuthCoverageReport;
|
|
43
46
|
/**
|
|
44
47
|
* Format auth coverage report as markdown or JSON.
|
|
45
48
|
*/
|
|
@@ -143,7 +143,7 @@ function hasAuthGuard(code) {
|
|
|
143
143
|
/**
|
|
144
144
|
* Analyze auth coverage across all route files.
|
|
145
145
|
*/
|
|
146
|
-
export function analyzeAuthCoverage(routeFiles, middlewareContent, layoutFiles) {
|
|
146
|
+
export function analyzeAuthCoverage(routeFiles, middlewareContent, layoutFiles, authExceptions) {
|
|
147
147
|
const routes = enumerateRoutes(routeFiles);
|
|
148
148
|
const matchers = parseMiddlewareMatchers(middlewareContent);
|
|
149
149
|
const hasMiddleware = middlewareContent.length > 0;
|
|
@@ -204,6 +204,22 @@ export function analyzeAuthCoverage(routeFiles, middlewareContent, layoutFiles)
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
|
+
// Apply authExceptions from .guardviberc — mark excepted routes as protected
|
|
208
|
+
if (authExceptions && authExceptions.length > 0) {
|
|
209
|
+
for (const route of routes) {
|
|
210
|
+
if (route.hasAuthGuard || route.middlewareCovered)
|
|
211
|
+
continue;
|
|
212
|
+
const isExcepted = authExceptions.some(exc => {
|
|
213
|
+
const excPath = exc.path.replace(/\[[\w]+\]/g, "[^/]+");
|
|
214
|
+
const regex = new RegExp("^" + excPath.replace(/\//g, "\\/") + "$");
|
|
215
|
+
return regex.test(route.urlPath) || route.urlPath === exc.path || route.urlPath.startsWith(exc.path + "/");
|
|
216
|
+
});
|
|
217
|
+
if (isExcepted) {
|
|
218
|
+
route.hasAuthGuard = true;
|
|
219
|
+
route.protectionSource = "auth-guard"; // treated as intentionally public
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
207
223
|
const protectedRoutes = routes.filter(r => r.hasAuthGuard || r.middlewareCovered).length;
|
|
208
224
|
const unprotectedList = routes.filter(r => !r.hasAuthGuard && !r.middlewareCovered);
|
|
209
225
|
return {
|
|
@@ -16,6 +16,7 @@ import { auditConfig } from "./audit-config.js";
|
|
|
16
16
|
import { analyzeCrossFileTaint } from "./cross-file-taint.js";
|
|
17
17
|
import { analyzeAuthCoverage } from "./auth-coverage.js";
|
|
18
18
|
import { getRules } from "../utils/rule-registry.js";
|
|
19
|
+
import { loadConfig } from "../utils/config.js";
|
|
19
20
|
// --- Core Logic ---
|
|
20
21
|
/**
|
|
21
22
|
* Compute verdict: PASS (0 critical + 0 high), WARN (high > 0), FAIL (critical > 0)
|
|
@@ -239,7 +240,8 @@ export async function runFullAudit(path, options) {
|
|
|
239
240
|
const layoutFiles = jsFiles.filter(f => /\/layout\.(ts|tsx|js|jsx)$/.test(f.path));
|
|
240
241
|
if (routeFiles.length > 0) {
|
|
241
242
|
const middlewareFile = jsFiles.find(f => /middleware\.(ts|js)$/.test(f.path));
|
|
242
|
-
const
|
|
243
|
+
const config = loadConfig(projectRoot);
|
|
244
|
+
const report = analyzeAuthCoverage(routeFiles, middlewareFile?.content ?? "", layoutFiles, config.authExceptions);
|
|
243
245
|
const unprotected = report.unprotectedRoutes;
|
|
244
246
|
sections.push({ name: "auth-coverage", status: "ok", findings: unprotected, critical: 0, high: unprotected > 0 ? unprotected : 0, medium: 0,
|
|
245
247
|
details: `${report.protectedRoutes}/${report.totalRoutes} routes protected (${report.middlewareCoveragePercent}% middleware)` });
|
|
@@ -297,60 +299,60 @@ function buildInlineRemediationPlan(result) {
|
|
|
297
299
|
priority: 1,
|
|
298
300
|
tool: "scan_secrets",
|
|
299
301
|
actions: [
|
|
300
|
-
"Call scan_secrets with format: json
|
|
301
|
-
"For EACH secret: move to environment variable,
|
|
302
|
-
"Rotate any API keys/tokens that were committed —
|
|
303
|
-
"Call scan_secrets_history
|
|
304
|
-
"Re-run
|
|
302
|
+
"MCP: Call scan_secrets with format: json — OR CLI: run `npx guardvibe scan --format json` and look for SECRET: findings in the output",
|
|
303
|
+
"For EACH secret found: (a) if it's in a .env file, ensure .env* is in .gitignore, (b) if it's hardcoded in source code, move it to an environment variable, (c) NEVER dismiss secrets as 'already in .gitignore' — if GuardVibe found it, the FILE exists and contains real values",
|
|
304
|
+
"Rotate any API keys/tokens that were committed to git history — run `git log --all -p -S 'YOUR_KEY_PREFIX' -- '*.env*'` to check",
|
|
305
|
+
"MCP: Call scan_secrets_history — OR CLI: run `npx guardvibe scan --format json` to verify",
|
|
306
|
+
"Re-run the scan and confirm secret count dropped to 0. If it didn't, your fix didn't work — try again",
|
|
305
307
|
],
|
|
306
308
|
},
|
|
307
309
|
code: {
|
|
308
310
|
priority: 2,
|
|
309
311
|
tool: "scan_directory",
|
|
310
312
|
actions: [
|
|
311
|
-
"Call scan_directory with format: json to get full finding list
|
|
312
|
-
"Fix ALL critical and high severity findings
|
|
313
|
-
"Call
|
|
314
|
-
"Re-run
|
|
313
|
+
"MCP: Call scan_directory with format: json — OR CLI: run `npx guardvibe scan --format json` to get full finding list",
|
|
314
|
+
"Fix ALL critical and high severity findings. Read each flagged file, understand the vulnerability, apply the fix suggested in the finding",
|
|
315
|
+
"After fixing each file, re-scan it: MCP: Call scan_file — OR CLI: run `npx guardvibe check <file> --format json` to confirm the finding is gone",
|
|
316
|
+
"Re-run full scan to confirm total code findings dropped",
|
|
315
317
|
],
|
|
316
318
|
},
|
|
317
319
|
dependencies: {
|
|
318
320
|
priority: 3,
|
|
319
321
|
tool: "scan_dependencies",
|
|
320
322
|
actions: [
|
|
321
|
-
"Call scan_dependencies with format: json
|
|
322
|
-
"Run npm audit fix
|
|
323
|
-
"If a package
|
|
324
|
-
"Re-run
|
|
323
|
+
"MCP: Call scan_dependencies with format: json — OR CLI: run `npx guardvibe scan --format json` and check dependency findings, also run `npm audit`",
|
|
324
|
+
"Run `npm audit fix` to auto-fix what's possible. If that doesn't work, run `npm update <package>` for each vulnerable package",
|
|
325
|
+
"If a package can't be updated (breaking changes), find an alternative or pin to a patched version",
|
|
326
|
+
"Re-run `npx guardvibe audit` and confirm dependency findings dropped to 0",
|
|
325
327
|
],
|
|
326
328
|
},
|
|
327
329
|
config: {
|
|
328
330
|
priority: 4,
|
|
329
331
|
tool: "audit_config",
|
|
330
332
|
actions: [
|
|
331
|
-
"Call audit_config with format: json
|
|
332
|
-
"
|
|
333
|
-
"
|
|
334
|
-
"Re-run
|
|
333
|
+
"MCP: Call audit_config with format: json — OR CLI: run `npx guardvibe audit --format json` and parse the config section details",
|
|
334
|
+
"Common config fixes: add missing security headers in next.config.ts (CSP, HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy), set poweredByHeader: false, configure CORS properly",
|
|
335
|
+
"MCP: Call explain_remediation for each rule ID — OR CLI: run `npx guardvibe explain <RULE_ID>` to get specific fix guidance",
|
|
336
|
+
"Re-run audit and confirm config findings dropped",
|
|
335
337
|
],
|
|
336
338
|
},
|
|
337
339
|
taint: {
|
|
338
340
|
priority: 5,
|
|
339
341
|
tool: "analyze_cross_file_dataflow",
|
|
340
342
|
actions: [
|
|
341
|
-
"Call analyze_cross_file_dataflow
|
|
342
|
-
"
|
|
343
|
-
"Re-run
|
|
343
|
+
"MCP: Call analyze_cross_file_dataflow — OR CLI: run `npx guardvibe audit --format json` and parse the taint section. Look for user input (URL params, form data, req.body) flowing to dangerous sinks (SQL, HTML, file system)",
|
|
344
|
+
"Fix each tainted flow: add Zod/joi validation at the input source, use parameterized queries for SQL, use sanitizeUrl/DOMPurify for HTML output, validate file paths",
|
|
345
|
+
"Re-run audit and confirm taint findings dropped to 0",
|
|
344
346
|
],
|
|
345
347
|
},
|
|
346
348
|
"auth-coverage": {
|
|
347
349
|
priority: 6,
|
|
348
350
|
tool: "auth_coverage",
|
|
349
351
|
actions: [
|
|
350
|
-
"Call auth_coverage with format: json to list all unprotected routes",
|
|
351
|
-
"
|
|
352
|
-
"
|
|
353
|
-
"Re-run
|
|
352
|
+
"MCP: Call auth_coverage with format: json — OR CLI: run `npx guardvibe auth-coverage --format json` to list all unprotected routes",
|
|
353
|
+
"For each unprotected route: (a) if it needs auth, add middleware or auth guard (Clerk/NextAuth/Supabase), (b) if it's intentionally public (homepage, blog, about, etc.), add it to .guardviberc file under authExceptions with a reason",
|
|
354
|
+
"Create or update .guardviberc in project root: {\"authExceptions\": [{\"path\": \"/blog\", \"reason\": \"Public page\"}]}",
|
|
355
|
+
"Re-run `npx guardvibe auth-coverage --format json` and confirm unprotected count matches your authExceptions count",
|
|
354
356
|
],
|
|
355
357
|
},
|
|
356
358
|
};
|
|
@@ -388,6 +390,14 @@ export function formatAuditResult(result, format) {
|
|
|
388
390
|
remediationPlan: {
|
|
389
391
|
totalSectionsWithFindings: plan.length,
|
|
390
392
|
warning: `MANDATORY: You must fix ALL ${plan.length} sections below. Do NOT fix only the code section and skip the rest. Work through each section in priority order. After completing ALL sections, call verify_remediation to confirm.`,
|
|
393
|
+
rules: [
|
|
394
|
+
"Do NOT dismiss findings as 'false positive' without running the section-specific tool first and verifying the finding is genuinely wrong.",
|
|
395
|
+
"Do NOT say 'npm audit is clean' to skip dependencies — GuardVibe uses the OSV database which may detect CVEs that npm audit misses.",
|
|
396
|
+
"Do NOT say 'already in .gitignore' to skip secrets — if GuardVibe detects secrets, they exist in the scanned files and must be removed or moved to env vars.",
|
|
397
|
+
"Do NOT skip auth-coverage by saying routes are 'intentionally public' — either add auth guards or add them to .guardviberc authExceptions so the scanner recognizes them.",
|
|
398
|
+
"After fixing each section, re-run the section-specific tool to confirm the finding count dropped. If it didn't drop, your fix didn't work.",
|
|
399
|
+
"Only call verify_remediation AFTER you have worked through ALL sections and confirmed each one improved.",
|
|
400
|
+
],
|
|
391
401
|
steps: plan,
|
|
392
402
|
},
|
|
393
403
|
});
|
|
@@ -464,6 +474,13 @@ export function formatAuditResult(result, format) {
|
|
|
464
474
|
lines.push(``);
|
|
465
475
|
lines.push(`> **IMPORTANT: You MUST fix ALL ${plan.length} sections below. Do NOT fix only the code section and skip the rest. Work through each section in priority order. After completing ALL sections, call \`verify_remediation\` to confirm.**`);
|
|
466
476
|
lines.push(``);
|
|
477
|
+
lines.push(`**Rules:**`);
|
|
478
|
+
lines.push(`- Do NOT dismiss findings as "false positive" without running the section-specific tool first`);
|
|
479
|
+
lines.push(`- Do NOT say "npm audit is clean" to skip dependencies — GuardVibe uses OSV which may detect CVEs npm misses`);
|
|
480
|
+
lines.push(`- Do NOT say "already in .gitignore" to skip secrets — if detected, they exist in scanned files`);
|
|
481
|
+
lines.push(`- After fixing each section, re-run the section tool to confirm finding count dropped`);
|
|
482
|
+
lines.push(`- Only call verify_remediation AFTER all sections show improvement`);
|
|
483
|
+
lines.push(``);
|
|
467
484
|
for (const step of plan) {
|
|
468
485
|
lines.push(`### Step ${step.priority}: ${step.section} (${step.findings} findings — ${step.critical} critical, ${step.high} high)`);
|
|
469
486
|
lines.push(``);
|
package/build/utils/config.d.ts
CHANGED
|
@@ -26,6 +26,13 @@ export interface GuardVibeConfig {
|
|
|
26
26
|
* e.g. ["requireAdmin", "verifyUser", "ensureLoggedIn"]
|
|
27
27
|
* These are added ON TOP of the built-in pattern-agnostic detection. */
|
|
28
28
|
authFunctions?: string[];
|
|
29
|
+
/** Routes that are intentionally public (no auth required).
|
|
30
|
+
* e.g. [{"path": "/blog", "reason": "Public page"}]
|
|
31
|
+
* These are excluded from auth-coverage unprotected count. */
|
|
32
|
+
authExceptions?: Array<{
|
|
33
|
+
path: string;
|
|
34
|
+
reason: string;
|
|
35
|
+
}>;
|
|
29
36
|
}
|
|
30
37
|
export declare function loadConfig(dir?: string): GuardVibeConfig;
|
|
31
38
|
export declare function resetConfigCache(): void;
|
package/build/utils/config.js
CHANGED
|
@@ -68,6 +68,7 @@ export function loadConfig(dir) {
|
|
|
68
68
|
requiredControls: Array.isArray(parsed.compliance.requiredControls) ? parsed.compliance.requiredControls : undefined,
|
|
69
69
|
} : undefined,
|
|
70
70
|
authFunctions: Array.isArray(parsed.authFunctions) ? parsed.authFunctions : undefined,
|
|
71
|
+
authExceptions: Array.isArray(parsed.authExceptions) ? parsed.authExceptions : undefined,
|
|
71
72
|
};
|
|
72
73
|
}
|
|
73
74
|
catch { }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.12",
|
|
4
4
|
"mcpName": "io.github.goklab/guardvibe",
|
|
5
5
|
"description": "Security MCP for vibe coding. 335 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",
|