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 +109 -1
- package/build/cli.js +24 -7
- package/build/data/rules/cicd.js +4 -0
- package/build/data/rules/core.js +5 -1
- package/build/data/rules/deployment.js +2 -0
- package/build/data/rules/dockerfile.js +5 -0
- package/build/data/rules/go.js +6 -0
- package/build/index.js +24 -14
- package/build/tools/compliance-report.js +5 -49
- package/build/tools/export-sarif.js +5 -37
- package/build/tools/policy-check.js +5 -42
- package/build/tools/review-pr.js +1 -13
- package/build/tools/scan-directory.js +8 -57
- package/build/utils/constants.d.ts +10 -0
- package/build/utils/constants.js +32 -0
- package/build/utils/rule-registry.d.ts +3 -0
- package/build/utils/rule-registry.js +7 -0
- package/build/utils/walk-directory.d.ts +14 -0
- package/build/utils/walk-directory.js +46 -0
- package/package.json +2 -2
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 (
|
|
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
|
-
|
|
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);
|
package/build/data/rules/cicd.js
CHANGED
|
@@ -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",
|
package/build/data/rules/core.js
CHANGED
|
@@ -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: "
|
|
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",
|
package/build/data/rules/go.js
CHANGED
|
@@ -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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
303
|
-
|
|
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
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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;
|
package/build/tools/review-pr.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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 =
|
|
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,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.
|
|
4
|
-
"description": "Security MCP for vibe coding. 277 rules,
|
|
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",
|