@quantracode/vibecheck 0.3.1 → 0.3.3
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 +166 -659
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
# @quantracode/vibecheck
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The first AI Enforcement Security tool. Proves whether AI-written code actually enforces the security it claims — not just implied, commented, or assumed.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## What is AI Enforcement Security?
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Traditional security tools scan for vulnerabilities. VibeCheck verifies enforcement reality. That's a fundamentally different job.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
# Using npx
|
|
11
|
-
npx @quantracode/vibecheck scan --fail-on off --out vibecheck-scan.json
|
|
9
|
+
AI-generated code often hallucinates security guarantees. It writes comments claiming protection exists, imports security libraries but doesn't use them, or creates middleware that never gets wired up. VibeCheck detects these patterns and proves what's actually enforced.
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
**Key Principles:**
|
|
12
|
+
- **Deterministic** — No LLM calls, results are reproducible
|
|
13
|
+
- **Local-only** — All analysis runs on your machine, code never uploaded
|
|
14
|
+
- **Low false positives** — Precision over recall
|
|
15
|
+
- **Framework-aware** — Built for Next.js, Express patterns
|
|
16
|
+
- **Enforcement-focused** — Proves what's enforced, not just scanned
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
## Quick Start
|
|
18
19
|
|
|
19
20
|
```bash
|
|
20
|
-
|
|
21
|
+
# Install globally
|
|
22
|
+
npm install -g @quantracode/vibecheck
|
|
23
|
+
|
|
24
|
+
# Or use without installing
|
|
25
|
+
npx @quantracode/vibecheck scan --fail-on off --out vibecheck-scan.json
|
|
21
26
|
```
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
### Scan Another Folder
|
|
24
29
|
|
|
25
30
|
```bash
|
|
26
|
-
|
|
27
|
-
# or
|
|
28
|
-
pnpm add -g @quantracode/vibecheck
|
|
31
|
+
npx @quantracode/vibecheck scan --target ../my-other-app --out scan.json
|
|
29
32
|
```
|
|
30
33
|
|
|
31
34
|
## Usage
|
|
@@ -129,7 +132,9 @@ vibecheck view
|
|
|
129
132
|
# Browser opens to http://localhost:3000 with results loaded
|
|
130
133
|
```
|
|
131
134
|
|
|
132
|
-
|
|
135
|
+
## Command Line Options
|
|
136
|
+
|
|
137
|
+
### Scan Options
|
|
133
138
|
|
|
134
139
|
| Option | Description | Default |
|
|
135
140
|
|--------|-------------|---------|
|
|
@@ -141,7 +146,6 @@ vibecheck view
|
|
|
141
146
|
| `-e, --exclude <glob>` | Glob pattern to exclude (repeatable) | See below |
|
|
142
147
|
| `--include-tests` | Include test files in scan | `false` |
|
|
143
148
|
| `--emit-intent-map` | Include route map and coverage metrics | `false` |
|
|
144
|
-
| `--changed` | Only scan changed files (not implemented) | `false` |
|
|
145
149
|
|
|
146
150
|
### Default Excludes
|
|
147
151
|
|
|
@@ -156,618 +160,93 @@ The following patterns are excluded by default:
|
|
|
156
160
|
- `test/`, `tests/`, `fixtures/`, `__mocks__/`, `__fixtures__/`
|
|
157
161
|
- `cypress/`, `e2e/`, `*.stories.*`
|
|
158
162
|
|
|
159
|
-
## Scanner
|
|
160
|
-
|
|
161
|
-
VibeCheck organizes security rules into modular scanner packs. Each pack focuses on a specific security domain.
|
|
162
|
-
|
|
163
|
-
### Auth Pack
|
|
164
|
-
|
|
165
|
-
Rules for authentication and authorization issues.
|
|
166
|
-
|
|
167
|
-
#### VC-AUTH-001: Unprotected State-Changing API Route
|
|
168
|
-
|
|
169
|
-
**Severity:** High / Critical
|
|
170
|
-
**Category:** auth
|
|
171
|
-
|
|
172
|
-
Detects Next.js App Router API route handlers (POST, PUT, PATCH, DELETE) that perform database operations without authentication checks.
|
|
173
|
-
|
|
174
|
-
**What it looks for:**
|
|
175
|
-
- Route handlers in `app/**/route.ts` files
|
|
176
|
-
- Handlers that use Prisma, Drizzle, or other database operations
|
|
177
|
-
- Missing calls to `getServerSession`, `auth()`, or similar auth checks
|
|
178
|
-
|
|
179
|
-
**Example (vulnerable):**
|
|
180
|
-
```typescript
|
|
181
|
-
// app/api/users/route.ts
|
|
182
|
-
export async function POST(request: Request) {
|
|
183
|
-
const body = await request.json();
|
|
184
|
-
await prisma.user.create({ data: body }); // No auth check!
|
|
185
|
-
return Response.json({ success: true });
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
**Example (safe):**
|
|
190
|
-
```typescript
|
|
191
|
-
// app/api/users/route.ts
|
|
192
|
-
export async function POST(request: Request) {
|
|
193
|
-
const session = await getServerSession();
|
|
194
|
-
if (!session) {
|
|
195
|
-
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
196
|
-
}
|
|
197
|
-
const body = await request.json();
|
|
198
|
-
await prisma.user.create({ data: body });
|
|
199
|
-
return Response.json({ success: true });
|
|
200
|
-
}
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
#### VC-MW-001: Middleware Matcher Gap
|
|
206
|
-
|
|
207
|
-
**Severity:** High
|
|
208
|
-
**Category:** middleware
|
|
209
|
-
|
|
210
|
-
Detects Next.js middleware that doesn't cover API routes, potentially leaving them unprotected.
|
|
211
|
-
|
|
212
|
-
**What it looks for:**
|
|
213
|
-
- `middleware.ts` files with `config.matcher` exports
|
|
214
|
-
- Matchers that exclude `/api` routes
|
|
215
|
-
- Projects using next-auth without middleware protection
|
|
216
|
-
|
|
217
|
-
**Example (vulnerable):**
|
|
218
|
-
```typescript
|
|
219
|
-
// middleware.ts
|
|
220
|
-
export const config = {
|
|
221
|
-
matcher: ['/dashboard/:path*'] // Missing /api routes!
|
|
222
|
-
};
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
**Example (safe):**
|
|
226
|
-
```typescript
|
|
227
|
-
// middleware.ts
|
|
228
|
-
export const config = {
|
|
229
|
-
matcher: ['/api/:path*', '/dashboard/:path*']
|
|
230
|
-
};
|
|
231
|
-
```
|
|
163
|
+
## Scanner Categories
|
|
232
164
|
|
|
233
|
-
|
|
165
|
+
VibeCheck includes 30+ enforcement verification scanners across these categories:
|
|
234
166
|
|
|
235
|
-
###
|
|
167
|
+
### Auth & Authorization
|
|
236
168
|
|
|
237
|
-
|
|
169
|
+
| Rule ID | Title | Severity |
|
|
170
|
+
|---------|-------|----------|
|
|
171
|
+
| VC-AUTH-001 | Unprotected State-Changing API Route | High/Critical |
|
|
172
|
+
| VC-MW-001 | Middleware Matcher Gap | High |
|
|
173
|
+
| VC-AUTH-010 | Auth-by-UI with Server Gap | Critical |
|
|
238
174
|
|
|
239
|
-
|
|
175
|
+
### Input Validation
|
|
240
176
|
|
|
241
|
-
|
|
242
|
-
|
|
177
|
+
| Rule ID | Title | Severity |
|
|
178
|
+
|---------|-------|----------|
|
|
179
|
+
| VC-VAL-001 | Validation Defined But Output Ignored | Medium |
|
|
180
|
+
| VC-VAL-002 | Client-Side Only Validation | Medium |
|
|
243
181
|
|
|
244
|
-
|
|
182
|
+
### Network Security
|
|
245
183
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
-
|
|
249
|
-
-
|
|
184
|
+
| Rule ID | Title | Severity |
|
|
185
|
+
|---------|-------|----------|
|
|
186
|
+
| VC-NET-001 | SSRF-Prone Fetch | High |
|
|
187
|
+
| VC-NET-002 | Open Redirect | High |
|
|
188
|
+
| VC-NET-003 | Over-permissive CORS with Credentials | High |
|
|
189
|
+
| VC-NET-004 | Missing Request Timeout | Low |
|
|
250
190
|
|
|
251
|
-
|
|
252
|
-
```typescript
|
|
253
|
-
const schema = z.object({ name: z.string() });
|
|
254
|
-
schema.parse(body); // Result ignored!
|
|
255
|
-
await prisma.user.create({ data: body }); // Uses unvalidated body
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
**Example (safe):**
|
|
259
|
-
```typescript
|
|
260
|
-
const schema = z.object({ name: z.string() });
|
|
261
|
-
const validated = schema.parse(body);
|
|
262
|
-
await prisma.user.create({ data: validated });
|
|
263
|
-
```
|
|
191
|
+
### Secrets & Config
|
|
264
192
|
|
|
265
|
-
|
|
193
|
+
| Rule ID | Title | Severity |
|
|
194
|
+
|---------|-------|----------|
|
|
195
|
+
| VC-CONFIG-001 | Undocumented Environment Variable | Low |
|
|
196
|
+
| VC-CONFIG-002 | Insecure Default Secret Fallback | Critical |
|
|
197
|
+
| VC-PRIV-003 | Debug Flags in Production | Medium |
|
|
266
198
|
|
|
267
|
-
### Privacy
|
|
199
|
+
### Privacy & Data
|
|
268
200
|
|
|
269
|
-
|
|
201
|
+
| Rule ID | Title | Severity |
|
|
202
|
+
|---------|-------|----------|
|
|
203
|
+
| VC-PRIV-001 | Sensitive Data Logged | High |
|
|
204
|
+
| VC-PRIV-002 | Over-broad API Response | Medium/High |
|
|
270
205
|
|
|
271
|
-
|
|
206
|
+
### Cryptography
|
|
272
207
|
|
|
273
|
-
|
|
274
|
-
|
|
208
|
+
| Rule ID | Title | Severity |
|
|
209
|
+
|---------|-------|----------|
|
|
210
|
+
| VC-CRYPTO-001 | Math.random for Tokens | High |
|
|
211
|
+
| VC-CRYPTO-002 | JWT Decode Without Verify | Critical |
|
|
212
|
+
| VC-CRYPTO-003 | Weak Password Hashing | High |
|
|
275
213
|
|
|
276
|
-
|
|
214
|
+
### File Uploads
|
|
277
215
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
-
|
|
281
|
-
|
|
282
|
-
**Example (vulnerable):**
|
|
283
|
-
```typescript
|
|
284
|
-
console.log("User login:", { email, password }); // Logs password!
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
**Example (safe):**
|
|
288
|
-
```typescript
|
|
289
|
-
console.log("User login:", { email, timestamp: Date.now() });
|
|
290
|
-
```
|
|
216
|
+
| Rule ID | Title | Severity |
|
|
217
|
+
|---------|-------|----------|
|
|
218
|
+
| VC-UP-001 | File Upload Without Constraints | High |
|
|
219
|
+
| VC-UP-002 | Upload to Public Path | High |
|
|
291
220
|
|
|
292
|
-
|
|
221
|
+
### Middleware
|
|
293
222
|
|
|
294
|
-
|
|
223
|
+
| Rule ID | Title | Severity |
|
|
224
|
+
|---------|-------|----------|
|
|
225
|
+
| VC-RATE-001 | Missing Rate Limiting | Medium |
|
|
295
226
|
|
|
296
|
-
|
|
227
|
+
### AI Hallucinations
|
|
297
228
|
|
|
298
|
-
|
|
229
|
+
| Rule ID | Title | Severity |
|
|
230
|
+
|---------|-------|----------|
|
|
231
|
+
| VC-HALL-001 | Security Library Imported But Not Used | Medium |
|
|
232
|
+
| VC-HALL-010 | Comment Claims Protection But Unproven | Medium |
|
|
233
|
+
| VC-HALL-011 | Middleware Assumed But Not Matching | High |
|
|
234
|
+
| VC-HALL-012 | Validation Claimed But Missing/Ignored | Medium |
|
|
299
235
|
|
|
300
|
-
|
|
301
|
-
**Category:** config
|
|
302
|
-
|
|
303
|
-
Detects `process.env.VAR` references that aren't documented in `.env.example`.
|
|
304
|
-
|
|
305
|
-
---
|
|
306
|
-
|
|
307
|
-
#### VC-CONFIG-002: Insecure Default Secret Fallback
|
|
308
|
-
|
|
309
|
-
**Severity:** Critical
|
|
310
|
-
**Category:** secrets
|
|
311
|
-
|
|
312
|
-
Detects hardcoded fallback values for security-critical environment variables.
|
|
313
|
-
|
|
314
|
-
**What it looks for:**
|
|
315
|
-
- `process.env.VAR || "fallback"` patterns
|
|
316
|
-
- Variables named: SECRET, KEY, TOKEN, PASSWORD, etc.
|
|
317
|
-
- Hardcoded string fallbacks
|
|
318
|
-
|
|
319
|
-
**Example (vulnerable):**
|
|
320
|
-
```typescript
|
|
321
|
-
const jwtSecret = process.env.JWT_SECRET || "development-secret";
|
|
322
|
-
```
|
|
236
|
+
### Supply Chain
|
|
323
237
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
---
|
|
331
|
-
|
|
332
|
-
### Network Pack
|
|
333
|
-
|
|
334
|
-
Rules for network security issues.
|
|
335
|
-
|
|
336
|
-
#### VC-NET-001: SSRF-Prone Fetch
|
|
337
|
-
|
|
338
|
-
**Severity:** High
|
|
339
|
-
**Category:** network
|
|
340
|
-
|
|
341
|
-
Detects fetch/axios calls where the URL is derived from user input without validation.
|
|
342
|
-
|
|
343
|
-
**What it looks for:**
|
|
344
|
-
- `fetch()` or `axios.get()` calls
|
|
345
|
-
- URL constructed from request parameters, query strings, or body
|
|
346
|
-
- No URL validation or allowlist checks
|
|
347
|
-
|
|
348
|
-
**Example (vulnerable):**
|
|
349
|
-
```typescript
|
|
350
|
-
export async function GET(request: Request) {
|
|
351
|
-
const { searchParams } = new URL(request.url);
|
|
352
|
-
const url = searchParams.get("url");
|
|
353
|
-
const response = await fetch(url); // SSRF risk!
|
|
354
|
-
return Response.json(await response.json());
|
|
355
|
-
}
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
**Example (safe):**
|
|
359
|
-
```typescript
|
|
360
|
-
const ALLOWED_HOSTS = ["api.example.com", "cdn.example.com"];
|
|
361
|
-
|
|
362
|
-
export async function GET(request: Request) {
|
|
363
|
-
const { searchParams } = new URL(request.url);
|
|
364
|
-
const url = searchParams.get("url");
|
|
365
|
-
const parsed = new URL(url);
|
|
366
|
-
if (!ALLOWED_HOSTS.includes(parsed.hostname)) {
|
|
367
|
-
return Response.json({ error: "Invalid host" }, { status: 400 });
|
|
368
|
-
}
|
|
369
|
-
const response = await fetch(url);
|
|
370
|
-
return Response.json(await response.json());
|
|
371
|
-
}
|
|
372
|
-
```
|
|
238
|
+
| Rule ID | Title | Severity |
|
|
239
|
+
|---------|-------|----------|
|
|
240
|
+
| VC-SC-001 | Unpinned Dependencies | Medium |
|
|
241
|
+
| VC-SC-002 | Suspicious Postinstall Scripts | High |
|
|
242
|
+
| VC-SC-003 | Deprecated Packages | Low |
|
|
373
243
|
|
|
374
|
-
|
|
244
|
+
### Abuse & Compute
|
|
375
245
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
#### VC-HALL-001: Security Library Imported But Not Used
|
|
381
|
-
|
|
382
|
-
**Severity:** Medium
|
|
383
|
-
**Category:** middleware
|
|
384
|
-
|
|
385
|
-
Detects security libraries (helmet, cors, csurf, etc.) that are imported but the import is never used.
|
|
386
|
-
|
|
387
|
-
**What it looks for:**
|
|
388
|
-
- Imports from security packages: helmet, cors, csurf, express-rate-limit, hpp, etc.
|
|
389
|
-
- Import identifier not referenced after the import statement
|
|
390
|
-
|
|
391
|
-
**Example (vulnerable):**
|
|
392
|
-
```typescript
|
|
393
|
-
import helmet from "helmet"; // Imported but never used!
|
|
394
|
-
import cors from "cors";
|
|
395
|
-
|
|
396
|
-
const app = express();
|
|
397
|
-
app.use(cors());
|
|
398
|
-
// Missing: app.use(helmet());
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
---
|
|
402
|
-
|
|
403
|
-
#### VC-HALL-002: NextAuth Imported But Not Enforced
|
|
404
|
-
|
|
405
|
-
**Severity:** High
|
|
406
|
-
**Category:** auth
|
|
407
|
-
|
|
408
|
-
Detects next-auth imported but `getServerSession` never called, suggesting auth is configured but not enforced.
|
|
409
|
-
|
|
410
|
-
**What it looks for:**
|
|
411
|
-
- Imports from `next-auth` or `next-auth/next`
|
|
412
|
-
- No calls to `getServerSession` anywhere in the file
|
|
413
|
-
|
|
414
|
-
---
|
|
415
|
-
|
|
416
|
-
## Phase 2 Rules
|
|
417
|
-
|
|
418
|
-
### Network Pack (Extended)
|
|
419
|
-
|
|
420
|
-
#### VC-NET-002: Open Redirect
|
|
421
|
-
|
|
422
|
-
**Severity:** High
|
|
423
|
-
**Category:** network
|
|
424
|
-
|
|
425
|
-
Detects server-side redirects where user-controlled input determines the destination.
|
|
426
|
-
|
|
427
|
-
**Two-signal requirement:** Must identify user-controlled source AND redirect call uses that value.
|
|
428
|
-
|
|
429
|
-
**Example (vulnerable):**
|
|
430
|
-
```typescript
|
|
431
|
-
export async function GET(request: Request) {
|
|
432
|
-
const { searchParams } = new URL(request.url);
|
|
433
|
-
const next = searchParams.get("next");
|
|
434
|
-
return NextResponse.redirect(next!); // Open redirect!
|
|
435
|
-
}
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
---
|
|
439
|
-
|
|
440
|
-
#### VC-NET-003: Over-permissive CORS with Credentials
|
|
441
|
-
|
|
442
|
-
**Severity:** High
|
|
443
|
-
**Category:** network
|
|
444
|
-
|
|
445
|
-
Detects CORS configurations that combine `origin: "*"` with `credentials: true`.
|
|
446
|
-
|
|
447
|
-
**Example (vulnerable):**
|
|
448
|
-
```typescript
|
|
449
|
-
cors({ origin: "*", credentials: true }) // Dangerous combination!
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
---
|
|
453
|
-
|
|
454
|
-
#### VC-NET-004: Missing Request Timeout
|
|
455
|
-
|
|
456
|
-
**Severity:** Low
|
|
457
|
-
**Category:** network
|
|
458
|
-
|
|
459
|
-
Detects fetch/axios calls without timeout in API route handlers.
|
|
460
|
-
|
|
461
|
-
**Example (vulnerable):**
|
|
462
|
-
```typescript
|
|
463
|
-
const response = await fetch("https://external-api.com/data"); // No timeout!
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
---
|
|
467
|
-
|
|
468
|
-
### Middleware Pack
|
|
469
|
-
|
|
470
|
-
#### VC-RATE-001: Missing Rate Limiting
|
|
471
|
-
|
|
472
|
-
**Severity:** Medium
|
|
473
|
-
**Category:** middleware
|
|
474
|
-
**Confidence:** 0.65
|
|
475
|
-
|
|
476
|
-
Detects unauthenticated state-changing endpoints without rate limiting.
|
|
477
|
-
|
|
478
|
-
**What it looks for:**
|
|
479
|
-
- POST/PUT/PATCH/DELETE handlers without auth checks
|
|
480
|
-
- Handlers with database writes or sensitive operations
|
|
481
|
-
- No rate limiting signals in handler or middleware
|
|
482
|
-
|
|
483
|
-
---
|
|
484
|
-
|
|
485
|
-
### Validation Pack (Extended)
|
|
486
|
-
|
|
487
|
-
#### VC-VAL-002: Client-Side Only Validation
|
|
488
|
-
|
|
489
|
-
**Severity:** Medium
|
|
490
|
-
**Category:** validation
|
|
491
|
-
|
|
492
|
-
Detects validation in frontend components but missing in API routes.
|
|
493
|
-
|
|
494
|
-
---
|
|
495
|
-
|
|
496
|
-
### Privacy Pack (Extended)
|
|
497
|
-
|
|
498
|
-
#### VC-PRIV-002: Over-broad API Response
|
|
499
|
-
|
|
500
|
-
**Severity:** Medium/High
|
|
501
|
-
**Category:** privacy
|
|
502
|
-
|
|
503
|
-
Detects Prisma queries returning full models without `select` restrictions.
|
|
504
|
-
|
|
505
|
-
**Example (vulnerable):**
|
|
506
|
-
```typescript
|
|
507
|
-
const users = await prisma.user.findMany(); // Returns password hash!
|
|
508
|
-
return Response.json(users);
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
**Example (safe):**
|
|
512
|
-
```typescript
|
|
513
|
-
const users = await prisma.user.findMany({
|
|
514
|
-
select: { id: true, name: true, email: true }
|
|
515
|
-
});
|
|
516
|
-
return Response.json(users);
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
---
|
|
520
|
-
|
|
521
|
-
#### VC-PRIV-003: Debug Flags in Production
|
|
522
|
-
|
|
523
|
-
**Severity:** Medium
|
|
524
|
-
**Category:** config
|
|
525
|
-
|
|
526
|
-
Detects `debug: true` or `dev: true` in config files without NODE_ENV guards.
|
|
527
|
-
|
|
528
|
-
---
|
|
529
|
-
|
|
530
|
-
### Crypto Pack
|
|
531
|
-
|
|
532
|
-
#### VC-CRYPTO-001: Math.random for Tokens
|
|
533
|
-
|
|
534
|
-
**Severity:** High
|
|
535
|
-
**Category:** crypto
|
|
536
|
-
|
|
537
|
-
Detects Math.random used to generate tokens, keys, or session IDs.
|
|
538
|
-
|
|
539
|
-
**Example (vulnerable):**
|
|
540
|
-
```typescript
|
|
541
|
-
const token = Math.random().toString(36).substring(2); // Predictable!
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
**Example (safe):**
|
|
545
|
-
```typescript
|
|
546
|
-
const token = crypto.randomBytes(32).toString('hex');
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
---
|
|
550
|
-
|
|
551
|
-
#### VC-CRYPTO-002: JWT Decode Without Verify
|
|
552
|
-
|
|
553
|
-
**Severity:** Critical
|
|
554
|
-
**Category:** crypto
|
|
555
|
-
|
|
556
|
-
Detects jwt.decode() used without jwt.verify() in the same file.
|
|
557
|
-
|
|
558
|
-
**Example (vulnerable):**
|
|
559
|
-
```typescript
|
|
560
|
-
const payload = jwt.decode(token); // Signature not verified!
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
**Example (safe):**
|
|
564
|
-
```typescript
|
|
565
|
-
const payload = jwt.verify(token, secret); // Signature verified
|
|
566
|
-
```
|
|
567
|
-
|
|
568
|
-
---
|
|
569
|
-
|
|
570
|
-
#### VC-CRYPTO-003: Weak Password Hashing
|
|
571
|
-
|
|
572
|
-
**Severity:** High
|
|
573
|
-
**Category:** crypto
|
|
574
|
-
|
|
575
|
-
Detects MD5/SHA1 for passwords or bcrypt with saltRounds < 10.
|
|
576
|
-
|
|
577
|
-
**Example (vulnerable):**
|
|
578
|
-
```typescript
|
|
579
|
-
crypto.createHash('md5').update(password).digest('hex'); // Weak!
|
|
580
|
-
bcrypt.hash(password, 5); // Too few rounds!
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
---
|
|
584
|
-
|
|
585
|
-
### Uploads Pack
|
|
586
|
-
|
|
587
|
-
#### VC-UP-001: File Upload Without Constraints
|
|
588
|
-
|
|
589
|
-
**Severity:** High
|
|
590
|
-
**Category:** uploads
|
|
591
|
-
|
|
592
|
-
Detects file uploads without size or type validation.
|
|
593
|
-
|
|
594
|
-
**Example (vulnerable):**
|
|
595
|
-
```typescript
|
|
596
|
-
const file = formData.get('file') as File;
|
|
597
|
-
// No size or type check before processing
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
---
|
|
601
|
-
|
|
602
|
-
#### VC-UP-002: Upload to Public Path
|
|
603
|
-
|
|
604
|
-
**Severity:** High
|
|
605
|
-
**Category:** uploads
|
|
606
|
-
|
|
607
|
-
Detects uploaded files written to public directories.
|
|
608
|
-
|
|
609
|
-
**Example (vulnerable):**
|
|
610
|
-
```typescript
|
|
611
|
-
fs.writeFileSync(`public/uploads/${filename}`, buffer); // Publicly accessible!
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
---
|
|
615
|
-
|
|
616
|
-
## Phase 3: Hallucination Detection Engine
|
|
617
|
-
|
|
618
|
-
Advanced cross-file analysis for detecting security intent vs implementation gaps.
|
|
619
|
-
|
|
620
|
-
### Intent Command
|
|
621
|
-
|
|
622
|
-
Generate a security intent map baseline for your codebase:
|
|
623
|
-
|
|
624
|
-
```bash
|
|
625
|
-
# Generate intent map
|
|
626
|
-
vibecheck intent ./my-project --out intent-map.json
|
|
627
|
-
|
|
628
|
-
# Include intent map in scan output
|
|
629
|
-
vibecheck scan ./my-project --emit-intent-map
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
### Hallucinations Pack (Phase 3)
|
|
633
|
-
|
|
634
|
-
#### VC-HALL-010: Comment Claims Protection But Unproven
|
|
635
|
-
|
|
636
|
-
**Severity:** Medium
|
|
637
|
-
**Category:** hallucinations
|
|
638
|
-
**Confidence:** 0.75
|
|
639
|
-
|
|
640
|
-
Detects comments that claim security protection but the implementation doesn't prove it.
|
|
641
|
-
|
|
642
|
-
**Example (vulnerable):**
|
|
643
|
-
```typescript
|
|
644
|
-
// This route is protected by authentication middleware
|
|
645
|
-
export async function POST(request: Request) {
|
|
646
|
-
// No auth check here, and middleware doesn't cover /api routes
|
|
647
|
-
await prisma.user.create({ data: body });
|
|
648
|
-
}
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
---
|
|
652
|
-
|
|
653
|
-
#### VC-HALL-011: Middleware Assumed But Not Matching
|
|
654
|
-
|
|
655
|
-
**Severity:** High
|
|
656
|
-
**Category:** hallucinations
|
|
657
|
-
**Confidence:** 0.70
|
|
658
|
-
|
|
659
|
-
Detects routes that expect middleware protection but are not covered by matcher patterns.
|
|
660
|
-
|
|
661
|
-
**Example (vulnerable):**
|
|
662
|
-
```typescript
|
|
663
|
-
// middleware.ts
|
|
664
|
-
export const config = {
|
|
665
|
-
matcher: ['/dashboard/:path*'], // Missing /api routes!
|
|
666
|
-
};
|
|
667
|
-
|
|
668
|
-
// app/api/users/route.ts
|
|
669
|
-
// Auth handled by middleware (but middleware doesn't cover this!)
|
|
670
|
-
export async function DELETE(request: Request) {
|
|
671
|
-
await prisma.user.delete({ where: { id } });
|
|
672
|
-
}
|
|
673
|
-
```
|
|
674
|
-
|
|
675
|
-
---
|
|
676
|
-
|
|
677
|
-
#### VC-HALL-012: Validation Claimed But Missing/Ignored
|
|
678
|
-
|
|
679
|
-
**Severity:** Medium
|
|
680
|
-
**Category:** hallucinations
|
|
681
|
-
**Confidence:** 0.80
|
|
682
|
-
|
|
683
|
-
Detects validation that is claimed but not properly implemented or used.
|
|
684
|
-
|
|
685
|
-
**Example (vulnerable):**
|
|
686
|
-
```typescript
|
|
687
|
-
const schema = z.object({ name: z.string() });
|
|
688
|
-
schema.parse(body); // Result ignored!
|
|
689
|
-
await prisma.user.create({ data: body }); // Uses raw body
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
---
|
|
693
|
-
|
|
694
|
-
#### VC-AUTH-010: Auth-by-UI with Server Gap
|
|
695
|
-
|
|
696
|
-
**Severity:** Critical
|
|
697
|
-
**Category:** auth
|
|
698
|
-
**Confidence:** 0.85
|
|
699
|
-
|
|
700
|
-
Detects client-side auth checks without corresponding server-side protection.
|
|
701
|
-
|
|
702
|
-
**Example (vulnerable):**
|
|
703
|
-
```tsx
|
|
704
|
-
// Client component
|
|
705
|
-
const { session } = useSession();
|
|
706
|
-
if (session) {
|
|
707
|
-
// Only render if logged in
|
|
708
|
-
await fetch('/api/users', { method: 'DELETE' }); // Server has no auth!
|
|
709
|
-
}
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
---
|
|
713
|
-
|
|
714
|
-
### Coverage Metrics
|
|
715
|
-
|
|
716
|
-
With `--emit-intent-map`, the scan artifact includes coverage metrics:
|
|
717
|
-
|
|
718
|
-
- **authCoverage**: Percentage of state-changing routes with proven auth
|
|
719
|
-
- **validationCoverage**: Percentage of routes with request bodies that have validation
|
|
720
|
-
- **middlewareCoverage**: Percentage of routes covered by middleware matchers
|
|
721
|
-
|
|
722
|
-
### Intent Map Structure
|
|
723
|
-
|
|
724
|
-
```json
|
|
725
|
-
{
|
|
726
|
-
"routeMap": [
|
|
727
|
-
{
|
|
728
|
-
"routeId": "abc123",
|
|
729
|
-
"method": "POST",
|
|
730
|
-
"path": "/api/users",
|
|
731
|
-
"file": "app/api/users/route.ts",
|
|
732
|
-
"startLine": 10,
|
|
733
|
-
"endLine": 25
|
|
734
|
-
}
|
|
735
|
-
],
|
|
736
|
-
"middlewareMap": [
|
|
737
|
-
{
|
|
738
|
-
"file": "middleware.ts",
|
|
739
|
-
"matchers": ["/api/:path*"],
|
|
740
|
-
"protectsApi": true,
|
|
741
|
-
"startLine": 15
|
|
742
|
-
}
|
|
743
|
-
],
|
|
744
|
-
"intentMap": [
|
|
745
|
-
{
|
|
746
|
-
"intentId": "def456",
|
|
747
|
-
"type": "AUTH_ENFORCED",
|
|
748
|
-
"scope": "route",
|
|
749
|
-
"source": "comment",
|
|
750
|
-
"textEvidence": "// Protected by auth"
|
|
751
|
-
}
|
|
752
|
-
],
|
|
753
|
-
"proofTraces": {
|
|
754
|
-
"abc123": {
|
|
755
|
-
"routeId": "abc123",
|
|
756
|
-
"authProven": true,
|
|
757
|
-
"validationProven": false,
|
|
758
|
-
"middlewareCovered": true,
|
|
759
|
-
"steps": [...]
|
|
760
|
-
}
|
|
761
|
-
},
|
|
762
|
-
"coverage": {
|
|
763
|
-
"authCoverage": 0.85,
|
|
764
|
-
"validationCoverage": 0.60,
|
|
765
|
-
"middlewareCoverage": 1.0
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
```
|
|
769
|
-
|
|
770
|
-
---
|
|
246
|
+
| Rule ID | Title | Severity |
|
|
247
|
+
|---------|-------|----------|
|
|
248
|
+
| VC-ABUSE-001 | Unbounded AI API Calls | High |
|
|
249
|
+
| VC-ABUSE-002 | Missing Cost Controls | Medium |
|
|
771
250
|
|
|
772
251
|
## Output Formats
|
|
773
252
|
|
|
@@ -779,11 +258,11 @@ The default format, defined by `@vibecheck/schema`:
|
|
|
779
258
|
|
|
780
259
|
```json
|
|
781
260
|
{
|
|
782
|
-
"artifactVersion": "0.
|
|
261
|
+
"artifactVersion": "0.3",
|
|
783
262
|
"generatedAt": "2024-01-15T10:30:00.000Z",
|
|
784
263
|
"tool": {
|
|
785
264
|
"name": "vibecheck",
|
|
786
|
-
"version": "
|
|
265
|
+
"version": "0.3.2"
|
|
787
266
|
},
|
|
788
267
|
"summary": {
|
|
789
268
|
"totalFindings": 2,
|
|
@@ -824,21 +303,79 @@ The default format, defined by `@vibecheck/schema`:
|
|
|
824
303
|
|
|
825
304
|
### SARIF Format
|
|
826
305
|
|
|
827
|
-
|
|
306
|
+
SARIF (Static Analysis Results Interchange Format) is an OASIS standard for static analysis tools. Use `--format sarif` to generate SARIF 2.1.0 output, compatible with:
|
|
828
307
|
|
|
829
|
-
- **GitHub Code Scanning**
|
|
830
|
-
- **Azure DevOps**
|
|
831
|
-
- **VS Code**
|
|
832
|
-
- **Other tools**
|
|
308
|
+
- **GitHub Code Scanning** — Upload via `github/codeql-action/upload-sarif`
|
|
309
|
+
- **Azure DevOps** — Native SARIF support in security reports
|
|
310
|
+
- **VS Code** — SARIF Viewer extension
|
|
311
|
+
- **Other tools** — Any SARIF 2.1.0 compatible viewer
|
|
833
312
|
|
|
834
313
|
```bash
|
|
835
314
|
# Generate SARIF for GitHub Code Scanning
|
|
836
315
|
vibecheck scan --format sarif --out results.sarif
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## CI/CD Integration
|
|
319
|
+
|
|
320
|
+
### GitHub Actions Example
|
|
837
321
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
322
|
+
```yaml
|
|
323
|
+
name: Security Scan
|
|
324
|
+
|
|
325
|
+
on: [push, pull_request]
|
|
326
|
+
|
|
327
|
+
jobs:
|
|
328
|
+
vibecheck:
|
|
329
|
+
runs-on: ubuntu-latest
|
|
330
|
+
steps:
|
|
331
|
+
- uses: actions/checkout@v4
|
|
332
|
+
|
|
333
|
+
- name: Setup Node.js
|
|
334
|
+
uses: actions/setup-node@v4
|
|
335
|
+
with:
|
|
336
|
+
node-version: '20'
|
|
337
|
+
|
|
338
|
+
- name: Run VibeCheck
|
|
339
|
+
run: npx @quantracode/vibecheck scan --format sarif --out results.sarif --fail-on high
|
|
340
|
+
|
|
341
|
+
- name: Upload SARIF
|
|
342
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
343
|
+
if: always()
|
|
344
|
+
with:
|
|
345
|
+
sarif_file: results.sarif
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Policy Evaluation
|
|
349
|
+
|
|
350
|
+
Compare scans against baselines for regression detection:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
# Evaluate against startup profile
|
|
354
|
+
vibecheck evaluate \
|
|
355
|
+
--artifact vibecheck-scan.json \
|
|
356
|
+
--profile startup \
|
|
357
|
+
--out policy-report.json
|
|
358
|
+
|
|
359
|
+
# Compare against baseline (regression detection)
|
|
360
|
+
vibecheck evaluate \
|
|
361
|
+
--artifact vibecheck-scan.json \
|
|
362
|
+
--baseline main-branch-scan.json \
|
|
363
|
+
--profile enterprise
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Available profiles: `startup`, `growth`, `enterprise`
|
|
367
|
+
|
|
368
|
+
## Intent Map & Coverage Metrics
|
|
369
|
+
|
|
370
|
+
With `--emit-intent-map`, the scan artifact includes coverage metrics:
|
|
371
|
+
|
|
372
|
+
- **authCoverage**: Percentage of state-changing routes with proven auth
|
|
373
|
+
- **validationCoverage**: Percentage of routes with request bodies that have validation
|
|
374
|
+
- **middlewareCoverage**: Percentage of routes covered by middleware matchers
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
# Generate intent map
|
|
378
|
+
vibecheck scan ./my-project --emit-intent-map --out scan.json
|
|
842
379
|
```
|
|
843
380
|
|
|
844
381
|
## Architecture
|
|
@@ -847,57 +384,27 @@ vibecheck scan --format sarif --out results.sarif
|
|
|
847
384
|
packages/cli/src/
|
|
848
385
|
├── commands/
|
|
849
386
|
│ ├── scan.ts # Main scan orchestrator
|
|
387
|
+
│ ├── evaluate.ts # Policy evaluation
|
|
850
388
|
│ └── explain.ts # Report viewer
|
|
851
389
|
├── scanners/
|
|
852
390
|
│ ├── types.ts # ScanContext, types
|
|
853
391
|
│ ├── helpers/
|
|
854
392
|
│ │ ├── ast-helpers.ts # ts-morph utilities
|
|
855
393
|
│ │ └── context-builder.ts # ScanContext factory
|
|
856
|
-
│ ├── auth/
|
|
857
|
-
│
|
|
858
|
-
│
|
|
859
|
-
│ ├──
|
|
860
|
-
│
|
|
861
|
-
│ ├──
|
|
862
|
-
│
|
|
863
|
-
│ ├──
|
|
864
|
-
│
|
|
865
|
-
│
|
|
866
|
-
│
|
|
867
|
-
│ │ └── ssrf-prone-fetch.ts # VC-NET-001
|
|
868
|
-
│ └── hallucinations/
|
|
869
|
-
│ └── unused-security-imports.ts # VC-HALL-001, VC-HALL-002
|
|
394
|
+
│ ├── auth/ # Auth & authorization scanners
|
|
395
|
+
│ ├── validation/ # Input validation scanners
|
|
396
|
+
│ ├── privacy/ # Privacy & data scanners
|
|
397
|
+
│ ├── config/ # Config & secrets scanners
|
|
398
|
+
│ ├── network/ # Network security scanners
|
|
399
|
+
│ ├── crypto/ # Cryptography scanners
|
|
400
|
+
│ ├── uploads/ # File upload scanners
|
|
401
|
+
│ ├── middleware/ # Middleware scanners
|
|
402
|
+
│ ├── hallucinations/ # AI hallucination scanners
|
|
403
|
+
│ ├── supply-chain/ # Supply chain scanners
|
|
404
|
+
│ └── abuse/ # Abuse & compute scanners
|
|
870
405
|
└── index.ts
|
|
871
406
|
```
|
|
872
407
|
|
|
873
|
-
## Design Principles
|
|
874
|
-
|
|
875
|
-
1. **Deterministic** - No LLM calls, results are reproducible
|
|
876
|
-
2. **Local-only** - All analysis runs on your machine
|
|
877
|
-
3. **Low false positives** - Precision over recall
|
|
878
|
-
4. **Framework-aware** - Built for Next.js, Express patterns
|
|
879
|
-
5. **Schema-compliant** - Output conforms to `@vibecheck/schema`
|
|
880
|
-
|
|
881
|
-
## Adding New Scanners
|
|
882
|
-
|
|
883
|
-
Each scanner must:
|
|
884
|
-
|
|
885
|
-
1. Accept a `ScanContext` with repo info and AST helpers
|
|
886
|
-
2. Return `Finding[]` conforming to the schema
|
|
887
|
-
3. Generate deterministic fingerprints for deduplication
|
|
888
|
-
4. Include clear evidence with file locations
|
|
889
|
-
|
|
890
|
-
```typescript
|
|
891
|
-
import { type ScanContext } from "../types.js";
|
|
892
|
-
import { type Finding } from "@vibecheck/schema";
|
|
893
|
-
|
|
894
|
-
export async function scanMyRule(ctx: ScanContext): Promise<Finding[]> {
|
|
895
|
-
const findings: Finding[] = [];
|
|
896
|
-
// ... detection logic
|
|
897
|
-
return findings;
|
|
898
|
-
}
|
|
899
|
-
```
|
|
900
|
-
|
|
901
408
|
## License
|
|
902
409
|
|
|
903
410
|
MIT
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -547,7 +547,7 @@ function validateArtifact(json) {
|
|
|
547
547
|
}
|
|
548
548
|
|
|
549
549
|
// src/constants.ts
|
|
550
|
-
var CLI_VERSION = "0.3.
|
|
550
|
+
var CLI_VERSION = "0.3.2";
|
|
551
551
|
|
|
552
552
|
// src/utils/file-utils.ts
|
|
553
553
|
import fs from "fs";
|
|
@@ -5725,7 +5725,7 @@ async function scanPostinstallScripts(context) {
|
|
|
5725
5725
|
startLine: 1,
|
|
5726
5726
|
endLine: 1,
|
|
5727
5727
|
snippet: `"${scriptKey}": "${script.length > 100 ? script.slice(0, 100) + "..." : script}"`,
|
|
5728
|
-
|
|
5728
|
+
label: "Install script definition"
|
|
5729
5729
|
}
|
|
5730
5730
|
],
|
|
5731
5731
|
remediation: {
|
|
@@ -5789,7 +5789,7 @@ async function scanVersionRanges(context) {
|
|
|
5789
5789
|
startLine: 1,
|
|
5790
5790
|
endLine: 1,
|
|
5791
5791
|
snippet: `"${name}": "${version}"`,
|
|
5792
|
-
|
|
5792
|
+
label: `${isDevDep ? "devDependencies" : "dependencies"} - ${criticalInfo.reason}`
|
|
5793
5793
|
}
|
|
5794
5794
|
],
|
|
5795
5795
|
remediation: {
|
|
@@ -6076,7 +6076,7 @@ async function scanDeprecatedPackages(context) {
|
|
|
6076
6076
|
startLine: 1,
|
|
6077
6077
|
endLine: 1,
|
|
6078
6078
|
snippet: `"${name}": "${version}"`,
|
|
6079
|
-
|
|
6079
|
+
label: isDevDep ? "devDependencies" : "dependencies"
|
|
6080
6080
|
}
|
|
6081
6081
|
],
|
|
6082
6082
|
remediation: {
|
|
@@ -6158,7 +6158,7 @@ Detected packages:
|
|
|
6158
6158
|
startLine: 1,
|
|
6159
6159
|
endLine: 1,
|
|
6160
6160
|
snippet: `"${pkgName}": "${allDeps[pkgName]}"`,
|
|
6161
|
-
|
|
6161
|
+
label: `${getGroupName(group)} authentication library`
|
|
6162
6162
|
});
|
|
6163
6163
|
}
|
|
6164
6164
|
}
|
|
@@ -6294,7 +6294,7 @@ async function scanSuspiciousScripts(context) {
|
|
|
6294
6294
|
startLine: 1,
|
|
6295
6295
|
endLine: 1,
|
|
6296
6296
|
snippet: `${name}@${pkg.version} (hasInstallScripts: true)`,
|
|
6297
|
-
|
|
6297
|
+
label: isDirect ? "Direct dependency" : "Transitive dependency"
|
|
6298
6298
|
},
|
|
6299
6299
|
...scriptContent ? [
|
|
6300
6300
|
{
|
|
@@ -6302,7 +6302,7 @@ async function scanSuspiciousScripts(context) {
|
|
|
6302
6302
|
startLine: 1,
|
|
6303
6303
|
endLine: 1,
|
|
6304
6304
|
snippet: scriptContent.length > 200 ? scriptContent.slice(0, 200) + "..." : scriptContent,
|
|
6305
|
-
|
|
6305
|
+
label: "Install script content"
|
|
6306
6306
|
}
|
|
6307
6307
|
] : []
|
|
6308
6308
|
],
|