guardvibe 1.7.0 → 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 +113 -5
- package/build/cli.js +24 -7
- package/build/data/rules/cicd.js +4 -0
- package/build/data/rules/core.js +41 -1
- package/build/data/rules/deployment.js +14 -0
- package/build/data/rules/dockerfile.js +5 -0
- package/build/data/rules/go.js +6 -0
- package/build/data/rules/modern-stack.js +48 -0
- package/build/data/rules/nextjs.js +12 -0
- package/build/data/rules/web-security.js +12 -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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GuardVibe
|
|
2
2
|
|
|
3
|
-
**The security MCP built for vibe coding.**
|
|
3
|
+
**The security MCP built for vibe coding.** 277 security rules covering the entire AI-generated code journey — from first line to production deployment.
|
|
4
4
|
|
|
5
5
|
Works with **Claude Code, Cursor, Gemini CLI, Codex, Windsurf**, and any MCP-compatible coding agent.
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ Works with **Claude Code, Cursor, Gemini CLI, Codex, Windsurf**, and any MCP-com
|
|
|
8
8
|
|
|
9
9
|
Most security tools are built for enterprise security teams. GuardVibe is built for **you** — the developer using AI to build and ship web apps fast.
|
|
10
10
|
|
|
11
|
-
- **
|
|
11
|
+
- **277 security rules** purpose-built for the stacks AI agents generate
|
|
12
12
|
- **Zero setup friction** — `npx guardvibe` and you're scanning
|
|
13
13
|
- **No account required** — runs 100% locally, no API keys, no cloud
|
|
14
14
|
- **Understands your stack** — not generic SAST, but rules that know Next.js, Supabase, Stripe, Clerk, and the tools you actually use
|
|
@@ -34,7 +34,7 @@ GuardVibe is purpose-built for the AI coding workflow. Traditional tools are exc
|
|
|
34
34
|
| CVE version detection | 21 packages | Extensive | Extensive |
|
|
35
35
|
| Compliance mapping (SOC2, PCI-DSS, HIPAA) | Built-in | Paid tier | None |
|
|
36
36
|
| SARIF CI/CD export | Yes | Yes | Limited |
|
|
37
|
-
| Rule count |
|
|
37
|
+
| Rule count | 277 (focused) | 5000+ (broad) | N/A |
|
|
38
38
|
|
|
39
39
|
**When to use GuardVibe:** You're building with AI agents and want security scanning integrated into your coding workflow — no dashboard, no account, no CI setup.
|
|
40
40
|
|
|
@@ -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,10 +143,20 @@ 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
|
|
|
149
|
-
## Security Rules (
|
|
159
|
+
## Security Rules (277 rules across 23 modules)
|
|
150
160
|
|
|
151
161
|
| Category | Rules | Coverage |
|
|
152
162
|
|----------|-------|----------|
|
|
@@ -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,
|
|
@@ -346,4 +350,40 @@ export const coreRules = [
|
|
|
346
350
|
fixCode: '// BAD: user controls the regex\nconst re = new RegExp(req.query.search);\n\n// GOOD: escape regex special chars\nfunction escapeRegex(s: string) {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&");\n}\nconst re = new RegExp(escapeRegex(req.query.search));\n\n// BETTER: use string methods\nconst results = items.filter(i => i.name.includes(query));',
|
|
347
351
|
compliance: ["SOC2:CC7.1"],
|
|
348
352
|
},
|
|
353
|
+
{
|
|
354
|
+
id: "VG108",
|
|
355
|
+
name: "Vue v-html Directive with User Data",
|
|
356
|
+
severity: "high",
|
|
357
|
+
owasp: "A07:2025 Cross-Site Scripting",
|
|
358
|
+
description: "Vue's v-html directive renders raw HTML without sanitization, equivalent to innerHTML. If user-controlled data is bound via v-html, attackers can inject arbitrary scripts for stored or reflected XSS.",
|
|
359
|
+
pattern: /v-html\s*=\s*["'](?!['"])\w/gi,
|
|
360
|
+
languages: ["html", "javascript", "typescript"],
|
|
361
|
+
fix: "Avoid v-html with user data. Use text interpolation {{ }} or sanitize with DOMPurify before rendering.",
|
|
362
|
+
fixCode: '<!-- BAD: raw HTML rendering -->\n<!-- <div v-html="userComment"></div> -->\n\n<!-- GOOD: text interpolation (auto-escaped) -->\n<div>{{ userComment }}</div>\n\n<!-- If HTML needed: sanitize first -->\n<div v-html="DOMPurify.sanitize(userComment)"></div>',
|
|
363
|
+
compliance: ["SOC2:CC7.1"],
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: "VG109",
|
|
367
|
+
name: "Angular innerHTML Binding with User Data",
|
|
368
|
+
severity: "high",
|
|
369
|
+
owasp: "A07:2025 Cross-Site Scripting",
|
|
370
|
+
description: "Angular's [innerHTML] property binding renders HTML content. While Angular's built-in sanitizer strips scripts, it can be bypassed with bypassSecurityTrustHtml() or via CSS/SVG-based XSS vectors.",
|
|
371
|
+
pattern: /(?:\[innerHTML\]\s*=\s*["']\w|bypassSecurityTrustHtml\s*\()/gi,
|
|
372
|
+
languages: ["html", "typescript"],
|
|
373
|
+
fix: "Avoid [innerHTML] with user data. If unavoidable, never use bypassSecurityTrustHtml() on user input.",
|
|
374
|
+
fixCode: '<!-- BAD: bypass Angular sanitizer -->\n<!-- <div [innerHTML]="trustedHtml"></div> -->\n<!-- this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml(userInput); -->\n\n<!-- GOOD: let Angular sanitize automatically -->\n<div [innerText]="userInput"></div>\n<!-- Or use Angular pipe with DOMPurify -->',
|
|
375
|
+
compliance: ["SOC2:CC7.1"],
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: "VG116",
|
|
379
|
+
name: "HTML Event Handler Injection via User Input",
|
|
380
|
+
severity: "high",
|
|
381
|
+
owasp: "A07:2025 Cross-Site Scripting",
|
|
382
|
+
description: "User input is interpolated into HTML attributes that accept JavaScript (onclick, onerror, onload, onmouseover, onfocus). Even without script tags, event handlers execute arbitrary JavaScript when the element is interacted with or loads.",
|
|
383
|
+
pattern: /(?:on(?:click|error|load|mouseover|focus|blur|submit|change|input|keyup|keydown))\s*=\s*(?:`[^`]*\$\{|["'][^"']*["']\s*\+\s*(?:user|input|query|param|req\.|data\.))/gi,
|
|
384
|
+
languages: ["javascript", "typescript", "html"],
|
|
385
|
+
fix: "Never interpolate user input into HTML event handler attributes. Use addEventListener with sanitized data instead.",
|
|
386
|
+
fixCode: '// BAD: user input in event handler\n// `<img onerror="${userInput}">`\n\n// GOOD: use addEventListener\nconst img = document.createElement("img");\nimg.addEventListener("error", () => handleError(sanitizedInput));',
|
|
387
|
+
compliance: ["SOC2:CC7.1"],
|
|
388
|
+
},
|
|
349
389
|
];
|
|
@@ -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",
|
|
@@ -230,4 +232,16 @@ export const deploymentRules = [
|
|
|
230
232
|
fixCode: '# BAD: breaks container isolation\n# hostNetwork: true\n# hostPID: true\n\n# GOOD: use pod networking\nspec:\n hostNetwork: false\n hostPID: false\n hostIPC: false\n containers:\n - name: app\n securityContext:\n runAsNonRoot: true',
|
|
231
233
|
compliance: ["SOC2:CC6.1", "PCI-DSS:Req6.5.10"],
|
|
232
234
|
},
|
|
235
|
+
{
|
|
236
|
+
id: "VG524",
|
|
237
|
+
name: "Data URL or Blob URL in User-Controlled src/href",
|
|
238
|
+
severity: "high",
|
|
239
|
+
owasp: "A07:2025 Cross-Site Scripting",
|
|
240
|
+
description: "User-controlled input is used in src or href attributes without blocking data: and blob: URL schemes. data:text/html URLs can embed full HTML pages with scripts, and javascript: URLs execute code — bypassing same-origin restrictions.",
|
|
241
|
+
pattern: /(?:src|href|action|poster|srcDoc)\s*=\s*\{[\s\S]{0,100}?(?:user|input|param|query|data|url|link|content)[\s\S]{0,100}?(?:(?!(?:startsWith|protocol|scheme|allowlist|whitelist|URL\()[\s\S]{0,50}?))\}/gi,
|
|
242
|
+
languages: ["javascript", "typescript"],
|
|
243
|
+
fix: "Validate URL scheme against an allowlist (https: only). Block data:, blob:, and javascript: URLs from user input.",
|
|
244
|
+
fixCode: '// Validate URL scheme\nfunction isSafeUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return ["https:", "http:"].includes(parsed.protocol);\n } catch {\n return false;\n }\n}\n\n// Usage\n<img src={isSafeUrl(userUrl) ? userUrl : "/placeholder.png"} />',
|
|
245
|
+
compliance: ["SOC2:CC7.1"],
|
|
246
|
+
},
|
|
233
247
|
];
|
|
@@ -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
|
];
|
|
@@ -434,4 +434,52 @@ export const modernStackRules = [
|
|
|
434
434
|
fixCode: '// Express: trust only your reverse proxy\napp.set("trust proxy", 1); // trust first proxy\n\n// Rate limiter: use req.ip (respects trust proxy)\nimport rateLimit from "express-rate-limit";\nconst limiter = rateLimit({\n keyGenerator: (req) => req.ip, // uses trusted proxy chain\n max: 100,\n windowMs: 15 * 60 * 1000,\n});',
|
|
435
435
|
compliance: ["SOC2:CC7.1"],
|
|
436
436
|
},
|
|
437
|
+
{
|
|
438
|
+
id: "VG990",
|
|
439
|
+
name: "SVG File Upload Without Content Sanitization",
|
|
440
|
+
severity: "critical",
|
|
441
|
+
owasp: "A07:2025 Cross-Site Scripting",
|
|
442
|
+
description: "File upload accepts SVG files but does not scan or sanitize SVG content. SVG files can contain embedded <script> tags, event handlers (onload, onclick), and external resource references that execute JavaScript when the SVG is rendered in a browser.",
|
|
443
|
+
pattern: /(?:(?:allowedMimeTypes|accept|mimeTypes|fileTypes|allowedTypes|contentType)\s*[:=]\s*(?:\[[\s\S]*?|['"`])[\s\S]{0,100}?(?:svg|image\/svg|\.svg))/gi,
|
|
444
|
+
languages: ["javascript", "typescript"],
|
|
445
|
+
fix: "Either reject SVG uploads entirely or sanitize SVG content by stripping script tags, event handlers, and external references. Use a library like DOMPurify with SVG profile.",
|
|
446
|
+
fixCode: '// Option 1: Reject SVGs\nconst ALLOWED_TYPES = ["image/png", "image/jpeg", "image/webp"]; // no SVG\n\n// Option 2: Sanitize SVG content\nimport DOMPurify from "dompurify";\nconst cleanSvg = DOMPurify.sanitize(svgContent, {\n USE_PROFILES: { svg: true, svgFilters: true },\n FORBID_TAGS: ["script", "foreignObject"],\n FORBID_ATTR: ["onclick", "onerror", "onload"],\n});',
|
|
447
|
+
compliance: ["SOC2:CC7.1"],
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
id: "VG991",
|
|
451
|
+
name: "Markdown Rendered as HTML Without Sanitization",
|
|
452
|
+
severity: "high",
|
|
453
|
+
owasp: "A07:2025 Cross-Site Scripting",
|
|
454
|
+
description: "Markdown library output (marked, showdown, markdown-it, remark) is rendered as HTML without sanitization. Most markdown parsers allow raw HTML by default — user-submitted markdown like `<img onerror=alert(1)>` passes through as executable HTML.",
|
|
455
|
+
pattern: /(?:marked|showdown|markdownIt|markdown-it|unified|remark|rehype)[\s\S]{0,300}?(?:innerHTML|dangerouslySetInnerHTML|v-html|\[innerHTML\]|\.html\s*\(|res\.send)/gi,
|
|
456
|
+
languages: ["javascript", "typescript"],
|
|
457
|
+
fix: "Sanitize markdown HTML output with DOMPurify before rendering, or configure the parser to disable raw HTML.",
|
|
458
|
+
fixCode: '// BAD: unsanitized markdown\n// element.innerHTML = marked.parse(userMarkdown);\n\n// GOOD: sanitize after parsing\nimport DOMPurify from "dompurify";\nimport { marked } from "marked";\nconst html = DOMPurify.sanitize(marked.parse(userMarkdown));\n\n// Or disable HTML in parser\nmarked.setOptions({ sanitize: true });\n// markdown-it: const md = markdownIt({ html: false });',
|
|
459
|
+
compliance: ["SOC2:CC7.1"],
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
id: "VG992",
|
|
463
|
+
name: "Rich Text Editor Output Without Sanitization",
|
|
464
|
+
severity: "high",
|
|
465
|
+
owasp: "A07:2025 Cross-Site Scripting",
|
|
466
|
+
description: "WYSIWYG/rich text editor content (TipTap, Draft.js, Slate, Quill, CKEditor, TinyMCE) is rendered via innerHTML or dangerouslySetInnerHTML without sanitization. Editor output is user-controlled HTML that can contain XSS payloads — especially if the editor allows source code editing or paste from external sources.",
|
|
467
|
+
pattern: /(?:editor\.getHTML|getContent|convertToHTML|stateToHTML|serialize|draftToHtml|renderToString)[\s\S]{0,300}?(?:innerHTML|dangerouslySetInnerHTML|v-html|\[innerHTML\])/gi,
|
|
468
|
+
languages: ["javascript", "typescript"],
|
|
469
|
+
fix: "Always sanitize rich text editor output with DOMPurify before rendering, even if the editor has its own sanitization.",
|
|
470
|
+
fixCode: '// BAD: direct editor output rendering\n// <div dangerouslySetInnerHTML={{ __html: editor.getHTML() }} />\n\n// GOOD: sanitize editor output\nimport DOMPurify from "dompurify";\nconst cleanHtml = DOMPurify.sanitize(editor.getHTML(), {\n ALLOWED_TAGS: ["p", "b", "i", "em", "strong", "a", "ul", "ol", "li", "br", "h1", "h2", "h3"],\n ALLOWED_ATTR: ["href", "target", "rel"],\n});\n<div dangerouslySetInnerHTML={{ __html: cleanHtml }} />',
|
|
471
|
+
compliance: ["SOC2:CC7.1"],
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
id: "VG993",
|
|
475
|
+
name: "Upload Filename Used Without Sanitization",
|
|
476
|
+
severity: "high",
|
|
477
|
+
owasp: "A01:2025 Broken Access Control",
|
|
478
|
+
description: "User-uploaded file's original filename is used directly for storage without sanitization. Attackers can use directory traversal (../../etc/passwd), null bytes (file.php%00.jpg), double extensions (file.jpg.exe), or Unicode tricks to overwrite files, bypass type checks, or achieve remote code execution.",
|
|
479
|
+
pattern: /(?:file\.name|originalname|filename|req\.file\.originalname|formData\.get\s*\(\s*['"]file['"])\s*[\s\S]{0,100}?(?:writeFile|createWriteStream|save|upload|putObject|mv\s*\(|rename|storage)/gi,
|
|
480
|
+
languages: ["javascript", "typescript"],
|
|
481
|
+
fix: "Generate a random filename (UUID/nanoid) and validate the extension against an allowlist. Never use the original filename for storage.",
|
|
482
|
+
fixCode: 'import { randomUUID } from "crypto";\nimport path from "path";\n\n// Generate safe filename\nconst ext = path.extname(file.name).toLowerCase();\nconst ALLOWED_EXT = [".jpg", ".jpeg", ".png", ".webp", ".pdf"];\nif (!ALLOWED_EXT.includes(ext)) throw new Error("Invalid file type");\nconst safeName = `${randomUUID()}${ext}`;\nawait fs.writeFile(`/uploads/${safeName}`, buffer);',
|
|
483
|
+
compliance: ["SOC2:CC7.1"],
|
|
484
|
+
},
|
|
437
485
|
];
|
|
@@ -168,4 +168,16 @@ export const nextjsRules = [
|
|
|
168
168
|
fixCode: '// next.config.ts\nexport default {\n serverActions: {\n allowedOrigins: ["myapp.com", "*.myapp.com"],\n },\n};',
|
|
169
169
|
compliance: ["SOC2:CC6.6"],
|
|
170
170
|
},
|
|
171
|
+
{
|
|
172
|
+
id: "VG414",
|
|
173
|
+
name: "Server-Side Template Injection (SSTI)",
|
|
174
|
+
severity: "critical",
|
|
175
|
+
owasp: "A02:2025 Injection",
|
|
176
|
+
description: "User input is rendered using an unescaped template directive (EJS <%- %>, Handlebars {{{ }}}, Pug != operator, Nunjucks | safe filter). These directives bypass HTML escaping, allowing attackers to inject arbitrary HTML and JavaScript that executes server-side or client-side.",
|
|
177
|
+
pattern: /(?:<%-\s*\w|(?:\{\{\{)\s*\w|!=\s*\w[\s\S]{0,50}?(?:user|input|query|body|param|req\.|data\.)|\|\s*safe\s*(?:\}\}|\%\}))/gi,
|
|
178
|
+
languages: ["javascript", "typescript", "html"],
|
|
179
|
+
fix: "Always use escaped template directives: EJS <%= %>, Handlebars {{ }}, Pug =. Only use unescaped rendering for trusted, developer-controlled content.",
|
|
180
|
+
fixCode: '<!-- EJS: use escaped output -->\n<p><%= userInput %></p> <!-- SAFE: HTML-escaped -->\n<!-- NOT: <%- userInput %> DANGEROUS: raw HTML -->\n\n<!-- Handlebars: use double braces -->\n<p>{{userInput}}</p> <!-- SAFE: escaped -->\n<!-- NOT: {{{userInput}}} DANGEROUS: raw HTML -->\n\n<!-- Pug: use = not != -->\np= userInput //- SAFE: escaped\n//- NOT: p!= userInput DANGEROUS: raw HTML',
|
|
181
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
182
|
+
},
|
|
171
183
|
];
|
|
@@ -173,4 +173,16 @@ export const webSecurityRules = [
|
|
|
173
173
|
fixCode: "// Reads from OPENAI_API_KEY env automatically\nconst openai = new OpenAI();",
|
|
174
174
|
compliance: ["SOC2:CC6.1"],
|
|
175
175
|
},
|
|
176
|
+
{
|
|
177
|
+
id: "VG678",
|
|
178
|
+
name: "Missing X-Content-Type-Options Header",
|
|
179
|
+
severity: "high",
|
|
180
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
181
|
+
description: "Response serving user-uploaded files does not set X-Content-Type-Options: nosniff. Browsers may MIME-sniff the content and execute uploaded files as HTML/JavaScript, enabling stored XSS via file uploads.",
|
|
182
|
+
pattern: /(?:createReadStream|sendFile|send\s*\(|pipe\s*\(|res\.download|res\.sendFile|getSignedUrl|getPublicUrl)[\s\S]{0,500}?(?:(?!X-Content-Type-Options|nosniff)[\s\S]){10,}?(?:res\.end|\.pipe|return|response)/gi,
|
|
183
|
+
languages: ["javascript", "typescript"],
|
|
184
|
+
fix: "Set X-Content-Type-Options: nosniff on all responses serving user-uploaded content.",
|
|
185
|
+
fixCode: '// Set nosniff header for uploaded file responses\nres.setHeader("X-Content-Type-Options", "nosniff");\nres.setHeader("Content-Disposition", "attachment"); // force download for unknown types\nres.sendFile(filePath);',
|
|
186
|
+
compliance: ["SOC2:CC6.1"],
|
|
187
|
+
},
|
|
176
188
|
];
|
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.
|
|
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",
|