@quantracode/vibecheck 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,903 +1,903 @@
1
- # @quantracode/vibecheck
2
-
3
- A deterministic, local-only security scanner for modern web applications. Designed to catch common security issues in Next.js, Express, and other Node.js projects with high precision and low false positives.
4
-
5
- ## Quickstart (No Install)
6
-
7
- Run VibeCheck instantly without installation:
8
-
9
- ```bash
10
- # Using npx
11
- npx @quantracode/vibecheck scan --fail-on off --out vibecheck-scan.json
12
-
13
- # Using pnpm dlx
14
- pnpm dlx @quantracode/vibecheck scan --fail-on off --out vibecheck-scan.json
15
- ```
16
-
17
- ### Scan Another Folder
18
-
19
- ```bash
20
- npx @quantracode/vibecheck scan --target ../my-other-app --out scan.json
21
- ```
22
-
23
- ## Installation
24
-
25
- ```bash
26
- npm install -g @quantracode/vibecheck
27
- # or
28
- pnpm add -g @quantracode/vibecheck
29
- ```
30
-
31
- ## Usage
32
-
33
- ```bash
34
- # Scan current directory
35
- vibecheck scan
36
-
37
- # Scan specific directory with output file
38
- vibecheck scan ./my-project --out ./reports/scan.json
39
-
40
- # Use --target as alternative to positional argument
41
- vibecheck scan --target ./my-project
42
-
43
- # Output in SARIF format (for GitHub Code Scanning)
44
- vibecheck scan --format sarif
45
-
46
- # Output both JSON and SARIF
47
- vibecheck scan --format both
48
-
49
- # Fail CI if medium or higher findings
50
- vibecheck scan --fail-on medium
51
-
52
- # Disable fail threshold (always exit 0)
53
- vibecheck scan --fail-on off
54
-
55
- # Exclude specific directories
56
- vibecheck scan -e "**/legacy/**" -e "**/vendor/**"
57
-
58
- # Include test files in scan (excluded by default)
59
- vibecheck scan --include-tests
60
-
61
- # Generate intent map with coverage metrics
62
- vibecheck scan --emit-intent-map
63
-
64
- # Explain a scan report
65
- vibecheck explain ./scan.json
66
-
67
- # Start the web viewer
68
- vibecheck view
69
-
70
- # Open viewer with specific artifact
71
- vibecheck view -a ./scan.json
72
- ```
73
-
74
- ## View Command
75
-
76
- The `view` command starts a local web viewer to explore scan results interactively.
77
-
78
- ```bash
79
- # Start viewer (auto-detects artifacts in current directory)
80
- vibecheck view
81
-
82
- # Specify artifact file explicitly
83
- vibecheck view -a ./vibecheck-scan.json
84
- vibecheck view --artifact ./scan-results.json
85
-
86
- # Use a different port
87
- vibecheck view --port 8080
88
-
89
- # Don't auto-open browser
90
- vibecheck view --no-open
91
-
92
- # Force update the viewer to latest version
93
- vibecheck view --update
94
-
95
- # Clear cached viewer files
96
- vibecheck view --clear-cache
97
- ```
98
-
99
- ### View Command Options
100
-
101
- | Option | Description | Default |
102
- |--------|-------------|---------|
103
- | `-p, --port <port>` | Port to run the viewer on | `3000` |
104
- | `-a, --artifact <path>` | Path to artifact file to open | Auto-detected |
105
- | `--no-open` | Don't automatically open the browser | Opens browser |
106
- | `--update` | Force update the viewer to latest version | - |
107
- | `--clear-cache` | Clear the cached viewer and exit | - |
108
-
109
- ### How It Works
110
-
111
- 1. **Auto-download**: The viewer is automatically downloaded from npm on first run and cached in `~/.vibecheck/viewer/`
112
- 2. **Auto-detect artifacts**: Looks for scan artifacts in common locations:
113
- - `vibecheck-artifacts/artifact.json`
114
- - `vibecheck-artifact.json`
115
- - `.vibecheck/artifact.json`
116
- - `scan-results.json`
117
- 3. **Auto-load**: When an artifact is detected, it's automatically loaded into the viewer
118
- 4. **Local-only**: The viewer runs entirely on your machine with no external connections
119
-
120
- ### Workflow Example
121
-
122
- ```bash
123
- # 1. Run a scan
124
- vibecheck scan --out vibecheck-artifacts/artifact.json
125
-
126
- # 2. View results (artifact auto-detected)
127
- vibecheck view
128
-
129
- # Browser opens to http://localhost:3000 with results loaded
130
- ```
131
-
132
- ### Command Line Options
133
-
134
- | Option | Description | Default |
135
- |--------|-------------|---------|
136
- | `-t, --target <path>` | Target directory to scan | Current directory |
137
- | `-o, --out <path>` | Output file or directory | `vibecheck-artifacts/vibecheck-scan.json` |
138
- | `-f, --format <format>` | Output format: `json`, `sarif`, or `both` | `json` |
139
- | `--repo-name <name>` | Override repository name | Auto-detected |
140
- | `--fail-on <threshold>` | Exit with non-zero if findings >= threshold | `high` |
141
- | `-e, --exclude <glob>` | Glob pattern to exclude (repeatable) | See below |
142
- | `--include-tests` | Include test files in scan | `false` |
143
- | `--emit-intent-map` | Include route map and coverage metrics | `false` |
144
- | `--changed` | Only scan changed files (not implemented) | `false` |
145
-
146
- ### Default Excludes
147
-
148
- The following patterns are excluded by default:
149
-
150
- **Core excludes (always applied):**
151
- - `node_modules`, `dist`, `.git`, `build`, `.next`, `coverage`
152
- - `.turbo`, `.cache`, `out`, `.vercel`, `.netlify`
153
-
154
- **Test excludes (skipped with `--include-tests`):**
155
- - `__tests__/**`, `*.test.*`, `*.spec.*`
156
- - `test/`, `tests/`, `fixtures/`, `__mocks__/`, `__fixtures__/`
157
- - `cypress/`, `e2e/`, `*.stories.*`
158
-
159
- ## Scanner Packs
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
- ```
232
-
233
- ---
234
-
235
- ### Validation Pack
236
-
237
- Rules for input validation issues.
238
-
239
- #### VC-VAL-001: Validation Defined But Output Ignored
240
-
241
- **Severity:** Medium
242
- **Category:** validation
243
-
244
- Detects cases where validation libraries (Zod, Yup, Joi) are called but the validated result is not used.
245
-
246
- **What it looks for:**
247
- - Calls to `.parse()`, `.validate()`, `.parseAsync()`, etc.
248
- - Result not assigned to a variable
249
- - Raw `request.body` or `req.body` used after validation
250
-
251
- **Example (vulnerable):**
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
- ```
264
-
265
- ---
266
-
267
- ### Privacy Pack
268
-
269
- Rules for data privacy and logging issues.
270
-
271
- #### VC-PRIV-001: Sensitive Data Logged
272
-
273
- **Severity:** High
274
- **Category:** privacy
275
-
276
- Detects logging statements that include sensitive variable names.
277
-
278
- **What it looks for:**
279
- - `console.log`, `console.info`, `console.debug`, `logger.info`, etc.
280
- - Variables containing: password, secret, token, apiKey, creditCard, ssn, etc.
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
- ```
291
-
292
- ---
293
-
294
- ### Config Pack
295
-
296
- Rules for configuration and secrets management issues.
297
-
298
- #### VC-CONFIG-001: Undocumented Environment Variable
299
-
300
- **Severity:** Low
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
- ```
323
-
324
- **Example (safe):**
325
- ```typescript
326
- const jwtSecret = process.env.JWT_SECRET;
327
- if (!jwtSecret) throw new Error("JWT_SECRET is required");
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
- ```
373
-
374
- ---
375
-
376
- ### Hallucinations Pack
377
-
378
- Rules for detecting security libraries that are imported but not properly used.
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
- ---
771
-
772
- ## Output Formats
773
-
774
- VibeCheck supports two output formats: JSON and SARIF.
775
-
776
- ### JSON Format
777
-
778
- The default format, defined by `@vibecheck/schema`:
779
-
780
- ```json
781
- {
782
- "artifactVersion": "0.1",
783
- "generatedAt": "2024-01-15T10:30:00.000Z",
784
- "tool": {
785
- "name": "vibecheck",
786
- "version": "1.0.0"
787
- },
788
- "summary": {
789
- "totalFindings": 2,
790
- "bySeverity": { "critical": 0, "high": 1, "medium": 1, "low": 0, "info": 0 },
791
- "byCategory": { "auth": 1, "validation": 1, ... }
792
- },
793
- "findings": [
794
- {
795
- "id": "f-abc123",
796
- "severity": "high",
797
- "confidence": 0.85,
798
- "category": "auth",
799
- "ruleId": "VC-AUTH-001",
800
- "title": "Missing authentication on POST /api/users",
801
- "description": "...",
802
- "evidence": [
803
- {
804
- "file": "app/api/users/route.ts",
805
- "startLine": 25,
806
- "endLine": 30,
807
- "snippet": "...",
808
- "label": "Unprotected route handler"
809
- }
810
- ],
811
- "remediation": {
812
- "recommendedFix": "Add authentication middleware"
813
- },
814
- "fingerprint": "sha256:..."
815
- }
816
- ],
817
- "metrics": {
818
- "filesScanned": 50,
819
- "linesOfCode": 5000,
820
- "scanDurationMs": 1234
821
- }
822
- }
823
- ```
824
-
825
- ### SARIF Format
826
-
827
- [SARIF (Static Analysis Results Interchange Format)](https://sarifweb.azurewebsites.net/) is an OASIS standard for static analysis tools. Use `--format sarif` to generate SARIF 2.1.0 output, compatible with:
828
-
829
- - **GitHub Code Scanning** - Upload via `github/codeql-action/upload-sarif`
830
- - **Azure DevOps** - Native SARIF support in security reports
831
- - **VS Code** - SARIF Viewer extension
832
- - **Other tools** - Any SARIF 2.1.0 compatible viewer
833
-
834
- ```bash
835
- # Generate SARIF for GitHub Code Scanning
836
- vibecheck scan --format sarif --out results.sarif
837
-
838
- # Upload to GitHub (in CI workflow)
839
- - uses: github/codeql-action/upload-sarif@v2
840
- with:
841
- sarif_file: results.sarif
842
- ```
843
-
844
- ## Architecture
845
-
846
- ```
847
- packages/cli/src/
848
- ├── commands/
849
- │ ├── scan.ts # Main scan orchestrator
850
- │ └── explain.ts # Report viewer
851
- ├── scanners/
852
- │ ├── types.ts # ScanContext, types
853
- │ ├── helpers/
854
- │ │ ├── ast-helpers.ts # ts-morph utilities
855
- │ │ └── context-builder.ts # ScanContext factory
856
- │ ├── auth/
857
- │ │ ├── unprotected-api-route.ts # VC-AUTH-001
858
- │ │ └── middleware-gap.ts # VC-MW-001
859
- │ ├── validation/
860
- │ │ └── ignored-validation.ts # VC-VAL-001
861
- │ ├── privacy/
862
- │ │ └── sensitive-logging.ts # VC-PRIV-001
863
- │ ├── config/
864
- │ │ ├── undocumented-env.ts # VC-CONFIG-001
865
- │ │ └── insecure-defaults.ts # VC-CONFIG-002
866
- │ ├── network/
867
- │ │ └── ssrf-prone-fetch.ts # VC-NET-001
868
- │ └── hallucinations/
869
- │ └── unused-security-imports.ts # VC-HALL-001, VC-HALL-002
870
- └── index.ts
871
- ```
872
-
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
- ## License
902
-
903
- MIT
1
+ # @quantracode/vibecheck
2
+
3
+ A deterministic, local-only security scanner for modern web applications. Designed to catch common security issues in Next.js, Express, and other Node.js projects with high precision and low false positives.
4
+
5
+ ## Quickstart (No Install)
6
+
7
+ Run VibeCheck instantly without installation:
8
+
9
+ ```bash
10
+ # Using npx
11
+ npx @quantracode/vibecheck scan --fail-on off --out vibecheck-scan.json
12
+
13
+ # Using pnpm dlx
14
+ pnpm dlx @quantracode/vibecheck scan --fail-on off --out vibecheck-scan.json
15
+ ```
16
+
17
+ ### Scan Another Folder
18
+
19
+ ```bash
20
+ npx @quantracode/vibecheck scan --target ../my-other-app --out scan.json
21
+ ```
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install -g @quantracode/vibecheck
27
+ # or
28
+ pnpm add -g @quantracode/vibecheck
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ```bash
34
+ # Scan current directory
35
+ vibecheck scan
36
+
37
+ # Scan specific directory with output file
38
+ vibecheck scan ./my-project --out ./reports/scan.json
39
+
40
+ # Use --target as alternative to positional argument
41
+ vibecheck scan --target ./my-project
42
+
43
+ # Output in SARIF format (for GitHub Code Scanning)
44
+ vibecheck scan --format sarif
45
+
46
+ # Output both JSON and SARIF
47
+ vibecheck scan --format both
48
+
49
+ # Fail CI if medium or higher findings
50
+ vibecheck scan --fail-on medium
51
+
52
+ # Disable fail threshold (always exit 0)
53
+ vibecheck scan --fail-on off
54
+
55
+ # Exclude specific directories
56
+ vibecheck scan -e "**/legacy/**" -e "**/vendor/**"
57
+
58
+ # Include test files in scan (excluded by default)
59
+ vibecheck scan --include-tests
60
+
61
+ # Generate intent map with coverage metrics
62
+ vibecheck scan --emit-intent-map
63
+
64
+ # Explain a scan report
65
+ vibecheck explain ./scan.json
66
+
67
+ # Start the web viewer
68
+ vibecheck view
69
+
70
+ # Open viewer with specific artifact
71
+ vibecheck view -a ./scan.json
72
+ ```
73
+
74
+ ## View Command
75
+
76
+ The `view` command starts a local web viewer to explore scan results interactively.
77
+
78
+ ```bash
79
+ # Start viewer (auto-detects artifacts in current directory)
80
+ vibecheck view
81
+
82
+ # Specify artifact file explicitly
83
+ vibecheck view -a ./vibecheck-scan.json
84
+ vibecheck view --artifact ./scan-results.json
85
+
86
+ # Use a different port
87
+ vibecheck view --port 8080
88
+
89
+ # Don't auto-open browser
90
+ vibecheck view --no-open
91
+
92
+ # Force update the viewer to latest version
93
+ vibecheck view --update
94
+
95
+ # Clear cached viewer files
96
+ vibecheck view --clear-cache
97
+ ```
98
+
99
+ ### View Command Options
100
+
101
+ | Option | Description | Default |
102
+ |--------|-------------|---------|
103
+ | `-p, --port <port>` | Port to run the viewer on | `3000` |
104
+ | `-a, --artifact <path>` | Path to artifact file to open | Auto-detected |
105
+ | `--no-open` | Don't automatically open the browser | Opens browser |
106
+ | `--update` | Force update the viewer to latest version | - |
107
+ | `--clear-cache` | Clear the cached viewer and exit | - |
108
+
109
+ ### How It Works
110
+
111
+ 1. **Auto-download**: The viewer is automatically downloaded from npm on first run and cached in `~/.vibecheck/viewer/`
112
+ 2. **Auto-detect artifacts**: Looks for scan artifacts in common locations:
113
+ - `vibecheck-artifacts/artifact.json`
114
+ - `vibecheck-artifact.json`
115
+ - `.vibecheck/artifact.json`
116
+ - `scan-results.json`
117
+ 3. **Auto-load**: When an artifact is detected, it's automatically loaded into the viewer
118
+ 4. **Local-only**: The viewer runs entirely on your machine with no external connections
119
+
120
+ ### Workflow Example
121
+
122
+ ```bash
123
+ # 1. Run a scan
124
+ vibecheck scan --out vibecheck-artifacts/artifact.json
125
+
126
+ # 2. View results (artifact auto-detected)
127
+ vibecheck view
128
+
129
+ # Browser opens to http://localhost:3000 with results loaded
130
+ ```
131
+
132
+ ### Command Line Options
133
+
134
+ | Option | Description | Default |
135
+ |--------|-------------|---------|
136
+ | `-t, --target <path>` | Target directory to scan | Current directory |
137
+ | `-o, --out <path>` | Output file or directory | `vibecheck-artifacts/vibecheck-scan.json` |
138
+ | `-f, --format <format>` | Output format: `json`, `sarif`, or `both` | `json` |
139
+ | `--repo-name <name>` | Override repository name | Auto-detected |
140
+ | `--fail-on <threshold>` | Exit with non-zero if findings >= threshold | `high` |
141
+ | `-e, --exclude <glob>` | Glob pattern to exclude (repeatable) | See below |
142
+ | `--include-tests` | Include test files in scan | `false` |
143
+ | `--emit-intent-map` | Include route map and coverage metrics | `false` |
144
+ | `--changed` | Only scan changed files (not implemented) | `false` |
145
+
146
+ ### Default Excludes
147
+
148
+ The following patterns are excluded by default:
149
+
150
+ **Core excludes (always applied):**
151
+ - `node_modules`, `dist`, `.git`, `build`, `.next`, `coverage`
152
+ - `.turbo`, `.cache`, `out`, `.vercel`, `.netlify`
153
+
154
+ **Test excludes (skipped with `--include-tests`):**
155
+ - `__tests__/**`, `*.test.*`, `*.spec.*`
156
+ - `test/`, `tests/`, `fixtures/`, `__mocks__/`, `__fixtures__/`
157
+ - `cypress/`, `e2e/`, `*.stories.*`
158
+
159
+ ## Scanner Packs
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
+ ```
232
+
233
+ ---
234
+
235
+ ### Validation Pack
236
+
237
+ Rules for input validation issues.
238
+
239
+ #### VC-VAL-001: Validation Defined But Output Ignored
240
+
241
+ **Severity:** Medium
242
+ **Category:** validation
243
+
244
+ Detects cases where validation libraries (Zod, Yup, Joi) are called but the validated result is not used.
245
+
246
+ **What it looks for:**
247
+ - Calls to `.parse()`, `.validate()`, `.parseAsync()`, etc.
248
+ - Result not assigned to a variable
249
+ - Raw `request.body` or `req.body` used after validation
250
+
251
+ **Example (vulnerable):**
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
+ ```
264
+
265
+ ---
266
+
267
+ ### Privacy Pack
268
+
269
+ Rules for data privacy and logging issues.
270
+
271
+ #### VC-PRIV-001: Sensitive Data Logged
272
+
273
+ **Severity:** High
274
+ **Category:** privacy
275
+
276
+ Detects logging statements that include sensitive variable names.
277
+
278
+ **What it looks for:**
279
+ - `console.log`, `console.info`, `console.debug`, `logger.info`, etc.
280
+ - Variables containing: password, secret, token, apiKey, creditCard, ssn, etc.
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
+ ```
291
+
292
+ ---
293
+
294
+ ### Config Pack
295
+
296
+ Rules for configuration and secrets management issues.
297
+
298
+ #### VC-CONFIG-001: Undocumented Environment Variable
299
+
300
+ **Severity:** Low
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
+ ```
323
+
324
+ **Example (safe):**
325
+ ```typescript
326
+ const jwtSecret = process.env.JWT_SECRET;
327
+ if (!jwtSecret) throw new Error("JWT_SECRET is required");
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
+ ```
373
+
374
+ ---
375
+
376
+ ### Hallucinations Pack
377
+
378
+ Rules for detecting security libraries that are imported but not properly used.
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
+ ---
771
+
772
+ ## Output Formats
773
+
774
+ VibeCheck supports two output formats: JSON and SARIF.
775
+
776
+ ### JSON Format
777
+
778
+ The default format, defined by `@vibecheck/schema`:
779
+
780
+ ```json
781
+ {
782
+ "artifactVersion": "0.1",
783
+ "generatedAt": "2024-01-15T10:30:00.000Z",
784
+ "tool": {
785
+ "name": "vibecheck",
786
+ "version": "1.0.0"
787
+ },
788
+ "summary": {
789
+ "totalFindings": 2,
790
+ "bySeverity": { "critical": 0, "high": 1, "medium": 1, "low": 0, "info": 0 },
791
+ "byCategory": { "auth": 1, "validation": 1, ... }
792
+ },
793
+ "findings": [
794
+ {
795
+ "id": "f-abc123",
796
+ "severity": "high",
797
+ "confidence": 0.85,
798
+ "category": "auth",
799
+ "ruleId": "VC-AUTH-001",
800
+ "title": "Missing authentication on POST /api/users",
801
+ "description": "...",
802
+ "evidence": [
803
+ {
804
+ "file": "app/api/users/route.ts",
805
+ "startLine": 25,
806
+ "endLine": 30,
807
+ "snippet": "...",
808
+ "label": "Unprotected route handler"
809
+ }
810
+ ],
811
+ "remediation": {
812
+ "recommendedFix": "Add authentication middleware"
813
+ },
814
+ "fingerprint": "sha256:..."
815
+ }
816
+ ],
817
+ "metrics": {
818
+ "filesScanned": 50,
819
+ "linesOfCode": 5000,
820
+ "scanDurationMs": 1234
821
+ }
822
+ }
823
+ ```
824
+
825
+ ### SARIF Format
826
+
827
+ [SARIF (Static Analysis Results Interchange Format)](https://sarifweb.azurewebsites.net/) is an OASIS standard for static analysis tools. Use `--format sarif` to generate SARIF 2.1.0 output, compatible with:
828
+
829
+ - **GitHub Code Scanning** - Upload via `github/codeql-action/upload-sarif`
830
+ - **Azure DevOps** - Native SARIF support in security reports
831
+ - **VS Code** - SARIF Viewer extension
832
+ - **Other tools** - Any SARIF 2.1.0 compatible viewer
833
+
834
+ ```bash
835
+ # Generate SARIF for GitHub Code Scanning
836
+ vibecheck scan --format sarif --out results.sarif
837
+
838
+ # Upload to GitHub (in CI workflow)
839
+ - uses: github/codeql-action/upload-sarif@v2
840
+ with:
841
+ sarif_file: results.sarif
842
+ ```
843
+
844
+ ## Architecture
845
+
846
+ ```
847
+ packages/cli/src/
848
+ ├── commands/
849
+ │ ├── scan.ts # Main scan orchestrator
850
+ │ └── explain.ts # Report viewer
851
+ ├── scanners/
852
+ │ ├── types.ts # ScanContext, types
853
+ │ ├── helpers/
854
+ │ │ ├── ast-helpers.ts # ts-morph utilities
855
+ │ │ └── context-builder.ts # ScanContext factory
856
+ │ ├── auth/
857
+ │ │ ├── unprotected-api-route.ts # VC-AUTH-001
858
+ │ │ └── middleware-gap.ts # VC-MW-001
859
+ │ ├── validation/
860
+ │ │ └── ignored-validation.ts # VC-VAL-001
861
+ │ ├── privacy/
862
+ │ │ └── sensitive-logging.ts # VC-PRIV-001
863
+ │ ├── config/
864
+ │ │ ├── undocumented-env.ts # VC-CONFIG-001
865
+ │ │ └── insecure-defaults.ts # VC-CONFIG-002
866
+ │ ├── network/
867
+ │ │ └── ssrf-prone-fetch.ts # VC-NET-001
868
+ │ └── hallucinations/
869
+ │ └── unused-security-imports.ts # VC-HALL-001, VC-HALL-002
870
+ └── index.ts
871
+ ```
872
+
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
+ ## License
902
+
903
+ MIT