guardvibe 1.7.1 → 1.8.0

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/README.md CHANGED
@@ -127,7 +127,7 @@ SOC2, PCI-DSS, HIPAA control mapping with compliance reports
127
127
  ### Supply Chain
128
128
  Malicious postinstall scripts, unpinned GitHub Actions, typosquat detection
129
129
 
130
- ## Tools (12 MCP tools)
130
+ ## Tools (22 MCP tools)
131
131
 
132
132
  | Tool | What it does |
133
133
  |------|-------------|
@@ -143,6 +143,16 @@ Malicious postinstall scripts, unpinned GitHub Actions, typosquat detection
143
143
  | `export_sarif` | SARIF v2.1.0 export for CI/CD integration |
144
144
  | `get_security_docs` | Security best practices and guides |
145
145
  | `fix_code` | **Auto-fix suggestions** with concrete patches for AI agents |
146
+ | `audit_config` | Audit project configuration files for cross-file security misconfigurations |
147
+ | `generate_policy` | Detect project stack and generate tailored security policies (CSP, CORS, RLS) |
148
+ | `review_pr` | Review PR diff for security issues with severity gating |
149
+ | `scan_secrets_history` | Scan git history for leaked secrets (active and removed) |
150
+ | `policy_check` | Check project against compliance policies defined in .guardviberc |
151
+ | `analyze_dataflow` | Track tainted data flows from user input to dangerous sinks |
152
+ | `check_command` | Analyze shell commands for security risks before execution |
153
+ | `scan_config_change` | Compare config file versions to detect security downgrades |
154
+ | `repo_security_posture` | Assess overall repository security posture and map sensitive areas |
155
+ | `explain_remediation` | Get detailed remediation guidance with exploit scenarios and fix strategies |
146
156
 
147
157
  All scanning tools support `format: "json"` for machine-readable output.
148
158
 
@@ -311,6 +321,104 @@ Tested on a real 644-file Next.js + Supabase project:
311
321
  - False positive rate: **near zero** (comment/string filtering, human-readable text detection)
312
322
  - Detection rate: **100%** on known vulnerability patterns
313
323
 
324
+ ## Troubleshooting
325
+
326
+ ### MCP connection issues
327
+
328
+ If your AI agent cannot connect to GuardVibe:
329
+
330
+ 1. **Restart your IDE/agent.** MCP servers are started by the host application. After running `npx guardvibe init`, restart Claude Code, Cursor, or Gemini CLI for the config to take effect.
331
+ 2. **Check the config path.** Run `npx guardvibe init claude` again and verify the output shows the correct config file location (`.claude.json` in your project root for Claude Code, `.cursor/mcp.json` for Cursor).
332
+ 3. **Verify Node.js version.** GuardVibe requires Node.js >= 18.0.0. Check with `node --version`.
333
+ 4. **Check npx cache.** If you upgraded GuardVibe and the old version is cached, run `npx -y guardvibe@latest` to force the latest version.
334
+
335
+ ### Node.js version requirements
336
+
337
+ GuardVibe requires **Node.js >= 18.0.0**. Earlier versions will fail with syntax errors or missing APIs. Node.js 22 LTS is recommended.
338
+
339
+ ### False positives
340
+
341
+ If a rule triggers on safe code:
342
+
343
+ - **Inline suppression:** Add `// guardvibe-ignore VG001` on the same line, or `// guardvibe-ignore-next-line VG001` on the line above. Supports `//`, `#`, and `<!-- -->` comment styles.
344
+ - **Config exclusion:** Add the rule ID to `rules.disable` in `.guardviberc`:
345
+ ```json
346
+ { "rules": { "disable": ["VG030"] } }
347
+ ```
348
+ - **Path exclusion:** Add directories to `scan.exclude` in `.guardviberc`:
349
+ ```json
350
+ { "scan": { "exclude": ["fixtures/", "test-data/"] } }
351
+ ```
352
+
353
+ ### Pre-commit hook issues
354
+
355
+ - **Hook not running:** Verify the hook file exists at `.git/hooks/pre-commit` and is executable (`chmod +x .git/hooks/pre-commit`).
356
+ - **Hook blocking valid commits:** Use `git commit --no-verify` to skip the hook temporarily, then investigate the findings.
357
+ - **Removing the hook:** Run `npx guardvibe hook uninstall`.
358
+
359
+ ## Security Model
360
+
361
+ GuardVibe is designed for use on sensitive and proprietary codebases:
362
+
363
+ - **100% local execution.** All scanning happens on your machine. No code, findings, or metadata are sent to any server.
364
+ - **No accounts, no API keys, no telemetry.** There is no signup, no cloud dashboard, and no usage tracking of any kind.
365
+ - **One optional network call.** The `scan_dependencies` and `check_dependencies` tools query the [OSV API](https://osv.dev/) to check for known CVEs. This is opt-in -- you only call it when you explicitly use those tools. No other tool makes network requests.
366
+ - **Safe for air-gapped environments.** All code analysis rules run entirely offline. Only dependency vulnerability checks require network access.
367
+
368
+ ## Configuration (.guardviberc)
369
+
370
+ Create a `.guardviberc` JSON file in your project root to customize GuardVibe behavior.
371
+
372
+ ### Full example
373
+
374
+ ```json
375
+ {
376
+ "rules": {
377
+ "disable": ["VG030", "VG045"],
378
+ "severity": {
379
+ "VG002": "medium",
380
+ "VG010": "low"
381
+ }
382
+ },
383
+ "scan": {
384
+ "exclude": ["fixtures/", "coverage/", "dist/", "vendor/"],
385
+ "maxFileSize": 1048576
386
+ },
387
+ "plugins": [
388
+ "guardvibe-rules-awesome",
389
+ "./my-local-rules"
390
+ ],
391
+ "compliance": {
392
+ "frameworks": ["SOC2", "HIPAA"],
393
+ "failOn": "high",
394
+ "exceptions": [
395
+ {
396
+ "ruleId": "VG030",
397
+ "reason": "Accepted risk per security review 2026-03",
398
+ "approvedBy": "security-team",
399
+ "expiresAt": "2026-12-31",
400
+ "files": ["src/legacy/**"]
401
+ }
402
+ ],
403
+ "requiredControls": ["SOC2:CC6.1"]
404
+ }
405
+ }
406
+ ```
407
+
408
+ ### Configuration fields
409
+
410
+ | Field | Type | Default | Description |
411
+ |-------|------|---------|-------------|
412
+ | `rules.disable` | `string[]` | `[]` | Rule IDs to skip during scanning |
413
+ | `rules.severity` | `Record<string, string>` | `{}` | Override severity for specific rules |
414
+ | `scan.exclude` | `string[]` | `[]` | Glob patterns for directories/files to skip |
415
+ | `scan.maxFileSize` | `number` | `512000` | Maximum file size in bytes (files larger than this are skipped) |
416
+ | `plugins` | `string[]` | `[]` | npm package names or local paths to load as plugins |
417
+ | `compliance.frameworks` | `string[]` | -- | Compliance frameworks to map against (`SOC2`, `PCI-DSS`, `HIPAA`, `GDPR`, `ISO27001`) |
418
+ | `compliance.failOn` | `string` | `"high"` | Minimum severity that causes compliance failure |
419
+ | `compliance.exceptions` | `PolicyException[]` | `[]` | Approved exceptions with expiration dates |
420
+ | `compliance.requiredControls` | `string[]` | -- | Controls that must pass regardless of exceptions |
421
+
314
422
  ## Security
315
423
 
316
424
  GuardVibe takes supply chain security seriously:
package/build/cli.js CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "module";
2
3
  import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, unlinkSync } from "fs";
3
4
  import { join, dirname } from "path";
4
5
  import { homedir } from "os";
6
+ const require = createRequire(import.meta.url);
7
+ const pkg = require("../package.json");
5
8
  const GUARDVIBE_MCP_CONFIG = {
6
9
  command: "npx",
7
10
  args: ["-y", "guardvibe"],
@@ -314,23 +317,33 @@ function printUsage() {
314
317
  console.log(`
315
318
  GuardVibe Security - CLI
316
319
 
317
- Usage:
320
+ Commands:
318
321
  npx guardvibe scan [path] Scan a directory for security issues
319
- npx guardvibe check <file> Scan a single file
320
- npx guardvibe init <platform> Setup MCP server
321
- npx guardvibe hook install Install pre-commit hook
322
- npx guardvibe hook uninstall Remove pre-commit hook
322
+ npx guardvibe check <file> Scan a single file for security issues
323
+ npx guardvibe init <platform> Setup MCP server configuration
324
+ npx guardvibe hook install Install pre-commit security hook
325
+ npx guardvibe hook uninstall Remove pre-commit security hook
323
326
  npx guardvibe ci github Generate GitHub Actions workflow
324
327
 
328
+ Scan CLI (used by pre-commit hook and CI):
329
+ npx guardvibe-scan Scan git-staged files
330
+ npx guardvibe-scan --format sarif --output results.sarif
331
+
325
332
  Options:
326
333
  --format <type> Output format: markdown (default), json, sarif
327
334
  --output <file> Write results to file instead of stdout
335
+ --version, -V Print version and exit
336
+ --help, -h Show this help message
328
337
 
329
338
  MCP Platforms:
330
- claude Claude Code (.claude.json)
339
+ claude Claude Code (.claude.json in project root)
331
340
  gemini Gemini CLI (~/.gemini/settings.json)
332
341
  cursor Cursor (.cursor/mcp.json)
333
- all All platforms
342
+ all All platforms at once
343
+
344
+ Supported File Types:
345
+ .js .jsx .mjs .cjs .ts .tsx .mts .cts .py .go .html .sql
346
+ .sh .bash .yml .yaml .tf .toml .json Dockerfile
334
347
 
335
348
  Examples:
336
349
  npx guardvibe scan .
@@ -345,6 +358,10 @@ function printUsage() {
345
358
  }
346
359
  async function main() {
347
360
  const args = process.argv.slice(2);
361
+ if (args.includes("--version") || args.includes("-V")) {
362
+ console.log(pkg.version);
363
+ process.exit(0);
364
+ }
348
365
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
349
366
  printUsage();
350
367
  process.exit(0);
@@ -9,6 +9,7 @@ export const cicdRules = [
9
9
  languages: ["yaml"],
10
10
  fix: "Pass secrets as environment variables instead of interpolating in run steps.",
11
11
  fixCode: "# Pass secrets via env, not interpolation\nsteps:\n - run: echo \"deploying\"\n env:\n MY_SECRET: ${{ secrets.MY_SECRET }}",
12
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req2.3"],
12
13
  },
13
14
  {
14
15
  id: "VG211",
@@ -20,6 +21,7 @@ export const cicdRules = [
20
21
  languages: ["yaml"],
21
22
  fix: "Use pull_request trigger instead, or avoid checking out PR code with pull_request_target.",
22
23
  fixCode: "# Use pull_request instead of pull_request_target\non:\n pull_request:\n branches: [main]",
24
+ compliance: ["SOC2:CC6.1", "SOC2:CC6.6"],
23
25
  },
24
26
  {
25
27
  id: "VG212",
@@ -31,6 +33,7 @@ export const cicdRules = [
31
33
  languages: ["yaml"],
32
34
  fix: "Pin actions to a specific commit SHA or version tag.",
33
35
  fixCode: "# Pin to specific version or SHA\nuses: actions/checkout@v4\n# Or pin to commit SHA:\n# uses: actions/checkout@abc123def456",
36
+ compliance: ["SOC2:CC7.1"],
34
37
  },
35
38
  {
36
39
  id: "VG213",
@@ -42,6 +45,7 @@ export const cicdRules = [
42
45
  languages: ["yaml"],
43
46
  fix: "Specify minimum required permissions for each job.",
44
47
  fixCode: "# Use least-privilege permissions\npermissions:\n contents: read\n pull-requests: write",
48
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req8"],
45
49
  },
46
50
  {
47
51
  id: "VG214",
@@ -132,6 +132,7 @@ export const coreRules = [
132
132
  languages: ["json"],
133
133
  fix: "Pin dependencies to specific versions or use caret ranges (^1.2.3). Run npm audit regularly.",
134
134
  fixCode: "// Pin to specific version\n\"lodash\": \"^4.17.21\"\n// Run: npm audit to check for vulnerabilities",
135
+ compliance: ["SOC2:CC7.1"],
135
136
  },
136
137
  {
137
138
  id: "VG030",
@@ -143,6 +144,7 @@ export const coreRules = [
143
144
  languages: ["javascript", "typescript", "python", "go"],
144
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).",
145
146
  fixCode: "// Express rate limiting\nimport rateLimit from 'express-rate-limit';\napp.use('/api/', rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));",
147
+ compliance: ["SOC2:CC7.1"],
146
148
  },
147
149
  {
148
150
  id: "VG040",
@@ -166,6 +168,7 @@ export const coreRules = [
166
168
  languages: ["javascript", "typescript", "python"],
167
169
  fix: "Disable debug mode in production. Never expose stack traces to users.",
168
170
  fixCode: "// Use environment-based config\nconst DEBUG = process.env.NODE_ENV !== 'production';",
171
+ compliance: ["SOC2:CC6.1"],
169
172
  },
170
173
  {
171
174
  id: "VG042",
@@ -177,6 +180,7 @@ export const coreRules = [
177
180
  languages: ["javascript", "typescript"],
178
181
  fix: "Use helmet middleware: npm install helmet, then app.use(helmet()).",
179
182
  fixCode: "// Add helmet for security headers\nimport helmet from 'helmet';\napp.use(helmet());",
183
+ compliance: ["SOC2:CC6.1"],
180
184
  },
181
185
  {
182
186
  id: "VG060",
@@ -205,7 +209,7 @@ export const coreRules = [
205
209
  {
206
210
  id: "VG062",
207
211
  name: "Hardcoded secret in variable",
208
- severity: "high",
212
+ severity: "critical",
209
213
  owasp: "A07:2025 Auth Failures",
210
214
  description: "Variable named secret, password, or apiKey assigned a string literal. Secrets should come from environment variables or a secrets manager, never hardcoded in source.",
211
215
  pattern: /(?:(?:const|let|var|export)\s+)?(?:secret|password|passwd|apiKey|api_key|privateKey|private_key|signingKey|signing_key|encryptionKey|encryption_key|masterKey|master_key|dbPassword|db_password)\s*(?::\s*string\s*)?=\s*["'][^"']{8,}["']/gi,
@@ -47,6 +47,7 @@ export const deploymentRules = [
47
47
  languages: ["vercel-config", "json"],
48
48
  fix: "Set maxDuration to the minimum required. Default 300s is sufficient for most use cases.",
49
49
  fixCode: '// Set reasonable maxDuration\nexport const maxDuration = 60; // seconds — adjust to actual need',
50
+ compliance: ["SOC2:CC6.1"],
50
51
  },
51
52
  {
52
53
  id: "VG506",
@@ -83,6 +84,7 @@ export const deploymentRules = [
83
84
  languages: ["nextjs-config", "javascript", "typescript"],
84
85
  fix: "Set poweredByHeader to false in next.config.ts.",
85
86
  fixCode: "// next.config.ts\nconst config = {\n poweredByHeader: false,\n};",
87
+ compliance: ["SOC2:CC6.1"],
86
88
  },
87
89
  {
88
90
  id: "VG510",
@@ -9,6 +9,7 @@ export const dockerfileRules = [
9
9
  languages: ["dockerfile"],
10
10
  fix: "Add a USER instruction after installing dependencies: USER node (or appropriate non-root user).",
11
11
  fixCode: "FROM node:20-alpine\nRUN addgroup -S app && adduser -S app -G app\n# ... install dependencies ...\nUSER app\nCMD [\"node\", \"server.js\"]",
12
+ compliance: ["SOC2:CC6.1"],
12
13
  },
13
14
  {
14
15
  id: "VG201",
@@ -20,6 +21,7 @@ export const dockerfileRules = [
20
21
  languages: ["dockerfile"],
21
22
  fix: "Copy only dependency files first (package.json, requirements.txt), then install, then copy source.",
22
23
  fixCode: "# Copy dependency files first\nCOPY package.json package-lock.json ./\nRUN npm ci --production\n# Then copy source\nCOPY . .",
24
+ compliance: ["SOC2:CC6.1"],
23
25
  },
24
26
  {
25
27
  id: "VG202",
@@ -31,6 +33,7 @@ export const dockerfileRules = [
31
33
  languages: ["dockerfile"],
32
34
  fix: "Pin to a specific version tag: FROM node:20-alpine instead of FROM node:latest.",
33
35
  fixCode: "# Pin to specific version\nFROM node:20-alpine\n# Not: FROM node:latest\n# Not: FROM node",
36
+ compliance: ["SOC2:CC7.1"],
34
37
  },
35
38
  {
36
39
  id: "VG203",
@@ -42,6 +45,7 @@ export const dockerfileRules = [
42
45
  languages: ["dockerfile"],
43
46
  fix: "Use runtime environment variables or Docker secrets instead of baking secrets into the image.",
44
47
  fixCode: "# Don't bake secrets in image\n# Instead, pass at runtime:\n# docker run -e SECRET_KEY=xxx myapp\n# Or use Docker secrets / .env file",
48
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req2.3"],
45
49
  },
46
50
  {
47
51
  id: "VG204",
@@ -53,6 +57,7 @@ export const dockerfileRules = [
53
57
  languages: ["dockerfile"],
54
58
  fix: "Use COPY instead of ADD for local files. Only use ADD for URLs or tar extraction.",
55
59
  fixCode: "# Use COPY for local files\nCOPY ./src /app/src\n# Only use ADD for remote files or tar extraction\n# ADD https://example.com/file.tar.gz /app/",
60
+ compliance: ["SOC2:CC6.1"],
56
61
  },
57
62
  {
58
63
  id: "VG205",
@@ -10,6 +10,7 @@ export const goRules = [
10
10
  languages: ["go"],
11
11
  fix: "Use parameterized queries: db.Query('SELECT * FROM users WHERE id = $1', id). Never use fmt.Sprintf for SQL.",
12
12
  fixCode: "// Use parameterized queries\nrows, err := db.Query(\"SELECT * FROM users WHERE id = $1\", id)",
13
+ compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
13
14
  },
14
15
  {
15
16
  id: "VG111",
@@ -21,6 +22,7 @@ export const goRules = [
21
22
  languages: ["go"],
22
23
  fix: "Validate and sanitize all input before passing to exec.Command. Use an allowlist of permitted commands.",
23
24
  fixCode: "// Validate input, use allowlist\ncmd := exec.Command(\"ls\", \"-la\", safeDir)",
25
+ compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
24
26
  },
25
27
  {
26
28
  id: "VG112",
@@ -32,6 +34,7 @@ export const goRules = [
32
34
  languages: ["go"],
33
35
  fix: "Avoid template.HTML() with user input. Use html/template which auto-escapes by default.",
34
36
  fixCode: "// Use html/template which auto-escapes\n// Avoid template.HTML() with user input\ntmpl.Execute(w, data) // auto-escaped",
37
+ compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.7"],
35
38
  },
36
39
  {
37
40
  id: "VG113",
@@ -43,6 +46,7 @@ export const goRules = [
43
46
  languages: ["go"],
44
47
  fix: "Wrap handlers with authentication middleware: http.Handle('/api/', authMiddleware(handler)).",
45
48
  fixCode: "// Wrap with auth middleware\nhttp.Handle(\"/api/\", authMiddleware(apiHandler))",
49
+ compliance: ["SOC2:CC6.1", "SOC2:CC6.6"],
46
50
  },
47
51
  {
48
52
  id: "VG114",
@@ -54,6 +58,7 @@ export const goRules = [
54
58
  languages: ["go"],
55
59
  fix: "Use crypto/sha256 or golang.org/x/crypto/bcrypt for password hashing.",
56
60
  fixCode: "// Use bcrypt for passwords\nimport \"golang.org/x/crypto/bcrypt\"\nhash, _ := bcrypt.GenerateFromPassword([]byte(password), 12)",
61
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req4.1"],
57
62
  },
58
63
  {
59
64
  id: "VG115",
@@ -65,5 +70,6 @@ export const goRules = [
65
70
  languages: ["go"],
66
71
  fix: "Set specific allowed origins instead of wildcard '*'.",
67
72
  fixCode: "// Specify allowed origins\nw.Header().Set(\"Access-Control-Allow-Origin\", \"https://myapp.com\")",
73
+ compliance: ["SOC2:CC6.1"],
68
74
  },
69
75
  ];
package/build/index.js CHANGED
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "module";
2
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
5
  import { z } from "zod";
5
6
  import { checkCode } from "./tools/check-code.js";
7
+ const require = createRequire(import.meta.url);
8
+ const pkg = require("../package.json");
6
9
  import { checkProject } from "./tools/check-project.js";
7
10
  import { getSecurityDocs } from "./tools/get-security-docs.js";
8
11
  import { checkDependencies } from "./tools/check-deps.js";
@@ -27,9 +30,10 @@ import { explainRemediation } from "./tools/explain-remediation.js";
27
30
  import { discoverPlugins } from "./plugins/loader.js";
28
31
  import { builtinRules } from "./data/rules/index.js";
29
32
  import { loadConfig } from "./utils/config.js";
33
+ import { setRules, getRules } from "./utils/rule-registry.js";
30
34
  const server = new McpServer({
31
35
  name: "guardvibe",
32
- version: "1.6.0",
36
+ version: pkg.version,
33
37
  });
34
38
  // Tool 1: Analyze code for security vulnerabilities
35
39
  server.tool("check_code", "Analyze code for security vulnerabilities (OWASP Top 10, XSS, SQL injection, insecure patterns). Use this when reviewing or writing code to catch security issues early.", {
@@ -43,7 +47,7 @@ server.tool("check_code", "Analyze code for security vulnerabilities (OWASP Top
43
47
  .describe("Framework context (e.g. express, nextjs, fastapi, react, django)"),
44
48
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (human) or json (machine-readable for agents)"),
45
49
  }, async ({ code, language, framework, format }) => {
46
- const rules = globalThis.__guardvibe_rules;
50
+ const rules = getRules();
47
51
  const results = checkCode(code, language, framework, undefined, undefined, format, rules);
48
52
  return {
49
53
  content: [{ type: "text", text: results }],
@@ -59,7 +63,7 @@ server.tool("check_project", "Scan multiple files for security vulnerabilities a
59
63
  .describe("List of files to scan: [{path, content}]"),
60
64
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (human) or json (machine-readable for agents)"),
61
65
  }, async ({ files, format }) => {
62
- const rules = globalThis.__guardvibe_rules;
66
+ const rules = getRules();
63
67
  const results = checkProject(files, format, rules);
64
68
  return {
65
69
  content: [{ type: "text", text: results }],
@@ -111,7 +115,7 @@ server.tool("scan_directory", "Scan an entire project directory for security vul
111
115
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (human) or json (machine-readable for agents)"),
112
116
  baseline: z.string().optional().describe("Path to a previous scan JSON output file for baseline comparison (new/fixed/unchanged findings)"),
113
117
  }, async ({ path, recursive, exclude, format, baseline }) => {
114
- const rules = globalThis.__guardvibe_rules;
118
+ const rules = getRules();
115
119
  const results = scanDirectory(path, recursive, exclude, format, rules, baseline);
116
120
  return { content: [{ type: "text", text: results }] };
117
121
  });
@@ -136,7 +140,7 @@ server.tool("scan_secrets", "Scan files and directories for leaked secrets, API
136
140
  server.tool("scan_staged", "Scan git-staged files for security vulnerabilities before committing. Run this before every commit to catch issues early. No input needed — automatically reads staged files.", {
137
141
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (human) or json (machine-readable for agents)"),
138
142
  }, async ({ format }) => {
139
- const rules = globalThis.__guardvibe_rules;
143
+ const rules = getRules();
140
144
  const results = scanStaged(process.cwd(), format, rules);
141
145
  return { content: [{ type: "text", text: results }] };
142
146
  });
@@ -147,7 +151,7 @@ server.tool("compliance_report", "Generate a compliance-focused security report
147
151
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (human) or json (machine-readable for agents)"),
148
152
  mode: z.enum(["full", "executive"]).default("full").describe("Report mode: full (detailed) or executive (C-level summary)"),
149
153
  }, async ({ path, framework, format, mode }) => {
150
- const rules = globalThis.__guardvibe_rules;
154
+ const rules = getRules();
151
155
  const results = complianceReport(path, framework, format, rules, mode);
152
156
  return { content: [{ type: "text", text: results }] };
153
157
  });
@@ -155,7 +159,7 @@ server.tool("compliance_report", "Generate a compliance-focused security report
155
159
  server.tool("export_sarif", "Scan a directory and export results in SARIF v2.1.0 format for CI/CD integration (GitHub, GitLab, Azure DevOps). Returns JSON string.", {
156
160
  path: z.string().describe("Directory to scan"),
157
161
  }, async ({ path }) => {
158
- const rules = globalThis.__guardvibe_rules;
162
+ const rules = getRules();
159
163
  const results = exportSarif(path, rules);
160
164
  return { content: [{ type: "text", text: results }] };
161
165
  });
@@ -179,7 +183,7 @@ server.tool("fix_code", "Analyze code for security vulnerabilities and return fi
179
183
  .describe("Framework context (e.g. express, nextjs, fastapi, react, django)"),
180
184
  format: z.enum(["markdown", "json"]).default("json").describe("Output format: json (for agent auto-fix) or markdown (human review)"),
181
185
  }, async ({ code, language, framework, format }) => {
182
- const rules = globalThis.__guardvibe_rules;
186
+ const rules = getRules();
183
187
  const results = fixCode(code, language, framework, undefined, format, rules);
184
188
  return {
185
189
  content: [{ type: "text", text: results }],
@@ -209,7 +213,7 @@ server.tool("review_pr", "Review a pull request for security issues. Scans only
209
213
  diff_only: z.boolean().default(true).describe("Only report findings in changed lines (true) or all findings in changed files (false)"),
210
214
  fail_on: z.enum(["critical", "high", "medium", "low", "none"]).default("high").describe("Block PR if findings at this severity or above exist"),
211
215
  }, async ({ path, base, format, diff_only, fail_on }) => {
212
- const rules = globalThis.__guardvibe_rules;
216
+ const rules = getRules();
213
217
  const results = reviewPr(path, base, format, diff_only, fail_on, rules);
214
218
  return { content: [{ type: "text", text: results }] };
215
219
  });
@@ -227,7 +231,7 @@ server.tool("policy_check", "Check project against compliance policies defined i
227
231
  path: z.string().describe("Project root directory"),
228
232
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
229
233
  }, async ({ path, format }) => {
230
- const rules = globalThis.__guardvibe_rules;
234
+ const rules = getRules();
231
235
  const results = policyCheck(path, format, rules);
232
236
  return { content: [{ type: "text", text: results }] };
233
237
  });
@@ -280,7 +284,7 @@ server.tool("explain_remediation", "Deep explanation of a security finding: why
280
284
  code: z.string().optional().describe("Affected code snippet for context"),
281
285
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
282
286
  }, async ({ rule_id, code, format }) => {
283
- const rules = globalThis.__guardvibe_rules;
287
+ const rules = getRules();
284
288
  const results = explainRemediation(rule_id, code, format, rules);
285
289
  return { content: [{ type: "text", text: results }] };
286
290
  });
@@ -299,12 +303,18 @@ async function main() {
299
303
  // Register plugin tools
300
304
  for (const tool of plugins.tools) {
301
305
  server.tool(tool.name, tool.description, tool.schema, async (input) => {
302
- const result = await tool.handler(input);
303
- return { content: [{ type: "text", text: result }] };
306
+ try {
307
+ const result = await tool.handler(input);
308
+ return { content: [{ type: "text", text: result }] };
309
+ }
310
+ catch (err) {
311
+ const msg = err instanceof Error ? err.message : String(err);
312
+ return { content: [{ type: "text", text: `Plugin error (${tool.name}): ${msg}` }] };
313
+ }
304
314
  });
305
315
  }
306
316
  // Store merged rules for tool handlers
307
- globalThis.__guardvibe_rules = allRules;
317
+ setRules(allRules);
308
318
  const transport = new StdioServerTransport();
309
319
  await server.connect(transport);
310
320
  console.error("GuardVibe Security MCP server running on stdio");
@@ -1,59 +1,15 @@
1
- import { readdirSync, readFileSync, statSync } from "fs";
2
- import { join, extname, basename, resolve } from "path";
1
+ import { readFileSync, statSync } from "fs";
2
+ import { extname, basename, resolve } from "path";
3
3
  import { analyzeCode } from "./check-code.js";
4
4
  import { loadConfig } from "../utils/config.js";
5
- const EXTENSION_MAP = {
6
- ".js": "javascript", ".jsx": "javascript", ".mjs": "javascript", ".cjs": "javascript",
7
- ".ts": "typescript", ".tsx": "typescript", ".mts": "typescript", ".cts": "typescript",
8
- ".py": "python", ".go": "go", ".html": "html",
9
- ".sql": "sql", ".sh": "shell", ".bash": "shell",
10
- ".yml": "yaml", ".yaml": "yaml", ".tf": "terraform",
11
- ".toml": "toml", ".json": "json",
12
- };
13
- const CONFIG_FILE_MAP = {
14
- "vercel.json": "vercel-config",
15
- "next.config.js": "nextjs-config",
16
- "next.config.mjs": "nextjs-config",
17
- "next.config.ts": "nextjs-config",
18
- "docker-compose.yml": "docker-compose",
19
- "docker-compose.yaml": "docker-compose",
20
- "fly.toml": "fly-config",
21
- "render.yaml": "render-config",
22
- "netlify.toml": "netlify-config",
23
- };
24
- const DEFAULT_EXCLUDES = new Set([
25
- "node_modules", ".git", "build", "dist", "vendor", "__pycache__",
26
- ".next", ".nuxt", "coverage", ".turbo",
27
- ]);
28
- function walkDir(dir, excludes, results) {
29
- let entries;
30
- try {
31
- entries = readdirSync(dir, { withFileTypes: true });
32
- }
33
- catch {
34
- return;
35
- }
36
- for (const entry of entries) {
37
- if (excludes.has(entry.name))
38
- continue;
39
- const fullPath = join(dir, entry.name);
40
- if (entry.isDirectory()) {
41
- walkDir(fullPath, excludes, results);
42
- }
43
- else if (entry.isFile()) {
44
- const ext = extname(entry.name).toLowerCase();
45
- if (EXTENSION_MAP[ext] || entry.name.startsWith("Dockerfile") || CONFIG_FILE_MAP[entry.name]) {
46
- results.push(fullPath);
47
- }
48
- }
49
- }
50
- }
5
+ import { EXTENSION_MAP, CONFIG_FILE_MAP, DEFAULT_EXCLUDES } from "../utils/constants.js";
6
+ import { walkDirectory } from "../utils/walk-directory.js";
51
7
  export function complianceReport(path, framework, format = "markdown", rules, mode = "full") {
52
8
  const scanRoot = resolve(path);
53
9
  const config = loadConfig(scanRoot);
54
10
  const excludes = new Set([...DEFAULT_EXCLUDES, ...config.scan.exclude]);
55
11
  const filePaths = [];
56
- walkDir(scanRoot, excludes, filePaths);
12
+ walkDirectory(scanRoot, true, excludes, filePaths);
57
13
  // Scan all files
58
14
  const allFindings = [];
59
15
  for (const filePath of filePaths) {
@@ -1,42 +1,10 @@
1
- import { readdirSync, readFileSync, statSync } from "fs";
2
- import { join, extname, basename, resolve } from "path";
1
+ import { readFileSync, statSync } from "fs";
2
+ import { extname, basename, resolve } from "path";
3
3
  import { analyzeCode } from "./check-code.js";
4
4
  import { owaspRules } from "../data/rules/index.js";
5
5
  import { loadConfig } from "../utils/config.js";
6
- const EXTENSION_MAP = {
7
- ".js": "javascript", ".jsx": "javascript", ".mjs": "javascript", ".cjs": "javascript",
8
- ".ts": "typescript", ".tsx": "typescript", ".mts": "typescript", ".cts": "typescript",
9
- ".py": "python", ".go": "go", ".html": "html",
10
- ".sql": "sql", ".sh": "shell", ".bash": "shell",
11
- ".yml": "yaml", ".yaml": "yaml", ".tf": "terraform", ".json": "json",
12
- };
13
- const DEFAULT_EXCLUDES = new Set([
14
- "node_modules", ".git", "build", "dist", "vendor", "__pycache__",
15
- ".next", ".nuxt", "coverage", ".turbo",
16
- ]);
17
- function walkDir(dir, excludes, results) {
18
- let entries;
19
- try {
20
- entries = readdirSync(dir, { withFileTypes: true });
21
- }
22
- catch {
23
- return;
24
- }
25
- for (const entry of entries) {
26
- if (excludes.has(entry.name))
27
- continue;
28
- const fullPath = join(dir, entry.name);
29
- if (entry.isDirectory()) {
30
- walkDir(fullPath, excludes, results);
31
- }
32
- else if (entry.isFile()) {
33
- const ext = extname(entry.name).toLowerCase();
34
- if (EXTENSION_MAP[ext] || entry.name.startsWith("Dockerfile")) {
35
- results.push(fullPath);
36
- }
37
- }
38
- }
39
- }
6
+ import { EXTENSION_MAP, DEFAULT_EXCLUDES } from "../utils/constants.js";
7
+ import { walkDirectory } from "../utils/walk-directory.js";
40
8
  function severityToLevel(severity) {
41
9
  if (severity === "critical" || severity === "high")
42
10
  return "error";
@@ -49,7 +17,7 @@ export function exportSarif(path, rules) {
49
17
  const config = loadConfig(scanRoot);
50
18
  const excludes = new Set([...DEFAULT_EXCLUDES, ...config.scan.exclude]);
51
19
  const filePaths = [];
52
- walkDir(scanRoot, excludes, filePaths);
20
+ walkDirectory(scanRoot, true, excludes, filePaths);
53
21
  const allResults = [];
54
22
  for (const filePath of filePaths) {
55
23
  try {
@@ -1,46 +1,9 @@
1
- import { readdirSync, readFileSync, statSync } from "fs";
2
- import { join, extname, basename, resolve } from "path";
1
+ import { readFileSync, statSync } from "fs";
2
+ import { extname, basename, resolve } from "path";
3
3
  import { analyzeCode } from "./check-code.js";
4
4
  import { loadConfig } from "../utils/config.js";
5
- const EXTENSION_MAP = {
6
- ".js": "javascript", ".jsx": "javascript", ".mjs": "javascript", ".cjs": "javascript",
7
- ".ts": "typescript", ".tsx": "typescript", ".mts": "typescript", ".cts": "typescript",
8
- ".py": "python", ".go": "go", ".html": "html",
9
- ".sql": "sql", ".sh": "shell", ".bash": "shell",
10
- ".yml": "yaml", ".yaml": "yaml", ".tf": "terraform",
11
- ".toml": "toml", ".json": "json",
12
- };
13
- const CONFIG_FILE_MAP = {
14
- "vercel.json": "vercel-config",
15
- "next.config.js": "nextjs-config", "next.config.mjs": "nextjs-config", "next.config.ts": "nextjs-config",
16
- "docker-compose.yml": "docker-compose", "docker-compose.yaml": "docker-compose",
17
- };
18
- const DEFAULT_EXCLUDES = new Set([
19
- "node_modules", ".git", "build", "dist", "vendor", "__pycache__",
20
- ".next", ".nuxt", "coverage", ".turbo",
21
- ]);
22
- function walkDir(dir, excludes, results) {
23
- let entries;
24
- try {
25
- entries = readdirSync(dir, { withFileTypes: true });
26
- }
27
- catch {
28
- return;
29
- }
30
- for (const entry of entries) {
31
- if (excludes.has(entry.name))
32
- continue;
33
- const fullPath = join(dir, entry.name);
34
- if (entry.isDirectory())
35
- walkDir(fullPath, excludes, results);
36
- else if (entry.isFile()) {
37
- const ext = extname(entry.name).toLowerCase();
38
- if (EXTENSION_MAP[ext] || entry.name.startsWith("Dockerfile") || CONFIG_FILE_MAP[entry.name]) {
39
- results.push(fullPath);
40
- }
41
- }
42
- }
43
- }
5
+ import { EXTENSION_MAP, CONFIG_FILE_MAP, DEFAULT_EXCLUDES } from "../utils/constants.js";
6
+ import { walkDirectory } from "../utils/walk-directory.js";
44
7
  function isExcepted(ruleId, filePath, exceptions) {
45
8
  for (const exc of exceptions) {
46
9
  if (exc.ruleId !== ruleId && exc.ruleId !== "*")
@@ -87,7 +50,7 @@ export function policyCheck(path, format = "markdown", rules) {
87
50
  }
88
51
  const excludes = new Set([...DEFAULT_EXCLUDES, ...config.scan.exclude]);
89
52
  const filePaths = [];
90
- walkDir(scanRoot, excludes, filePaths);
53
+ walkDirectory(scanRoot, true, excludes, filePaths);
91
54
  const policyFindings = [];
92
55
  const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
93
56
  const failLevel = severityOrder[policy.failOn] ?? 1;
@@ -1,19 +1,7 @@
1
1
  import { execFileSync } from "child_process";
2
2
  import { extname, basename } from "path";
3
3
  import { analyzeCode } from "./check-code.js";
4
- const EXTENSION_MAP = {
5
- ".js": "javascript", ".jsx": "javascript", ".mjs": "javascript", ".cjs": "javascript",
6
- ".ts": "typescript", ".tsx": "typescript", ".mts": "typescript", ".cts": "typescript",
7
- ".py": "python", ".go": "go", ".html": "html",
8
- ".sql": "sql", ".sh": "shell", ".bash": "shell",
9
- ".yml": "yaml", ".yaml": "yaml", ".tf": "terraform",
10
- ".toml": "toml", ".json": "json",
11
- };
12
- const CONFIG_FILE_MAP = {
13
- "vercel.json": "vercel-config",
14
- "next.config.js": "nextjs-config", "next.config.mjs": "nextjs-config", "next.config.ts": "nextjs-config",
15
- "docker-compose.yml": "docker-compose", "docker-compose.yaml": "docker-compose",
16
- };
4
+ import { EXTENSION_MAP, CONFIG_FILE_MAP } from "../utils/constants.js";
17
5
  function execGit(args, cwd) {
18
6
  try {
19
7
  return execFileSync("git", args, { cwd, encoding: "utf-8", timeout: 15000 });
@@ -1,64 +1,15 @@
1
- import { readdirSync, readFileSync, statSync } from "fs";
2
- import { join, extname, basename, resolve } from "path";
1
+ import { createRequire } from "module";
2
+ import { readFileSync, statSync } from "fs";
3
+ import { extname, basename, resolve } from "path";
3
4
  import { createHash, randomUUID } from "crypto";
4
5
  import { analyzeCode } from "./check-code.js";
5
6
  import { loadConfig } from "../utils/config.js";
6
- const DEFAULT_EXCLUDES = new Set([
7
- "node_modules", ".git", "build", "dist", "vendor", "__pycache__",
8
- ".next", ".nuxt", ".svelte-kit", "target", "bin", "obj",
9
- "coverage", ".turbo", ".venv", "venv",
10
- ]);
11
- const EXTENSION_MAP = {
12
- ".js": "javascript", ".jsx": "javascript", ".mjs": "javascript", ".cjs": "javascript",
13
- ".ts": "typescript", ".tsx": "typescript", ".mts": "typescript", ".cts": "typescript",
14
- ".py": "python", ".go": "go", ".html": "html",
15
- ".sql": "sql", ".sh": "shell", ".bash": "shell",
16
- ".yml": "yaml", ".yaml": "yaml",
17
- ".tf": "terraform",
18
- ".toml": "toml", ".json": "json",
19
- };
20
- const CONFIG_FILE_MAP = {
21
- "vercel.json": "vercel-config",
22
- "next.config.js": "nextjs-config",
23
- "next.config.mjs": "nextjs-config",
24
- "next.config.ts": "nextjs-config",
25
- "docker-compose.yml": "docker-compose",
26
- "docker-compose.yaml": "docker-compose",
27
- "fly.toml": "fly-config",
28
- "render.yaml": "render-config",
29
- "netlify.toml": "netlify-config",
30
- };
7
+ import { DEFAULT_EXCLUDES, EXTENSION_MAP, CONFIG_FILE_MAP } from "../utils/constants.js";
8
+ import { walkDirectory } from "../utils/walk-directory.js";
9
+ const require = createRequire(import.meta.url);
10
+ const pkg = require("../../package.json");
31
11
  // GuardVibe version — used in scan metadata
32
- const GUARDVIBE_VERSION = "1.4.0";
33
- function walkDirectory(dir, recursive, excludes, results) {
34
- let entries;
35
- try {
36
- entries = readdirSync(dir, { withFileTypes: true });
37
- }
38
- catch {
39
- return;
40
- }
41
- for (const entry of entries) {
42
- if (excludes.has(entry.name))
43
- continue;
44
- const fullPath = join(dir, entry.name);
45
- if (entry.isDirectory() && recursive) {
46
- walkDirectory(fullPath, recursive, excludes, results);
47
- }
48
- else if (entry.isFile()) {
49
- const ext = extname(entry.name).toLowerCase();
50
- if (EXTENSION_MAP[ext]) {
51
- results.push(fullPath);
52
- }
53
- if (entry.name.startsWith("Dockerfile") || entry.name.endsWith(".dockerfile")) {
54
- results.push(fullPath);
55
- }
56
- if (CONFIG_FILE_MAP[entry.name] && !results.includes(fullPath)) {
57
- results.push(fullPath);
58
- }
59
- }
60
- }
61
- }
12
+ const GUARDVIBE_VERSION = pkg.version;
62
13
  function hashContent(content) {
63
14
  return createHash("sha256").update(content).digest("hex").substring(0, 16);
64
15
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared constants used across GuardVibe tools.
3
+ * Single source of truth — all tool modules import from here.
4
+ */
5
+ /** Maps file extensions to language identifiers for security analysis. */
6
+ export declare const EXTENSION_MAP: Record<string, string>;
7
+ /** Maps well-known config filenames to their language/type identifier. */
8
+ export declare const CONFIG_FILE_MAP: Record<string, string>;
9
+ /** Directory names excluded from filesystem scans by default. */
10
+ export declare const DEFAULT_EXCLUDES: Set<string>;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Shared constants used across GuardVibe tools.
3
+ * Single source of truth — all tool modules import from here.
4
+ */
5
+ /** Maps file extensions to language identifiers for security analysis. */
6
+ export const EXTENSION_MAP = {
7
+ ".js": "javascript", ".jsx": "javascript", ".mjs": "javascript", ".cjs": "javascript",
8
+ ".ts": "typescript", ".tsx": "typescript", ".mts": "typescript", ".cts": "typescript",
9
+ ".py": "python", ".go": "go", ".html": "html",
10
+ ".sql": "sql", ".sh": "shell", ".bash": "shell",
11
+ ".yml": "yaml", ".yaml": "yaml",
12
+ ".tf": "terraform",
13
+ ".toml": "toml", ".json": "json",
14
+ };
15
+ /** Maps well-known config filenames to their language/type identifier. */
16
+ export const CONFIG_FILE_MAP = {
17
+ "vercel.json": "vercel-config",
18
+ "next.config.js": "nextjs-config",
19
+ "next.config.mjs": "nextjs-config",
20
+ "next.config.ts": "nextjs-config",
21
+ "docker-compose.yml": "docker-compose",
22
+ "docker-compose.yaml": "docker-compose",
23
+ "fly.toml": "fly-config",
24
+ "render.yaml": "render-config",
25
+ "netlify.toml": "netlify-config",
26
+ };
27
+ /** Directory names excluded from filesystem scans by default. */
28
+ export const DEFAULT_EXCLUDES = new Set([
29
+ "node_modules", ".git", "build", "dist", "vendor", "__pycache__",
30
+ ".next", ".nuxt", ".svelte-kit", "target", "bin", "obj",
31
+ "coverage", ".turbo", ".venv", "venv",
32
+ ]);
@@ -0,0 +1,3 @@
1
+ import type { SecurityRule } from "../data/rules/types.js";
2
+ export declare function setRules(rules: SecurityRule[]): void;
3
+ export declare function getRules(): SecurityRule[];
@@ -0,0 +1,7 @@
1
+ let registeredRules = [];
2
+ export function setRules(rules) {
3
+ registeredRules = rules;
4
+ }
5
+ export function getRules() {
6
+ return registeredRules;
7
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared recursive directory walker used by scan-directory, export-sarif,
3
+ * compliance-report, policy-check, and generate-policy tools.
4
+ */
5
+ /**
6
+ * Recursively walk a directory, collecting file paths that match
7
+ * known source extensions, Dockerfiles, or config file names.
8
+ *
9
+ * @param dir - Directory to walk
10
+ * @param recursive - Whether to descend into subdirectories
11
+ * @param excludes - Set of directory names to skip
12
+ * @param results - Accumulator array (mutated in place)
13
+ */
14
+ export declare function walkDirectory(dir: string, recursive: boolean, excludes: Set<string>, results: string[]): void;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Shared recursive directory walker used by scan-directory, export-sarif,
3
+ * compliance-report, policy-check, and generate-policy tools.
4
+ */
5
+ import { readdirSync } from "fs";
6
+ import { join, extname } from "path";
7
+ import { EXTENSION_MAP, CONFIG_FILE_MAP } from "./constants.js";
8
+ /**
9
+ * Recursively walk a directory, collecting file paths that match
10
+ * known source extensions, Dockerfiles, or config file names.
11
+ *
12
+ * @param dir - Directory to walk
13
+ * @param recursive - Whether to descend into subdirectories
14
+ * @param excludes - Set of directory names to skip
15
+ * @param results - Accumulator array (mutated in place)
16
+ */
17
+ export function walkDirectory(dir, recursive, excludes, results) {
18
+ let entries;
19
+ try {
20
+ entries = readdirSync(dir, { withFileTypes: true });
21
+ }
22
+ catch {
23
+ return;
24
+ }
25
+ for (const entry of entries) {
26
+ if (excludes.has(entry.name))
27
+ continue;
28
+ const fullPath = join(dir, entry.name);
29
+ if (entry.isDirectory() && recursive) {
30
+ walkDirectory(fullPath, recursive, excludes, results);
31
+ }
32
+ else if (entry.isFile()) {
33
+ const ext = extname(entry.name).toLowerCase();
34
+ if (EXTENSION_MAP[ext]) {
35
+ results.push(fullPath);
36
+ }
37
+ if (entry.name.startsWith("Dockerfile") || entry.name.endsWith(".dockerfile")) {
38
+ if (!results.includes(fullPath))
39
+ results.push(fullPath);
40
+ }
41
+ if (CONFIG_FILE_MAP[entry.name] && !results.includes(fullPath)) {
42
+ results.push(fullPath);
43
+ }
44
+ }
45
+ }
46
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "1.7.1",
4
- "description": "Security MCP for vibe coding. 277 rules, 14 tools for Next.js, Supabase, Clerk, Stripe, Prisma, tRPC, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK, and the full AI-generated stack.",
3
+ "version": "1.8.0",
4
+ "description": "Security MCP for vibe coding. 277 rules, 22 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": {
7
7
  "guardvibe": "build/index.js",