@indicated/vibeguard 1.0.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/.claude/settings.local.json +5 -0
- package/.github/workflows/ci.yml +65 -0
- package/.github/workflows/release.yml +85 -0
- package/PROGRESS.md +192 -0
- package/README.md +183 -0
- package/dist/api/license.d.ts +13 -0
- package/dist/api/license.d.ts.map +1 -0
- package/dist/api/license.js +138 -0
- package/dist/api/license.js.map +1 -0
- package/dist/api/rules.d.ts +13 -0
- package/dist/api/rules.d.ts.map +1 -0
- package/dist/api/rules.js +57 -0
- package/dist/api/rules.js.map +1 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +145 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/login.d.ts +4 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +121 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +3 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +14 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/rules.d.ts +3 -0
- package/dist/cli/commands/rules.d.ts.map +1 -0
- package/dist/cli/commands/rules.js +52 -0
- package/dist/cli/commands/rules.js.map +1 -0
- package/dist/cli/commands/scan.d.ts +3 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/scan.js +114 -0
- package/dist/cli/commands/scan.js.map +1 -0
- package/dist/cli/config.d.ts +4 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +88 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +25 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/output.d.ts +15 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +152 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +188 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/scanner/index.d.ts +15 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +207 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/parsers/javascript.d.ts +12 -0
- package/dist/scanner/parsers/javascript.d.ts.map +1 -0
- package/dist/scanner/parsers/javascript.js +266 -0
- package/dist/scanner/parsers/javascript.js.map +1 -0
- package/dist/scanner/parsers/python.d.ts +3 -0
- package/dist/scanner/parsers/python.d.ts.map +1 -0
- package/dist/scanner/parsers/python.js +108 -0
- package/dist/scanner/parsers/python.js.map +1 -0
- package/dist/scanner/rules/definitions.d.ts +5 -0
- package/dist/scanner/rules/definitions.d.ts.map +1 -0
- package/dist/scanner/rules/definitions.js +584 -0
- package/dist/scanner/rules/definitions.js.map +1 -0
- package/dist/scanner/rules/loader.d.ts +8 -0
- package/dist/scanner/rules/loader.d.ts.map +1 -0
- package/dist/scanner/rules/loader.js +45 -0
- package/dist/scanner/rules/loader.js.map +1 -0
- package/dist/scanner/rules/matcher.d.ts +11 -0
- package/dist/scanner/rules/matcher.d.ts.map +1 -0
- package/dist/scanner/rules/matcher.js +53 -0
- package/dist/scanner/rules/matcher.js.map +1 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
- package/src/api/license.ts +120 -0
- package/src/api/rules.ts +70 -0
- package/src/cli/commands/init.ts +123 -0
- package/src/cli/commands/login.ts +92 -0
- package/src/cli/commands/mcp.ts +12 -0
- package/src/cli/commands/rules.ts +58 -0
- package/src/cli/commands/scan.ts +94 -0
- package/src/cli/config.ts +54 -0
- package/src/cli/index.ts +28 -0
- package/src/cli/output.ts +159 -0
- package/src/mcp/server.ts +195 -0
- package/src/scanner/index.ts +195 -0
- package/src/scanner/parsers/javascript.ts +285 -0
- package/src/scanner/parsers/python.ts +126 -0
- package/src/scanner/rules/definitions.ts +592 -0
- package/src/scanner/rules/loader.ts +59 -0
- package/src/scanner/rules/matcher.ts +68 -0
- package/src/types.ts +36 -0
- package/test-samples/secure.js +52 -0
- package/test-samples/vulnerable.js +56 -0
- package/test-samples/vulnerable.py +39 -0
- package/tests/helpers.ts +43 -0
- package/tests/rules/critical.test.ts +186 -0
- package/tests/rules/definitions.test.ts +167 -0
- package/tests/rules/high.test.ts +377 -0
- package/tests/rules/low.test.ts +172 -0
- package/tests/rules/medium.test.ts +224 -0
- package/tests/scanner/scanner.test.ts +161 -0
- package/tsconfig.json +19 -0
- package/vibe-coding-security-checklist.md +245 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test (Node ${{ matrix.node-version }})
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
node-version: [18, 20, 22]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout code
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Setup Node.js ${{ matrix.node-version }}
|
|
22
|
+
uses: actions/setup-node@v4
|
|
23
|
+
with:
|
|
24
|
+
node-version: ${{ matrix.node-version }}
|
|
25
|
+
cache: 'npm'
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: npm ci
|
|
29
|
+
|
|
30
|
+
- name: Build
|
|
31
|
+
run: npm run build
|
|
32
|
+
|
|
33
|
+
- name: Run tests
|
|
34
|
+
run: npm test
|
|
35
|
+
|
|
36
|
+
- name: Run tests with coverage
|
|
37
|
+
if: matrix.node-version == 20
|
|
38
|
+
run: npm run test:coverage
|
|
39
|
+
|
|
40
|
+
- name: Upload coverage report
|
|
41
|
+
if: matrix.node-version == 20
|
|
42
|
+
uses: actions/upload-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: coverage-report
|
|
45
|
+
path: coverage/
|
|
46
|
+
retention-days: 7
|
|
47
|
+
|
|
48
|
+
lint:
|
|
49
|
+
name: Lint & Type Check
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
steps:
|
|
52
|
+
- name: Checkout code
|
|
53
|
+
uses: actions/checkout@v4
|
|
54
|
+
|
|
55
|
+
- name: Setup Node.js
|
|
56
|
+
uses: actions/setup-node@v4
|
|
57
|
+
with:
|
|
58
|
+
node-version: 20
|
|
59
|
+
cache: 'npm'
|
|
60
|
+
|
|
61
|
+
- name: Install dependencies
|
|
62
|
+
run: npm ci
|
|
63
|
+
|
|
64
|
+
- name: Type check
|
|
65
|
+
run: npm run build
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
name: Test before release
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout code
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Setup Node.js
|
|
17
|
+
uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: 20
|
|
20
|
+
cache: 'npm'
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: npm ci
|
|
24
|
+
|
|
25
|
+
- name: Build
|
|
26
|
+
run: npm run build
|
|
27
|
+
|
|
28
|
+
- name: Run tests
|
|
29
|
+
run: npm test
|
|
30
|
+
|
|
31
|
+
publish:
|
|
32
|
+
name: Publish to npm
|
|
33
|
+
needs: test
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
permissions:
|
|
36
|
+
contents: read
|
|
37
|
+
id-token: write
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
- name: Checkout code
|
|
41
|
+
uses: actions/checkout@v4
|
|
42
|
+
|
|
43
|
+
- name: Setup Node.js
|
|
44
|
+
uses: actions/setup-node@v4
|
|
45
|
+
with:
|
|
46
|
+
node-version: 20
|
|
47
|
+
cache: 'npm'
|
|
48
|
+
registry-url: 'https://registry.npmjs.org'
|
|
49
|
+
|
|
50
|
+
- name: Install dependencies
|
|
51
|
+
run: npm ci
|
|
52
|
+
|
|
53
|
+
- name: Build
|
|
54
|
+
run: npm run build
|
|
55
|
+
|
|
56
|
+
- name: Publish to npm
|
|
57
|
+
run: npm publish --provenance --access public
|
|
58
|
+
env:
|
|
59
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
60
|
+
|
|
61
|
+
github-release:
|
|
62
|
+
name: Create GitHub Release
|
|
63
|
+
needs: publish
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
permissions:
|
|
66
|
+
contents: write
|
|
67
|
+
|
|
68
|
+
steps:
|
|
69
|
+
- name: Checkout code
|
|
70
|
+
uses: actions/checkout@v4
|
|
71
|
+
|
|
72
|
+
- name: Create GitHub Release
|
|
73
|
+
uses: softprops/action-gh-release@v1
|
|
74
|
+
with:
|
|
75
|
+
generate_release_notes: true
|
|
76
|
+
body: |
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm install -g vibeguard
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## What's Changed
|
|
84
|
+
|
|
85
|
+
See the automatically generated release notes below.
|
package/PROGRESS.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# VibeGuard Development Progress
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
VibeGuard is a local CLI security scanner for AI-generated code. This document tracks what has been implemented and what remains to be done.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ✅ Completed
|
|
10
|
+
|
|
11
|
+
### Phase 1: Core Scanner
|
|
12
|
+
- [x] TypeScript project setup with npm package structure
|
|
13
|
+
- [x] Project structure following the planned architecture
|
|
14
|
+
- [x] JS/TS parser using @babel/parser for AST analysis
|
|
15
|
+
- [x] Pattern-based scanning with regex
|
|
16
|
+
- [x] Basic `scan` command with file/directory support
|
|
17
|
+
- [x] Finding output with file, line number, and severity
|
|
18
|
+
|
|
19
|
+
### Phase 2: Full Rule Set
|
|
20
|
+
- [x] 45 security rules implemented across all severity levels:
|
|
21
|
+
- **Critical (8):** hardcoded-secret, sql-injection, eval-usage, command-injection, insecure-deserialization, django-debug-true, django-secret-key-exposed, django-raw-sql
|
|
22
|
+
- **High (21):** missing-auth-route, xss-innerhtml, secrets-localstorage, supabase-no-rls, firebase-no-rules, idor-vulnerability, path-traversal, ssrf-vulnerability, open-redirect, insecure-cookie, missing-csrf, nextjs-exposed-server-action, nextjs-api-route-no-auth, nextjs-dangerouslySetInnerHTML, nextjs-exposed-env, django-no-csrf-exempt, fastapi-no-auth-dependency, nestjs-no-auth-guard, react-href-javascript, react-url-state-injection, express-session-insecure
|
|
23
|
+
- **Medium (10):** permissive-cors, http-not-https, weak-password, hardcoded-ip, xxe-vulnerability, jwt-none-algorithm, django-allowed-hosts-all, fastapi-cors-all-origins, express-helmet-missing, express-body-parser-limit
|
|
24
|
+
- **Low (6):** verbose-errors, missing-rate-limit, console-log-sensitive, debug-mode-enabled, prototype-pollution, nestjs-exposed-internal-exception
|
|
25
|
+
- [x] Python parser support (pattern-based)
|
|
26
|
+
- [x] Severity levels (critical, high, medium, low)
|
|
27
|
+
- [x] Letter grading system (A+ to F)
|
|
28
|
+
|
|
29
|
+
### Phase 3: CLI Polish
|
|
30
|
+
- [x] `vibeguard scan` - Main scan command
|
|
31
|
+
- [x] Directory scanning
|
|
32
|
+
- [x] File scanning
|
|
33
|
+
- [x] `--staged` flag for git staged files
|
|
34
|
+
- [x] `--json` output format
|
|
35
|
+
- [x] `--force` to ignore exit code
|
|
36
|
+
- [x] `--quiet` minimal output
|
|
37
|
+
- [x] `vibeguard init` - Pre-commit hook setup
|
|
38
|
+
- [x] Git hook creation
|
|
39
|
+
- [x] Husky detection and integration
|
|
40
|
+
- [x] `.vibeguardrc.json` config file creation
|
|
41
|
+
- [x] `vibeguard rules` - List security rules
|
|
42
|
+
- [x] `--severity` filter
|
|
43
|
+
- [x] `--language` filter
|
|
44
|
+
- [x] `--json` output
|
|
45
|
+
- [x] `vibeguard login` / `vibeguard logout` - License key management
|
|
46
|
+
- [x] Pretty terminal output with colors
|
|
47
|
+
- [x] Config file support (`.vibeguardrc.json`)
|
|
48
|
+
|
|
49
|
+
### Phase 4: License System (CLI Side)
|
|
50
|
+
- [x] `login` command implemented
|
|
51
|
+
- [x] `logout` command implemented
|
|
52
|
+
- [x] License key storage in `~/.vibeguard/license.json`
|
|
53
|
+
- [x] Offline mode fallback (works without server)
|
|
54
|
+
- [x] API client stubs ready for server integration
|
|
55
|
+
|
|
56
|
+
### Bonus: MCP Integration
|
|
57
|
+
- [x] `vibeguard mcp` command to start MCP server
|
|
58
|
+
- [x] `scan_code` tool - Scan files/directories
|
|
59
|
+
- [x] `list_security_rules` tool - List available rules
|
|
60
|
+
- [x] `check_code_snippet` tool - Validate code without saving
|
|
61
|
+
- [x] Documentation for Claude Code integration
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 🔲 Not Yet Implemented
|
|
66
|
+
|
|
67
|
+
### Server-Side (Required for SaaS)
|
|
68
|
+
- [ ] License validation API (`POST /v1/license/validate`)
|
|
69
|
+
- [ ] License activation API (`POST /v1/license/activate`)
|
|
70
|
+
- [ ] Rules API (`GET /v1/rules`) for auto-updates
|
|
71
|
+
- [ ] Usage metrics API (`POST /v1/metrics`)
|
|
72
|
+
- [ ] Database for license keys and usage tracking
|
|
73
|
+
- [ ] Payment integration (Stripe, etc.)
|
|
74
|
+
|
|
75
|
+
### CLI Enhancements
|
|
76
|
+
- [ ] `vibeguard update` - Manual rule update command
|
|
77
|
+
- [ ] `vibeguard ignore` - Add inline ignore comments
|
|
78
|
+
- [ ] `vibeguard fix` - Auto-fix certain issues (where safe)
|
|
79
|
+
- [ ] Watch mode (`--watch`) for continuous scanning
|
|
80
|
+
- [ ] SARIF output format for GitHub Advanced Security
|
|
81
|
+
- [ ] GitLab CI/CD integration format
|
|
82
|
+
|
|
83
|
+
### Additional Rules
|
|
84
|
+
- [x] Command injection detection
|
|
85
|
+
- [x] Path traversal detection
|
|
86
|
+
- [x] Insecure deserialization
|
|
87
|
+
- [x] Hardcoded IP addresses
|
|
88
|
+
- [x] Missing CSRF protection
|
|
89
|
+
- [x] Insecure cookie settings
|
|
90
|
+
- [x] Open redirect vulnerabilities
|
|
91
|
+
- [x] XXE (XML External Entity) detection
|
|
92
|
+
- [x] SSRF (Server-Side Request Forgery) patterns
|
|
93
|
+
- [x] JWT none algorithm vulnerability
|
|
94
|
+
- [x] Console logging sensitive data
|
|
95
|
+
- [x] Debug mode enabled
|
|
96
|
+
- [x] Prototype pollution
|
|
97
|
+
|
|
98
|
+
### Parser Improvements
|
|
99
|
+
- [ ] Tree-sitter Python parser (currently regex-only)
|
|
100
|
+
- [ ] Better AST-based SQL injection detection for JS
|
|
101
|
+
- [x] Framework-specific rules (Next.js, Django, FastAPI, NestJS, Express, React)
|
|
102
|
+
- [ ] JSX/TSX component-level analysis
|
|
103
|
+
|
|
104
|
+
### Testing
|
|
105
|
+
- [x] Unit tests for scanner (13 tests)
|
|
106
|
+
- [x] Unit tests for rule matching (186 tests across all 45 rules)
|
|
107
|
+
- [x] Rule definitions tests (47 tests)
|
|
108
|
+
- [x] Test coverage reporting (Vitest + v8)
|
|
109
|
+
- [x] CI/CD pipeline (GitHub Actions)
|
|
110
|
+
- [x] CI workflow: tests on Node 18, 20, 22
|
|
111
|
+
- [x] Release workflow: auto-publish to npm on tags
|
|
112
|
+
- [ ] Integration tests for CLI commands
|
|
113
|
+
|
|
114
|
+
### Documentation
|
|
115
|
+
- [ ] API documentation for server endpoints
|
|
116
|
+
- [ ] Contributing guide
|
|
117
|
+
- [ ] Rule authoring guide
|
|
118
|
+
- [ ] Examples for each vulnerability type
|
|
119
|
+
|
|
120
|
+
### Distribution
|
|
121
|
+
- [ ] Publish to npm
|
|
122
|
+
- [ ] Homebrew formula
|
|
123
|
+
- [ ] Binary releases (pkg)
|
|
124
|
+
- [ ] Docker image
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 📁 Current Project Structure
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
vibeguard/
|
|
132
|
+
├── src/
|
|
133
|
+
│ ├── cli/
|
|
134
|
+
│ │ ├── index.ts # CLI entry point
|
|
135
|
+
│ │ ├── config.ts # Config file loading
|
|
136
|
+
│ │ ├── output.ts # Terminal formatting
|
|
137
|
+
│ │ └── commands/
|
|
138
|
+
│ │ ├── scan.ts # scan command
|
|
139
|
+
│ │ ├── init.ts # init command
|
|
140
|
+
│ │ ├── login.ts # login/logout commands
|
|
141
|
+
│ │ ├── rules.ts # rules command
|
|
142
|
+
│ │ └── mcp.ts # mcp command
|
|
143
|
+
│ ├── scanner/
|
|
144
|
+
│ │ ├── index.ts # Main scanner logic
|
|
145
|
+
│ │ ├── parsers/
|
|
146
|
+
│ │ │ ├── javascript.ts # JS/TS AST + pattern scanning
|
|
147
|
+
│ │ │ └── python.ts # Python pattern scanning
|
|
148
|
+
│ │ └── rules/
|
|
149
|
+
│ │ ├── definitions.ts # 14 security rules
|
|
150
|
+
│ │ ├── loader.ts # Rule loading (local + API)
|
|
151
|
+
│ │ └── matcher.ts # Pattern matching utilities
|
|
152
|
+
│ ├── mcp/
|
|
153
|
+
│ │ └── server.ts # MCP server for AI assistants
|
|
154
|
+
│ ├── api/
|
|
155
|
+
│ │ ├── license.ts # License management
|
|
156
|
+
│ │ └── rules.ts # Rules API client
|
|
157
|
+
│ └── types.ts # TypeScript types
|
|
158
|
+
├── dist/ # Compiled JavaScript
|
|
159
|
+
├── test-samples/ # Test files with vulnerabilities
|
|
160
|
+
├── package.json
|
|
161
|
+
├── tsconfig.json
|
|
162
|
+
├── README.md
|
|
163
|
+
└── PROGRESS.md # This file
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 🚀 Next Steps (Recommended Order)
|
|
169
|
+
|
|
170
|
+
1. **Testing** - Add unit tests before making more changes
|
|
171
|
+
2. **More Rules** - Expand rule coverage based on user feedback
|
|
172
|
+
3. **npm Publish** - Get it in users' hands
|
|
173
|
+
4. **Server API** - Build license/rules server for SaaS model
|
|
174
|
+
5. **CI Integration** - SARIF output for GitHub/GitLab
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 📊 Stats
|
|
179
|
+
|
|
180
|
+
| Metric | Count |
|
|
181
|
+
|--------|-------|
|
|
182
|
+
| Security Rules | 45 |
|
|
183
|
+
| CLI Commands | 6 |
|
|
184
|
+
| MCP Tools | 3 |
|
|
185
|
+
| Supported Languages | 3 (JS, TS, Python) |
|
|
186
|
+
| Frameworks | 6 (Next.js, Django, FastAPI, NestJS, Express, React) |
|
|
187
|
+
| Unit Tests | 246 |
|
|
188
|
+
| Lines of TypeScript | ~2,000 |
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
*Last updated: January 30, 2026*
|
package/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# VibeGuard
|
|
2
|
+
|
|
3
|
+
Local CLI security scanner for AI-generated code. Your code never leaves your machine.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g vibeguard
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Scan a directory
|
|
15
|
+
vibeguard scan ./src
|
|
16
|
+
|
|
17
|
+
# Scan specific files
|
|
18
|
+
vibeguard scan ./api.js ./auth.ts
|
|
19
|
+
|
|
20
|
+
# Set up pre-commit hook (blocks commits with critical/high issues)
|
|
21
|
+
vibeguard init
|
|
22
|
+
|
|
23
|
+
# View all security rules
|
|
24
|
+
vibeguard rules
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Commands
|
|
28
|
+
|
|
29
|
+
### `vibeguard scan [targets...]`
|
|
30
|
+
|
|
31
|
+
Scan files or directories for security vulnerabilities.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
vibeguard scan ./src # Scan directory
|
|
35
|
+
vibeguard scan --staged # Scan git staged files only
|
|
36
|
+
vibeguard scan --json # Output as JSON
|
|
37
|
+
vibeguard scan --force # Don't exit with error on issues
|
|
38
|
+
vibeguard scan --quiet # Minimal output
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `vibeguard init`
|
|
42
|
+
|
|
43
|
+
Set up a pre-commit hook to automatically scan code before commits.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
vibeguard init # Creates hook and config
|
|
47
|
+
vibeguard init --force # Overwrite existing hooks
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
This creates:
|
|
51
|
+
- A git pre-commit hook that runs `vibeguard scan --staged`
|
|
52
|
+
- A `.vibeguardrc.json` config file
|
|
53
|
+
|
|
54
|
+
### `vibeguard rules`
|
|
55
|
+
|
|
56
|
+
List all available security rules.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
vibeguard rules # List all rules
|
|
60
|
+
vibeguard rules --severity critical # Filter by severity
|
|
61
|
+
vibeguard rules --language python # Filter by language
|
|
62
|
+
vibeguard rules --json # Output as JSON
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### `vibeguard login`
|
|
66
|
+
|
|
67
|
+
Authenticate with your license key (for premium features).
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
vibeguard login
|
|
71
|
+
vibeguard login --key YOUR_KEY --email you@example.com
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### `vibeguard logout`
|
|
75
|
+
|
|
76
|
+
Remove stored license key.
|
|
77
|
+
|
|
78
|
+
### `vibeguard mcp`
|
|
79
|
+
|
|
80
|
+
Start VibeGuard as an MCP server for AI assistant integration.
|
|
81
|
+
|
|
82
|
+
## AI Assistant Integration (MCP)
|
|
83
|
+
|
|
84
|
+
VibeGuard can run as an MCP (Model Context Protocol) server, allowing AI coding assistants like Claude Code to directly scan your code for vulnerabilities.
|
|
85
|
+
|
|
86
|
+
### Setup for Claude Code
|
|
87
|
+
|
|
88
|
+
1. Install VibeGuard globally:
|
|
89
|
+
```bash
|
|
90
|
+
npm install -g vibeguard
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
2. Add to your Claude Code MCP settings (`~/.claude/settings.json`):
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"mcpServers": {
|
|
97
|
+
"vibeguard": {
|
|
98
|
+
"command": "vibeguard",
|
|
99
|
+
"args": ["mcp"]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
3. Restart Claude Code. The AI will now have access to these tools:
|
|
106
|
+
|
|
107
|
+
| Tool | Description |
|
|
108
|
+
|------|-------------|
|
|
109
|
+
| `scan_code` | Scan files/directories for security vulnerabilities |
|
|
110
|
+
| `list_security_rules` | List all available security rules |
|
|
111
|
+
| `check_code_snippet` | Check a code snippet without writing to disk |
|
|
112
|
+
|
|
113
|
+
### How it works
|
|
114
|
+
|
|
115
|
+
Once configured, you can ask Claude Code things like:
|
|
116
|
+
- "Scan my src folder for security issues"
|
|
117
|
+
- "Check this code for vulnerabilities before I save it"
|
|
118
|
+
- "What security rules does VibeGuard check for?"
|
|
119
|
+
|
|
120
|
+
The AI will automatically use VibeGuard to scan your code and report any issues.
|
|
121
|
+
|
|
122
|
+
## Configuration
|
|
123
|
+
|
|
124
|
+
Create a `.vibeguardrc.json` in your project root:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"exclude": [
|
|
129
|
+
"node_modules",
|
|
130
|
+
"dist",
|
|
131
|
+
"build",
|
|
132
|
+
"*.test.js"
|
|
133
|
+
],
|
|
134
|
+
"rules": {
|
|
135
|
+
"disabled": ["missing-rate-limit"]
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Supported Languages
|
|
141
|
+
|
|
142
|
+
- JavaScript (.js, .jsx, .mjs, .cjs)
|
|
143
|
+
- TypeScript (.ts, .tsx)
|
|
144
|
+
- Python (.py)
|
|
145
|
+
|
|
146
|
+
## Security Rules
|
|
147
|
+
|
|
148
|
+
### Critical
|
|
149
|
+
- **hardcoded-secret**: Detects hardcoded API keys, tokens, and passwords
|
|
150
|
+
- **sql-injection**: Detects SQL injection vulnerabilities
|
|
151
|
+
- **eval-usage**: Detects dangerous eval() usage
|
|
152
|
+
|
|
153
|
+
### High
|
|
154
|
+
- **missing-auth-route**: API routes without authentication
|
|
155
|
+
- **xss-innerhtml**: XSS via innerHTML/dangerouslySetInnerHTML
|
|
156
|
+
- **secrets-localstorage**: Sensitive data in localStorage/sessionStorage
|
|
157
|
+
- **supabase-no-rls**: Supabase queries without RLS
|
|
158
|
+
- **firebase-no-rules**: Firebase without security rules
|
|
159
|
+
- **idor-vulnerability**: Potential IDOR vulnerabilities
|
|
160
|
+
|
|
161
|
+
### Medium
|
|
162
|
+
- **permissive-cors**: Overly permissive CORS configuration
|
|
163
|
+
- **http-not-https**: HTTP instead of HTTPS
|
|
164
|
+
- **weak-password**: Weak password requirements
|
|
165
|
+
|
|
166
|
+
### Low
|
|
167
|
+
- **verbose-errors**: Detailed errors exposed to clients
|
|
168
|
+
- **missing-rate-limit**: Missing rate limiting on sensitive endpoints
|
|
169
|
+
|
|
170
|
+
## Exit Codes
|
|
171
|
+
|
|
172
|
+
- `0`: No critical/high issues found
|
|
173
|
+
- `1`: Critical or high severity issues found (blocks commit)
|
|
174
|
+
|
|
175
|
+
Use `--force` to always exit with 0, or `git commit --no-verify` to skip the hook.
|
|
176
|
+
|
|
177
|
+
## Privacy
|
|
178
|
+
|
|
179
|
+
VibeGuard runs entirely on your machine. Your code is never sent to any server. Only license validation requires network access.
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
Commercial SaaS - License key required for full features.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function getLicenseKey(): string | null;
|
|
2
|
+
export declare function saveLicenseKey(key: string, email?: string): void;
|
|
3
|
+
export declare function clearLicenseKey(): void;
|
|
4
|
+
export declare function validateLicense(key: string): Promise<{
|
|
5
|
+
valid: boolean;
|
|
6
|
+
message?: string;
|
|
7
|
+
tier?: string;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function activateLicense(email: string, key: string): Promise<{
|
|
10
|
+
success: boolean;
|
|
11
|
+
message: string;
|
|
12
|
+
}>;
|
|
13
|
+
//# sourceMappingURL=license.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"license.d.ts","sourceRoot":"","sources":["../../src/api/license.ts"],"names":[],"mappings":"AAcA,wBAAgB,aAAa,IAAI,MAAM,GAAG,IAAI,CAU7C;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAOhE;AAED,wBAAgB,eAAe,IAAI,IAAI,CAItC;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1D,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC,CAoCD;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAiChD"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getLicenseKey = getLicenseKey;
|
|
37
|
+
exports.saveLicenseKey = saveLicenseKey;
|
|
38
|
+
exports.clearLicenseKey = clearLicenseKey;
|
|
39
|
+
exports.validateLicense = validateLicense;
|
|
40
|
+
exports.activateLicense = activateLicense;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const CONFIG_DIR = path.join(os.homedir(), '.vibeguard');
|
|
45
|
+
const LICENSE_FILE = path.join(CONFIG_DIR, 'license.json');
|
|
46
|
+
const API_BASE_URL = process.env.VIBEGUARD_API_URL || 'https://api.vibeguard.dev';
|
|
47
|
+
function getLicenseKey() {
|
|
48
|
+
try {
|
|
49
|
+
if (fs.existsSync(LICENSE_FILE)) {
|
|
50
|
+
const data = JSON.parse(fs.readFileSync(LICENSE_FILE, 'utf-8'));
|
|
51
|
+
return data.key || null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Ignore errors
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
function saveLicenseKey(key, email) {
|
|
60
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
61
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
const data = { key, email };
|
|
64
|
+
fs.writeFileSync(LICENSE_FILE, JSON.stringify(data, null, 2));
|
|
65
|
+
}
|
|
66
|
+
function clearLicenseKey() {
|
|
67
|
+
if (fs.existsSync(LICENSE_FILE)) {
|
|
68
|
+
fs.unlinkSync(LICENSE_FILE);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function validateLicense(key) {
|
|
72
|
+
// For offline/development mode, accept any key
|
|
73
|
+
if (process.env.VIBEGUARD_OFFLINE === 'true') {
|
|
74
|
+
return { valid: true, tier: 'offline' };
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const response = await fetch(`${API_BASE_URL}/v1/license/validate`, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
},
|
|
82
|
+
body: JSON.stringify({ key }),
|
|
83
|
+
});
|
|
84
|
+
if (response.ok) {
|
|
85
|
+
const data = (await response.json());
|
|
86
|
+
return {
|
|
87
|
+
valid: true,
|
|
88
|
+
tier: data.tier || 'standard',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const error = (await response.json().catch(() => ({})));
|
|
92
|
+
return {
|
|
93
|
+
valid: false,
|
|
94
|
+
message: error.message || 'Invalid license key',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// If API is unreachable, allow offline mode
|
|
99
|
+
return {
|
|
100
|
+
valid: true,
|
|
101
|
+
message: 'Running in offline mode',
|
|
102
|
+
tier: 'offline',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function activateLicense(email, key) {
|
|
107
|
+
if (process.env.VIBEGUARD_OFFLINE === 'true') {
|
|
108
|
+
saveLicenseKey(key, email);
|
|
109
|
+
return { success: true, message: 'License activated in offline mode' };
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetch(`${API_BASE_URL}/v1/license/activate`, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
headers: {
|
|
115
|
+
'Content-Type': 'application/json',
|
|
116
|
+
},
|
|
117
|
+
body: JSON.stringify({ email, key }),
|
|
118
|
+
});
|
|
119
|
+
if (response.ok) {
|
|
120
|
+
saveLicenseKey(key, email);
|
|
121
|
+
return { success: true, message: 'License activated successfully' };
|
|
122
|
+
}
|
|
123
|
+
const error = (await response.json().catch(() => ({})));
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
message: error.message || 'Failed to activate license',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// Allow offline activation
|
|
131
|
+
saveLicenseKey(key, email);
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
message: 'License saved locally (offline mode)',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=license.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"license.js","sourceRoot":"","sources":["../../src/api/license.ts"],"names":[],"mappings":"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"}
|