vbguard 0.3.0 → 0.5.1
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 +247 -79
- package/package.json +4 -3
- package/src/bin.js +2 -0
- package/src/cli.js +91 -12
- package/src/index.js +53 -1
- package/src/precommit.js +67 -0
- package/src/reporter.js +126 -1
- package/src/scanners/auth-flow.js +234 -0
- package/src/scanners/firebase.js +124 -0
- package/src/scanners/hallucinated-packages.js +247 -0
- package/src/scanners/nextjs.js +139 -0
- package/src/scanners/supabase.js +102 -0
- package/src/scanners/vibe-patterns.js +193 -0
package/README.md
CHANGED
|
@@ -1,26 +1,46 @@
|
|
|
1
|
-
#
|
|
1
|
+
# vbguard
|
|
2
2
|
|
|
3
|
-
**Security scanner
|
|
3
|
+
**Security scanner for AI-generated code. Catches what Snyk, Semgrep, and GitGuardian miss.**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/vbguard)
|
|
6
|
+
[](https://www.npmjs.com/package/vbguard)
|
|
6
7
|
[](https://opensource.org/licenses/MIT)
|
|
7
8
|
|
|
8
9
|
---
|
|
9
10
|
|
|
10
|
-
Vibe coding is fast. But 45% of AI-generated code ships with known vulnerabilities. The Moltbook breach, the pickle exploits, the hardcoded Supabase keys
|
|
11
|
+
Vibe coding is fast. But 45% of AI-generated code ships with known vulnerabilities. The Moltbook breach, the pickle exploits, the hardcoded Supabase keys -- all caused by patterns that traditional scanners weren't designed to catch.
|
|
11
12
|
|
|
12
|
-
**
|
|
13
|
+
**vbguard** scans your codebase for security mistakes that AI coding tools (Cursor, Claude Code, Copilot, Lovable, Bolt, Replit) introduce most often. It also does things no other scanner can -- like detecting **hallucinated packages** and **broken auth flows**.
|
|
13
14
|
|
|
14
15
|
## Quick Start
|
|
15
16
|
|
|
16
17
|
```bash
|
|
17
|
-
npx
|
|
18
|
+
npx vbguard .
|
|
18
19
|
```
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
No config. No account. No API key. Runs in milliseconds.
|
|
22
|
+
|
|
23
|
+
## Get Your Security Score
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx vbguard . --score
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
vbguard security score:
|
|
31
|
+
|
|
32
|
+
████████████████░░░░ 78/100 -- good
|
|
33
|
+
|
|
34
|
+
* 0 critical
|
|
35
|
+
* 2 high
|
|
36
|
+
* 3 medium
|
|
37
|
+
* 1 low
|
|
38
|
+
```
|
|
21
39
|
|
|
22
40
|
## What It Catches
|
|
23
41
|
|
|
42
|
+
### Core Scanners
|
|
43
|
+
|
|
24
44
|
| Category | Examples | Severity |
|
|
25
45
|
|----------|----------|----------|
|
|
26
46
|
| **Hardcoded Secrets** | API keys, DB connection strings, JWTs, private keys inline in code | Critical |
|
|
@@ -33,143 +53,291 @@ That's it. No config, no account, no API key.
|
|
|
33
53
|
| **Missing .gitignore** | `.env` files not gitignored, secrets about to be committed | Critical |
|
|
34
54
|
| **Docker Misconfigs** | Running as root, copying `.env` into images, exposed DB ports | Medium-High |
|
|
35
55
|
|
|
36
|
-
|
|
56
|
+
### Hallucinated Package Detector (unique to vbguard)
|
|
57
|
+
|
|
58
|
+
AI tools frequently invent package names that don't exist. If someone registers that name with malicious code, your project is compromised.
|
|
37
59
|
|
|
38
|
-
- **
|
|
39
|
-
- **
|
|
60
|
+
- **CRITICAL**: Package doesn't exist on npm/PyPI (hallucinated by AI)
|
|
61
|
+
- **HIGH**: Package created less than 30 days ago (potential typosquat)
|
|
62
|
+
- **HIGH**: Package name within edit distance 1-2 of a popular package (`lodas` vs `lodash`)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Skip online checks for offline environments
|
|
66
|
+
npx vbguard . --offline
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Auth Flow Analyzer (Snyk can't do this)
|
|
70
|
+
|
|
71
|
+
Snyk and Semgrep **cannot** catch broken auth because it requires understanding application semantics. vbguard detects:
|
|
72
|
+
|
|
73
|
+
| Pattern | Why It Matters |
|
|
74
|
+
|---------|---------------|
|
|
75
|
+
| JWT with no expiration | Stolen tokens grant permanent access |
|
|
76
|
+
| JWT with weak/hardcoded secret | Anyone can forge valid tokens |
|
|
77
|
+
| User enumeration | Different errors for "user not found" vs "wrong password" reveal valid emails |
|
|
78
|
+
| Tokens in localStorage | Vulnerable to XSS -- use httpOnly cookies |
|
|
79
|
+
| OAuth open redirects | Unvalidated redirect_uri lets attackers steal tokens |
|
|
80
|
+
| Password reset without rate limiting | Enables brute-force of reset tokens |
|
|
81
|
+
| Signup with no email verification | Allows fake account creation |
|
|
82
|
+
| Inverted auth checks | The exact Lovable/Moltbook bug: blocking authenticated users, allowing anonymous |
|
|
83
|
+
| API routes with no auth middleware | Publicly accessible endpoints |
|
|
84
|
+
| Supabase signUp with no email confirmation | Anyone can register with any email |
|
|
85
|
+
|
|
86
|
+
### Vibe-Code Patterns (unique to AI-generated code)
|
|
87
|
+
|
|
88
|
+
Patterns that **only** appear in AI-generated code, not human code:
|
|
89
|
+
|
|
90
|
+
| Pattern | Why It Matters |
|
|
91
|
+
|---------|---------------|
|
|
92
|
+
| Security TODO comments | `TODO: add authentication` -- AI leaves these as placeholders and never comes back |
|
|
93
|
+
| Placeholder data in production | `test@test.com`, `John Doe`, `password123` shipped to prod |
|
|
94
|
+
| Sensitive data in console.log | `console.log(password)`, `console.log(token)` -- debugging leftovers |
|
|
95
|
+
| Commented-out security code | Auth checks, validation, rate limiting disabled during debugging |
|
|
96
|
+
| AI-generated markers | "Created with Cursor", "Copilot suggestion" -- indicates unreviewed code |
|
|
97
|
+
| Error stack trace leaks | `res.status(500).send(err.stack)` exposes internals to attackers |
|
|
98
|
+
| Silent error swallowing | `catch(e) {}` -- security failures silently ignored |
|
|
99
|
+
|
|
100
|
+
### Framework-Specific Scanners
|
|
101
|
+
|
|
102
|
+
#### Next.js
|
|
103
|
+
|
|
104
|
+
| Pattern | Severity |
|
|
105
|
+
|---------|----------|
|
|
106
|
+
| API keys in `"use client"` components | Critical |
|
|
107
|
+
| Missing `middleware.ts` for auth | Medium |
|
|
108
|
+
| Server Actions with no input validation | High |
|
|
109
|
+
| `publicRuntimeConfig` exposing secrets | Critical |
|
|
110
|
+
| API routes with no auth checks | Medium |
|
|
111
|
+
| `NEXT_PUBLIC_` prefix on sensitive env vars | Critical |
|
|
112
|
+
|
|
113
|
+
#### Supabase
|
|
114
|
+
|
|
115
|
+
| Pattern | Severity |
|
|
116
|
+
|---------|----------|
|
|
117
|
+
| Service role key in client-side code | Critical |
|
|
118
|
+
| `.from('table').select('*')` with no filter | Medium |
|
|
119
|
+
| Anon key used with no RLS | High |
|
|
120
|
+
| Public storage buckets with no policies | Medium |
|
|
121
|
+
| `SECURITY DEFINER` functions without auth checks | High |
|
|
122
|
+
|
|
123
|
+
#### Firebase
|
|
124
|
+
|
|
125
|
+
| Pattern | Severity |
|
|
126
|
+
|---------|----------|
|
|
127
|
+
| Firestore rules with `allow read, write: if true` | Critical |
|
|
128
|
+
| Realtime Database rules with `.read: true` at root | Critical |
|
|
129
|
+
| Storage rules with no auth condition | Critical/High |
|
|
130
|
+
| Firebase Admin SDK in client-side code | Critical |
|
|
131
|
+
| Firebase config with no App Check | Medium |
|
|
132
|
+
|
|
133
|
+
## Comparison
|
|
134
|
+
|
|
135
|
+
| Feature | vbguard | Snyk | Semgrep | GitGuardian |
|
|
136
|
+
|---------|---------|------|---------|-------------|
|
|
137
|
+
| Hardcoded secrets | Yes | Partial | Yes | Yes |
|
|
138
|
+
| Hallucinated package detection | **Yes** | No | No | No |
|
|
139
|
+
| Auth flow analysis | **Yes** | No | No | No |
|
|
140
|
+
| AI-code-specific patterns | **Yes** | No | No | No |
|
|
141
|
+
| Next.js-specific rules | **Yes** | No | Partial | No |
|
|
142
|
+
| Supabase security | **Yes** | No | No | No |
|
|
143
|
+
| Firebase security | **Yes** | Partial | Partial | No |
|
|
144
|
+
| Security score | **Yes** | No | No | No |
|
|
145
|
+
| Zero config | **Yes** | No | No | No |
|
|
146
|
+
| Runs offline | **Yes** | No | Yes | No |
|
|
147
|
+
| Free & open source | **Yes** | Partial | Yes | Partial |
|
|
148
|
+
| Speed | < 100ms | Minutes | Seconds | Seconds |
|
|
40
149
|
|
|
41
150
|
## Usage
|
|
42
151
|
|
|
43
152
|
```bash
|
|
44
153
|
# Scan current directory
|
|
45
|
-
|
|
154
|
+
npx vbguard .
|
|
46
155
|
|
|
47
156
|
# Scan a specific project
|
|
48
|
-
|
|
157
|
+
npx vbguard ./my-app
|
|
49
158
|
|
|
50
159
|
# Only show high and critical issues
|
|
51
|
-
|
|
160
|
+
npx vbguard . --severity=high
|
|
161
|
+
|
|
162
|
+
# Output as JSON
|
|
163
|
+
npx vbguard . --json
|
|
164
|
+
|
|
165
|
+
# Security score (0-100)
|
|
166
|
+
npx vbguard . --score
|
|
167
|
+
|
|
168
|
+
# Generate fix suggestions file
|
|
169
|
+
npx vbguard . --fix
|
|
52
170
|
|
|
53
|
-
#
|
|
54
|
-
|
|
171
|
+
# Only scan git-changed files (fast!)
|
|
172
|
+
npx vbguard . --diff
|
|
173
|
+
|
|
174
|
+
# Watch mode -- re-scans on file changes
|
|
175
|
+
npx vbguard . --watch
|
|
176
|
+
|
|
177
|
+
# SARIF output for GitHub Code Scanning
|
|
178
|
+
npx vbguard . --ci
|
|
179
|
+
|
|
180
|
+
# Skip online checks (hallucinated packages)
|
|
181
|
+
npx vbguard . --offline
|
|
55
182
|
|
|
56
183
|
# Hide fix suggestions
|
|
57
|
-
|
|
184
|
+
npx vbguard . --no-fix
|
|
58
185
|
|
|
59
186
|
# Ignore specific directories
|
|
60
|
-
|
|
187
|
+
npx vbguard . --ignore=tests,scripts
|
|
61
188
|
```
|
|
62
189
|
|
|
63
190
|
## CI/CD Integration
|
|
64
191
|
|
|
65
|
-
### GitHub Actions
|
|
192
|
+
### GitHub Actions (SARIF)
|
|
193
|
+
|
|
194
|
+
vbguard outputs SARIF format, which integrates directly with GitHub's Security tab:
|
|
66
195
|
|
|
67
196
|
```yaml
|
|
68
|
-
name: Security Scan
|
|
197
|
+
name: vbguard Security Scan
|
|
69
198
|
on: [push, pull_request]
|
|
70
199
|
|
|
71
200
|
jobs:
|
|
72
|
-
|
|
201
|
+
security-scan:
|
|
73
202
|
runs-on: ubuntu-latest
|
|
203
|
+
permissions:
|
|
204
|
+
security-events: write
|
|
74
205
|
steps:
|
|
75
206
|
- uses: actions/checkout@v4
|
|
76
207
|
- uses: actions/setup-node@v4
|
|
77
208
|
with:
|
|
78
209
|
node-version: '20'
|
|
79
|
-
-
|
|
210
|
+
- name: Run vbguard
|
|
211
|
+
run: npx vbguard@latest . --ci --offline > results.sarif || true
|
|
212
|
+
- name: Upload SARIF
|
|
213
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
214
|
+
with:
|
|
215
|
+
sarif_file: results.sarif
|
|
216
|
+
if: always()
|
|
80
217
|
```
|
|
81
218
|
|
|
82
|
-
|
|
219
|
+
### Simple CI (fail on critical/high)
|
|
83
220
|
|
|
84
|
-
|
|
221
|
+
```yaml
|
|
222
|
+
- run: npx vbguard . --severity=high --offline
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
vbguard exits with code 1 if critical or high issues are found, blocking the deploy.
|
|
226
|
+
|
|
227
|
+
## Pre-Commit Hook
|
|
228
|
+
|
|
229
|
+
### Option 1: Husky
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
npm install --save-dev husky
|
|
233
|
+
npx husky init
|
|
234
|
+
echo "npx vbguard-precommit" > .husky/pre-commit
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Option 2: Manual git hook
|
|
85
238
|
|
|
86
239
|
```bash
|
|
87
|
-
|
|
88
|
-
|
|
240
|
+
cat > .git/hooks/pre-commit << 'EOF'
|
|
241
|
+
#!/bin/sh
|
|
242
|
+
npx vbguard-precommit
|
|
243
|
+
EOF
|
|
244
|
+
chmod +x .git/hooks/pre-commit
|
|
89
245
|
```
|
|
90
246
|
|
|
247
|
+
The pre-commit hook:
|
|
248
|
+
- Only scans **staged files** (fast)
|
|
249
|
+
- Blocks commits with **critical or high** issues
|
|
250
|
+
- Shows exactly what was blocked and why
|
|
251
|
+
- Skips online checks for speed
|
|
252
|
+
- Use `git commit --no-verify` to bypass if needed
|
|
253
|
+
|
|
91
254
|
## Example Output
|
|
92
255
|
|
|
93
256
|
```
|
|
94
|
-
|
|
257
|
+
vbguard v0.5.0
|
|
95
258
|
Security scanner for AI-generated code
|
|
96
259
|
|
|
97
260
|
Scanning: /Users/dev/my-vibe-app
|
|
98
261
|
|
|
99
|
-
|
|
262
|
+
CRITICAL (3)
|
|
100
263
|
|
|
101
|
-
|
|
264
|
+
> secret/openai-api-key
|
|
102
265
|
src/api/chat.ts:5
|
|
103
|
-
Hardcoded OpenAI API Key detected.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
💡 Fix: Set specific origin: cors({ origin: 'https://yourdomain.com' })
|
|
130
|
-
|
|
131
|
-
─────────────────────────────────────────
|
|
266
|
+
Hardcoded OpenAI API Key detected.
|
|
267
|
+
Fix: Move to environment variable OPENAI_API_KEY.
|
|
268
|
+
|
|
269
|
+
> auth/jwt-weak-secret
|
|
270
|
+
src/auth/login.ts:23
|
|
271
|
+
JWT signed with weak secret "password". Anyone can forge tokens.
|
|
272
|
+
Fix: Use a strong random secret from process.env.JWT_SECRET.
|
|
273
|
+
|
|
274
|
+
> hallucinated/npm-package-not-found
|
|
275
|
+
package.json
|
|
276
|
+
Package "react-auth-helper" does not exist on npm.
|
|
277
|
+
Fix: Remove and search for the correct package name.
|
|
278
|
+
|
|
279
|
+
HIGH (2)
|
|
280
|
+
|
|
281
|
+
> vibe/security-todo-left-behind
|
|
282
|
+
src/middleware.ts:12
|
|
283
|
+
"TODO: add authentication before deploying"
|
|
284
|
+
Fix: Implement the security feature now.
|
|
285
|
+
|
|
286
|
+
> auth/token-in-localstorage
|
|
287
|
+
src/hooks/useAuth.ts:45
|
|
288
|
+
Auth token stored in localStorage. Vulnerable to XSS.
|
|
289
|
+
Fix: Use httpOnly cookies instead.
|
|
290
|
+
|
|
291
|
+
-----------------------------------------
|
|
132
292
|
5 issues found: 3 critical, 2 high
|
|
133
293
|
Scanned 24 files in 12ms
|
|
134
294
|
|
|
135
|
-
|
|
295
|
+
Fix critical and high severity issues before deploying!
|
|
136
296
|
```
|
|
137
297
|
|
|
138
|
-
##
|
|
139
|
-
|
|
140
|
-
Those tools are great for traditional code. But they weren't designed for AI-generated code patterns:
|
|
141
|
-
|
|
142
|
-
- **Snyk** focuses on dependency vulnerabilities, not hardcoded secrets or missing middleware
|
|
143
|
-
- **Semgrep** requires writing custom rules — vibeguard ships with AI-specific patterns out of the box
|
|
144
|
-
- **SonarQube** is enterprise-heavy and takes hours to configure
|
|
298
|
+
## Supported Languages
|
|
145
299
|
|
|
146
|
-
|
|
300
|
+
- **JavaScript / TypeScript** -- Express, Fastify, Next.js, React, Vue, Svelte
|
|
301
|
+
- **Python** -- Flask, FastAPI, Django
|
|
147
302
|
|
|
148
303
|
## How It Works
|
|
149
304
|
|
|
150
|
-
|
|
305
|
+
vbguard uses pattern matching (regex + structural analysis) against a curated ruleset of AI-specific vulnerability patterns. No AI, no API calls (except optional package registry checks), no data leaves your machine.
|
|
151
306
|
|
|
152
|
-
The ruleset is based on real-world breaches and
|
|
153
|
-
- The Moltbook breach (Supabase misconfiguration)
|
|
307
|
+
The ruleset is based on real-world breaches and research:
|
|
308
|
+
- The Moltbook breach (Supabase misconfiguration + inverted auth)
|
|
154
309
|
- Tenzai's 2025 study (69 vulnerabilities across 5 AI coding tools)
|
|
155
310
|
- Escape.tech's scan of 5,600 vibe-coded apps
|
|
156
311
|
- Georgia Tech's Vibe Security Radar (tracking AI-generated CVEs)
|
|
157
312
|
|
|
158
|
-
##
|
|
159
|
-
|
|
160
|
-
Contributions welcome. If you've found a vulnerability pattern that AI tools commonly introduce, open a PR to add it to the scanner.
|
|
313
|
+
## Project Structure
|
|
161
314
|
|
|
162
315
|
```
|
|
163
316
|
src/scanners/
|
|
164
|
-
secrets.js
|
|
165
|
-
dangerous-defaults.js
|
|
166
|
-
dangerous-functions.js
|
|
167
|
-
exposed-frontend.js
|
|
168
|
-
permissive-configs.js
|
|
169
|
-
dependencies.js
|
|
170
|
-
gitignore.js
|
|
317
|
+
secrets.js # Hardcoded API keys, tokens, connection strings
|
|
318
|
+
dangerous-defaults.js # Missing auth, rate limiting, CORS, headers
|
|
319
|
+
dangerous-functions.js # eval, pickle, SQL injection, XSS
|
|
320
|
+
exposed-frontend.js # Server secrets in client-side code
|
|
321
|
+
permissive-configs.js # Docker misconfigs
|
|
322
|
+
dependencies.js # Compromised/deprecated packages
|
|
323
|
+
gitignore.js # Missing .gitignore entries
|
|
324
|
+
hallucinated-packages.js # AI-hallucinated npm/PyPI packages
|
|
325
|
+
auth-flow.js # Broken auth patterns
|
|
326
|
+
vibe-patterns.js # AI-code-specific antipatterns
|
|
327
|
+
nextjs.js # Next.js framework rules
|
|
328
|
+
supabase.js # Supabase security rules
|
|
329
|
+
firebase.js # Firebase security rules
|
|
171
330
|
```
|
|
172
331
|
|
|
332
|
+
## Contributing
|
|
333
|
+
|
|
334
|
+
Contributions welcome! If you've found a vulnerability pattern that AI tools commonly introduce, open a PR to add it.
|
|
335
|
+
|
|
336
|
+
1. Add your pattern to the relevant scanner in `src/scanners/`
|
|
337
|
+
2. Add a test case in `test/test.js`
|
|
338
|
+
3. Run `npm test` to verify
|
|
339
|
+
4. Open a PR with a description of the real-world scenario this catches
|
|
340
|
+
|
|
173
341
|
## License
|
|
174
342
|
|
|
175
343
|
MIT
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vbguard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Security scanner for AI-generated code. Catches what traditional scanners miss.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"vbguard": "
|
|
7
|
+
"vbguard": "src/bin.js",
|
|
8
|
+
"vbguard-precommit": "src/precommit.js"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
11
|
"start": "node src/cli.js",
|
|
@@ -32,4 +33,4 @@
|
|
|
32
33
|
"README.md",
|
|
33
34
|
"LICENSE"
|
|
34
35
|
]
|
|
35
|
-
}
|
|
36
|
+
}
|
package/src/bin.js
ADDED
package/src/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
1
2
|
const path = require('path');
|
|
2
3
|
const { scanDirectory } = require('./index');
|
|
3
|
-
const { formatReport } = require('./reporter');
|
|
4
|
+
const { formatReport, formatSarif, formatScore, formatFixes } = require('./reporter');
|
|
4
5
|
|
|
5
6
|
const args = process.argv.slice(2);
|
|
6
7
|
|
|
@@ -15,17 +16,33 @@ const HELP = `
|
|
|
15
16
|
--severity=X Minimum severity (low, medium, high, critical)
|
|
16
17
|
--no-fix Hide fix suggestions
|
|
17
18
|
--ignore=X Comma-separated patterns to ignore
|
|
19
|
+
--offline Skip online checks (hallucinated package detection)
|
|
20
|
+
--fix Generate .vbguard-fixes.md with suggested code changes
|
|
21
|
+
--diff Only scan files changed since last git commit
|
|
22
|
+
--watch Watch for file changes and re-scan automatically
|
|
23
|
+
--ci Output results in SARIF format (for GitHub Code Scanning)
|
|
24
|
+
--score Output a security score (0-100) for the project
|
|
18
25
|
-h, --help Show this help
|
|
19
26
|
-v, --version Show version
|
|
20
27
|
`;
|
|
21
28
|
|
|
22
29
|
function parseArgs(args) {
|
|
23
|
-
const opts = {
|
|
30
|
+
const opts = {
|
|
31
|
+
dir: '.', json: false, severity: 'low', showFix: true,
|
|
32
|
+
ignore: [], offline: false, fix: false, diff: false,
|
|
33
|
+
watch: false, ci: false, score: false,
|
|
34
|
+
};
|
|
24
35
|
for (const arg of args) {
|
|
25
36
|
if (arg === '-h' || arg === '--help') { console.log(HELP); process.exit(0); }
|
|
26
|
-
if (arg === '-v' || arg === '--version') { console.log('0.1
|
|
37
|
+
if (arg === '-v' || arg === '--version') { console.log('0.5.1'); process.exit(0); }
|
|
27
38
|
if (arg === '--json') opts.json = true;
|
|
28
39
|
else if (arg === '--no-fix') opts.showFix = false;
|
|
40
|
+
else if (arg === '--offline') opts.offline = true;
|
|
41
|
+
else if (arg === '--fix') opts.fix = true;
|
|
42
|
+
else if (arg === '--diff') opts.diff = true;
|
|
43
|
+
else if (arg === '--watch') opts.watch = true;
|
|
44
|
+
else if (arg === '--ci') opts.ci = true;
|
|
45
|
+
else if (arg === '--score') opts.score = true;
|
|
29
46
|
else if (arg.startsWith('--severity=')) opts.severity = arg.split('=')[1];
|
|
30
47
|
else if (arg.startsWith('--ignore=')) opts.ignore = arg.split('=')[1].split(',');
|
|
31
48
|
else if (!arg.startsWith('-')) opts.dir = arg;
|
|
@@ -33,24 +50,86 @@ function parseArgs(args) {
|
|
|
33
50
|
return opts;
|
|
34
51
|
}
|
|
35
52
|
|
|
53
|
+
async function runScan(opts) {
|
|
54
|
+
const targetDir = path.resolve(opts.dir);
|
|
55
|
+
|
|
56
|
+
const results = await scanDirectory(targetDir, opts);
|
|
57
|
+
|
|
58
|
+
if (opts.ci) {
|
|
59
|
+
console.log(JSON.stringify(formatSarif(results, targetDir), null, 2));
|
|
60
|
+
} else if (opts.score) {
|
|
61
|
+
formatScore(results);
|
|
62
|
+
} else if (opts.json) {
|
|
63
|
+
console.log(JSON.stringify(results, null, 2));
|
|
64
|
+
} else {
|
|
65
|
+
formatReport(results, opts);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (opts.fix) {
|
|
69
|
+
const fixContent = formatFixes(results, targetDir);
|
|
70
|
+
const fixPath = path.join(targetDir, '.vbguard-fixes.md');
|
|
71
|
+
fs.writeFileSync(fixPath, fixContent);
|
|
72
|
+
console.log(` \x1b[32m📝 Fix suggestions written to .vbguard-fixes.md\x1b[0m\n`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
77
|
+
|
|
36
78
|
async function main() {
|
|
37
79
|
const opts = parseArgs(args);
|
|
38
80
|
const targetDir = path.resolve(opts.dir);
|
|
39
81
|
|
|
82
|
+
if (opts.watch) {
|
|
83
|
+
// Watch mode
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log(' \x1b[1m\x1b[35m⚡ vbguard\x1b[0m \x1b[2mv0.5.1 — watch mode\x1b[0m');
|
|
86
|
+
console.log(` \x1b[2mWatching:\x1b[0m ${targetDir}`);
|
|
87
|
+
console.log('');
|
|
88
|
+
|
|
89
|
+
let debounceTimer = null;
|
|
90
|
+
const doScan = async () => {
|
|
91
|
+
try {
|
|
92
|
+
process.stdout.write('\x1b[2J\x1b[H'); // Clear terminal
|
|
93
|
+
console.log('');
|
|
94
|
+
console.log(' \x1b[1m\x1b[35m⚡ vbguard\x1b[0m \x1b[2mv0.5.1 — watch mode\x1b[0m');
|
|
95
|
+
console.log(` \x1b[2mWatching:\x1b[0m ${targetDir}`);
|
|
96
|
+
console.log('');
|
|
97
|
+
await runScan(opts);
|
|
98
|
+
console.log(' \x1b[2mWatching for changes...\x1b[0m\n');
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error(`\x1b[31m Error: ${err.message}\x1b[0m`);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
await doScan();
|
|
105
|
+
console.log(' \x1b[2mWatching for changes...\x1b[0m\n');
|
|
106
|
+
|
|
107
|
+
const IGNORE = new Set(['node_modules', '.git', '.next', 'dist', 'build', '.cache']);
|
|
108
|
+
fs.watch(targetDir, { recursive: true }, (eventType, filename) => {
|
|
109
|
+
if (!filename) return;
|
|
110
|
+
const parts = filename.split(path.sep);
|
|
111
|
+
if (parts.some(p => IGNORE.has(p))) return;
|
|
112
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
113
|
+
debounceTimer = setTimeout(doScan, 500);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return; // Keep process alive
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Normal scan
|
|
40
120
|
console.log('');
|
|
41
|
-
console.log(' \x1b[1m\x1b[35m
|
|
121
|
+
console.log(' \x1b[1m\x1b[35m⚡ vbguard\x1b[0m \x1b[2mv0.5.1\x1b[0m');
|
|
42
122
|
console.log(' \x1b[2mSecurity scanner for AI-generated code\x1b[0m');
|
|
43
123
|
console.log('');
|
|
44
|
-
|
|
124
|
+
if (opts.diff) {
|
|
125
|
+
console.log(` \x1b[2mScanning changed files in:\x1b[0m ${targetDir}`);
|
|
126
|
+
} else {
|
|
127
|
+
console.log(` \x1b[2mScanning:\x1b[0m ${targetDir}`);
|
|
128
|
+
}
|
|
45
129
|
console.log('');
|
|
46
130
|
|
|
47
131
|
try {
|
|
48
|
-
const results = await
|
|
49
|
-
if (opts.json) {
|
|
50
|
-
console.log(JSON.stringify(results, null, 2));
|
|
51
|
-
} else {
|
|
52
|
-
formatReport(results, opts);
|
|
53
|
-
}
|
|
132
|
+
const results = await runScan(opts);
|
|
54
133
|
const hasCritical = results.findings.some(f => f.severity === 'critical' || f.severity === 'high');
|
|
55
134
|
process.exit(hasCritical ? 1 : 0);
|
|
56
135
|
} catch (err) {
|
|
@@ -59,4 +138,4 @@ async function main() {
|
|
|
59
138
|
}
|
|
60
139
|
}
|
|
61
140
|
|
|
62
|
-
main();
|
|
141
|
+
main();
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
3
4
|
|
|
4
5
|
const { scanSecrets } = require('./scanners/secrets');
|
|
5
6
|
const { scanDangerousDefaults } = require('./scanners/dangerous-defaults');
|
|
@@ -8,6 +9,12 @@ const { scanMissingGitignore } = require('./scanners/gitignore');
|
|
|
8
9
|
const { scanDangerousFunctions } = require('./scanners/dangerous-functions');
|
|
9
10
|
const { scanPermissiveConfigs } = require('./scanners/permissive-configs');
|
|
10
11
|
const { scanDependencies } = require('./scanners/dependencies');
|
|
12
|
+
const { scanHallucinatedPackages } = require('./scanners/hallucinated-packages');
|
|
13
|
+
const { scanAuthFlow } = require('./scanners/auth-flow');
|
|
14
|
+
const { scanVibePatterns } = require('./scanners/vibe-patterns');
|
|
15
|
+
const { scanNextjs, scanNextjsProject } = require('./scanners/nextjs');
|
|
16
|
+
const { scanSupabase } = require('./scanners/supabase');
|
|
17
|
+
const { scanFirebase } = require('./scanners/firebase');
|
|
11
18
|
|
|
12
19
|
const IGNORE_DIRS = new Set([
|
|
13
20
|
'node_modules', '.git', '.next', '__pycache__', '.venv', 'venv',
|
|
@@ -68,6 +75,28 @@ function walkDir(dir, ignore = []) {
|
|
|
68
75
|
return files;
|
|
69
76
|
}
|
|
70
77
|
|
|
78
|
+
function getChangedFiles(dir) {
|
|
79
|
+
try {
|
|
80
|
+
// Get both staged and unstaged changed files
|
|
81
|
+
const staged = execSync('git diff --cached --name-only', { cwd: dir, encoding: 'utf-8' }).trim();
|
|
82
|
+
const unstaged = execSync('git diff --name-only', { cwd: dir, encoding: 'utf-8' }).trim();
|
|
83
|
+
const untracked = execSync('git ls-files --others --exclude-standard', { cwd: dir, encoding: 'utf-8' }).trim();
|
|
84
|
+
const all = [staged, unstaged, untracked].filter(Boolean).join('\n');
|
|
85
|
+
return [...new Set(all.split('\n').filter(Boolean))].map(f => path.resolve(dir, f));
|
|
86
|
+
} catch {
|
|
87
|
+
return null; // Not a git repo
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getStagedFiles(dir) {
|
|
92
|
+
try {
|
|
93
|
+
const staged = execSync('git diff --cached --name-only', { cwd: dir, encoding: 'utf-8' }).trim();
|
|
94
|
+
return staged ? staged.split('\n').map(f => path.resolve(dir, f)) : [];
|
|
95
|
+
} catch {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
71
100
|
async function scanDirectory(dir, opts = {}) {
|
|
72
101
|
const startTime = Date.now();
|
|
73
102
|
|
|
@@ -75,7 +104,19 @@ async function scanDirectory(dir, opts = {}) {
|
|
|
75
104
|
throw new Error(`Directory not found: ${dir}`);
|
|
76
105
|
}
|
|
77
106
|
|
|
78
|
-
|
|
107
|
+
let files;
|
|
108
|
+
if (opts.diff) {
|
|
109
|
+
const changed = getChangedFiles(dir);
|
|
110
|
+
if (changed === null) {
|
|
111
|
+
throw new Error('--diff requires a git repository');
|
|
112
|
+
}
|
|
113
|
+
files = changed.filter(f => shouldScanFile(f));
|
|
114
|
+
} else if (opts.staged) {
|
|
115
|
+
const staged = getStagedFiles(dir);
|
|
116
|
+
files = staged.filter(f => shouldScanFile(f));
|
|
117
|
+
} else {
|
|
118
|
+
files = walkDir(dir, opts.ignore || []);
|
|
119
|
+
}
|
|
79
120
|
const findings = [];
|
|
80
121
|
let filesScanned = 0;
|
|
81
122
|
|
|
@@ -103,11 +144,22 @@ async function scanDirectory(dir, opts = {}) {
|
|
|
103
144
|
findings.push(...scanExposedFrontend(ctx));
|
|
104
145
|
findings.push(...scanDangerousFunctions(ctx));
|
|
105
146
|
findings.push(...scanPermissiveConfigs(ctx));
|
|
147
|
+
findings.push(...scanAuthFlow(ctx));
|
|
148
|
+
findings.push(...scanVibePatterns(ctx));
|
|
149
|
+
findings.push(...scanNextjs(ctx, dir));
|
|
150
|
+
findings.push(...scanSupabase(ctx));
|
|
151
|
+
findings.push(...scanFirebase(ctx));
|
|
106
152
|
}
|
|
107
153
|
|
|
108
154
|
// Project-level scanners
|
|
109
155
|
findings.push(...scanMissingGitignore(dir));
|
|
110
156
|
findings.push(...(await scanDependencies(dir)));
|
|
157
|
+
findings.push(...scanNextjsProject(dir));
|
|
158
|
+
|
|
159
|
+
// Online scanners (can be skipped with --offline)
|
|
160
|
+
if (!opts.offline) {
|
|
161
|
+
findings.push(...(await scanHallucinatedPackages(dir, opts)));
|
|
162
|
+
}
|
|
111
163
|
|
|
112
164
|
// Filter by severity
|
|
113
165
|
const severityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
|