guardvibe 3.0.6 → 3.0.7

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.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * CLI: guardvibe auth-coverage [path]
3
+ * Analyze authentication coverage across Next.js App Router routes.
4
+ */
5
+ export declare function runAuthCoverage(args: string[]): Promise<void>;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * CLI: guardvibe auth-coverage [path]
3
+ * Analyze authentication coverage across Next.js App Router routes.
4
+ */
5
+ import { readdirSync, readFileSync, statSync, writeFileSync, existsSync, mkdirSync } from "fs";
6
+ import { resolve, dirname } from "path";
7
+ import { parseArgs, validateFormat, getOutputPath } from "./args.js";
8
+ import { analyzeAuthCoverage, formatAuthCoverage } from "../tools/auth-coverage.js";
9
+ export async function runAuthCoverage(args) {
10
+ const { flags, positional } = parseArgs(args);
11
+ const targetPath = resolve(positional[0] ?? ".");
12
+ const format = validateFormat(flags);
13
+ const outputFile = getOutputPath(flags);
14
+ // Walk directory to discover route/page/layout/middleware files
15
+ const jsFiles = [];
16
+ const skip = new Set(["node_modules", ".git", ".next", "build", "dist", ".turbo", "coverage"]);
17
+ function walk(d) {
18
+ if (jsFiles.length >= 500)
19
+ return;
20
+ let entries;
21
+ try {
22
+ entries = readdirSync(d);
23
+ }
24
+ catch {
25
+ return;
26
+ }
27
+ for (const entry of entries) {
28
+ if (jsFiles.length >= 500)
29
+ return;
30
+ if (skip.has(entry))
31
+ continue;
32
+ const full = resolve(d, entry);
33
+ let stat;
34
+ try {
35
+ stat = statSync(full);
36
+ }
37
+ catch {
38
+ continue;
39
+ }
40
+ if (stat.isDirectory()) {
41
+ walk(full);
42
+ continue;
43
+ }
44
+ if (!/\.(ts|tsx|js|jsx)$/.test(entry))
45
+ continue;
46
+ if (stat.size > 100_000)
47
+ continue;
48
+ try {
49
+ const content = readFileSync(full, "utf-8");
50
+ const relPath = full.replace(targetPath + "/", "");
51
+ jsFiles.push({ path: relPath, content });
52
+ }
53
+ catch { /* skip unreadable */ }
54
+ }
55
+ }
56
+ walk(targetPath);
57
+ const routeFiles = jsFiles.filter(f => /\/(route|page)\.(ts|tsx|js|jsx)$/.test(f.path));
58
+ const layoutFiles = jsFiles.filter(f => /\/layout\.(ts|tsx|js|jsx)$/.test(f.path));
59
+ const middlewareFile = jsFiles.find(f => /middleware\.(ts|js)$/.test(f.path));
60
+ const report = analyzeAuthCoverage(routeFiles, middlewareFile?.content ?? "", layoutFiles);
61
+ const formatArg = format === "json" ? "json" : "markdown";
62
+ const result = formatAuthCoverage(report, formatArg);
63
+ if (outputFile) {
64
+ const dir = dirname(outputFile);
65
+ if (!existsSync(dir)) {
66
+ mkdirSync(dir, { recursive: true });
67
+ }
68
+ writeFileSync(outputFile, result, "utf-8");
69
+ console.log(` [OK] Results written to ${outputFile}`);
70
+ }
71
+ else {
72
+ console.log(result);
73
+ }
74
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * CLI: guardvibe compliance [path] --framework SOC2
3
+ * Generate compliance report mapping findings to framework controls.
4
+ */
5
+ export declare function runCompliance(args: string[]): Promise<void>;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * CLI: guardvibe compliance [path] --framework SOC2
3
+ * Generate compliance report mapping findings to framework controls.
4
+ */
5
+ import { writeFileSync, existsSync, mkdirSync } from "fs";
6
+ import { resolve, dirname } from "path";
7
+ import { parseArgs, validateFormat, getOutputPath, getStringFlag } from "./args.js";
8
+ import { complianceReport } from "../tools/compliance-report.js";
9
+ const VALID_FRAMEWORKS = new Set(["SOC2", "PCI-DSS", "HIPAA", "GDPR", "ISO27001", "EUAIACT"]);
10
+ export async function runCompliance(args) {
11
+ const { flags, positional } = parseArgs(args);
12
+ const targetPath = resolve(positional[0] ?? ".");
13
+ const framework = getStringFlag(flags, "framework") ?? "SOC2";
14
+ const format = validateFormat(flags);
15
+ const outputFile = getOutputPath(flags);
16
+ if (!VALID_FRAMEWORKS.has(framework)) {
17
+ console.error(` [ERR] Invalid framework "${framework}". Use: ${[...VALID_FRAMEWORKS].join(", ")}`);
18
+ process.exit(1);
19
+ }
20
+ const formatArg = format === "json" ? "json" : "markdown";
21
+ const result = complianceReport(targetPath, framework, formatArg);
22
+ if (outputFile) {
23
+ const dir = dirname(outputFile);
24
+ if (!existsSync(dir)) {
25
+ mkdirSync(dir, { recursive: true });
26
+ }
27
+ writeFileSync(outputFile, result, "utf-8");
28
+ console.log(` [OK] Results written to ${outputFile}`);
29
+ }
30
+ else {
31
+ console.log(result);
32
+ }
33
+ }
package/build/cli.js CHANGED
@@ -26,6 +26,8 @@ function printUsage() {
26
26
  npx guardvibe explain <ruleId> Get detailed remediation guidance for a rule
27
27
  npx guardvibe fix <file> Get security fix suggestions for a file
28
28
  npx guardvibe check-cmd "<cmd>" Check if a shell command is safe to execute
29
+ npx guardvibe auth-coverage [path] Auth coverage analysis (Next.js routes)
30
+ npx guardvibe compliance [path] Compliance report (--framework SOC2|GDPR|...)
29
31
  npx guardvibe init <platform> Setup MCP server configuration
30
32
  npx guardvibe hook install Install pre-commit security hook
31
33
  npx guardvibe hook uninstall Remove pre-commit security hook
@@ -142,6 +144,14 @@ async function main() {
142
144
  const { runCheckCmd } = await import("./cli/check-cmd.js");
143
145
  await runCheckCmd(subArgs);
144
146
  }
147
+ else if (command === "auth-coverage") {
148
+ const { runAuthCoverage } = await import("./cli/auth-coverage.js");
149
+ await runAuthCoverage(subArgs);
150
+ }
151
+ else if (command === "compliance") {
152
+ const { runCompliance } = await import("./cli/compliance.js");
153
+ await runCompliance(subArgs);
154
+ }
145
155
  else {
146
156
  console.error(` Unknown command: ${command}`);
147
157
  printUsage();
@@ -276,6 +276,7 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
276
276
  const codeHasFilenameSanitization = /(?:\.replace\s*\(\s*\/\[?\^?[a-z0-9\\-_\]]*\]?\/?[gi]*\s*,|sanitize(?:File|Name|Path)|safeName|cleanName)/i.test(code) ||
277
277
  /(?:Date\.now\(\)|timestamp|uuid|nanoid|crypto\.randomUUID)[\s\S]{0,80}?\.\s*(?:ext|split|pop)/i.test(code);
278
278
  const isPeerDeps = /["']peerDependencies["']/i.test(code);
279
+ const codeHasAuthSession = /(?:supabase\.auth\.getUser|supabase\.auth\.getSession|getServerSession|auth\(\)|getSession\(\)|currentUser\(\))/i.test(code);
279
280
  // Config: check custom auth function names from .guardviberc
280
281
  if (!codeHasAuthGuard && config.authFunctions && config.authFunctions.length > 0) {
281
282
  const customPattern = new RegExp(`(?:${config.authFunctions.join("|")})\\s*\\(`, "i");
@@ -544,6 +545,23 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
544
545
  if (/(?:Count|Length|Balance|Map|List|Array|Index|Size|Total|Num|Id|Type|Name|Status|Data|Info|Error|Result|Response|Config|Option|Url|Path|Provider|Model|Limit|Quota|Rate|Max|Min)/i.test(varName))
545
546
  continue;
546
547
  }
548
+ // Skip VG1005 (.or() filter injection) when all interpolated variables are
549
+ // server-verified auth IDs (user.id, session.user.id, auth.uid, currentUser.id)
550
+ if (rule.id === "VG1005" && codeHasAuthSession) {
551
+ // The regex match ends at `${` — grab the full template literal from code
552
+ const orStart = match.index;
553
+ const backtickIdx = code.indexOf('`', orStart);
554
+ if (backtickIdx !== -1) {
555
+ const closingBacktick = code.indexOf('`', backtickIdx + 1);
556
+ if (closingBacktick !== -1) {
557
+ const fullTemplate = code.substring(backtickIdx, closingBacktick + 1);
558
+ const interpolations = [...fullTemplate.matchAll(/\$\{([^}]+)\}/g)].map(m => m[1].trim());
559
+ const safeAuthPattern = /^(?:user\.id|user\?\.id|session\.user\.id|session\.user\?\.id|currentUser\.id|currentUser\?\.id|auth\.uid|auth\?\.uid|session\.uid|session\?\.uid)$/;
560
+ if (interpolations.length > 0 && interpolations.every(v => safeAuthPattern.test(v)))
561
+ continue;
562
+ }
563
+ }
564
+ }
547
565
  // Skip VG903 React version in peerDependencies sections
548
566
  if (rule.id === "VG903") {
549
567
  const beforeText = code.substring(0, match.index);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "3.0.6",
3
+ "version": "3.0.7",
4
4
  "mcpName": "io.github.goklab/guardvibe",
5
5
  "description": "Security MCP for vibe coding. 334 rules, 34 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",