guardvibe 3.0.11 → 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.
@@ -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 report = analyzeAuthCoverage(routeFiles, middlewareFile?.content ?? "", layoutFiles);
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) {
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 report = analyzeAuthCoverage(routeFiles, middlewareFile?.content ?? "", layoutFiles);
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[]): AuthCoverageReport;
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 report = analyzeAuthCoverage(routeFiles, middlewareFile?.content ?? "", layoutFiles);
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 to list all secrets with file locations",
301
- "For EACH secret: move to environment variable, add file to .gitignore",
302
- "Rotate any API keys/tokens that were committed — they are compromised",
303
- "Call scan_secrets_history to check git history for previously committed secrets",
304
- "Re-run scan_secrets to confirm 0 secrets remain",
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 with fix suggestions",
312
- "Fix ALL critical and high severity findings using fix_code for each file",
313
- "Call verify_fix after each fix to confirm the vulnerability is resolved",
314
- "Re-run scan_directory to confirm findings are resolved",
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 to list vulnerable packages with CVE details",
322
- "Run npm audit fix or npm update <package> for each vulnerable dependency",
323
- "If a package is abandoned, find an alternative with check_package_health",
324
- "Re-run scan_dependencies to confirm 0 CVEs remain",
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 to list all config issues",
332
- "Call explain_remediation for each finding to get specific fix guidance",
333
- "Apply fixes to next.config, middleware, .env, vercel.json, etc.",
334
- "Re-run audit_config to confirm config issues are resolved",
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 to trace tainted data flows from source to sink",
342
- "Add input validation (zod/joi) at each source, or output encoding at each sink",
343
- "Re-run analyze_cross_file_dataflow to confirm tainted flows are resolved",
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
- "Add auth guard (Clerk/NextAuth/Supabase) to each unprotected route",
352
- "If a route is intentionally public, document it in .guardviberc authExceptions",
353
- "Re-run auth_coverage to confirm all routes are protected or documented",
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
  };
@@ -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;
@@ -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.11",
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",