@oculum/scanner 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/formatters/cli-terminal.d.ts +27 -0
- package/dist/formatters/cli-terminal.d.ts.map +1 -0
- package/dist/formatters/cli-terminal.js +412 -0
- package/dist/formatters/cli-terminal.js.map +1 -0
- package/dist/formatters/github-comment.d.ts +41 -0
- package/dist/formatters/github-comment.d.ts.map +1 -0
- package/dist/formatters/github-comment.js +306 -0
- package/dist/formatters/github-comment.js.map +1 -0
- package/dist/formatters/grouping.d.ts +52 -0
- package/dist/formatters/grouping.d.ts.map +1 -0
- package/dist/formatters/grouping.js +152 -0
- package/dist/formatters/grouping.js.map +1 -0
- package/dist/formatters/index.d.ts +9 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +35 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/vscode-diagnostic.d.ts +103 -0
- package/dist/formatters/vscode-diagnostic.d.ts.map +1 -0
- package/dist/formatters/vscode-diagnostic.js +151 -0
- package/dist/formatters/vscode-diagnostic.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +648 -0
- package/dist/index.js.map +1 -0
- package/dist/layer1/comments.d.ts +8 -0
- package/dist/layer1/comments.d.ts.map +1 -0
- package/dist/layer1/comments.js +203 -0
- package/dist/layer1/comments.js.map +1 -0
- package/dist/layer1/config-audit.d.ts +8 -0
- package/dist/layer1/config-audit.d.ts.map +1 -0
- package/dist/layer1/config-audit.js +252 -0
- package/dist/layer1/config-audit.js.map +1 -0
- package/dist/layer1/entropy.d.ts +8 -0
- package/dist/layer1/entropy.d.ts.map +1 -0
- package/dist/layer1/entropy.js +500 -0
- package/dist/layer1/entropy.js.map +1 -0
- package/dist/layer1/file-flags.d.ts +7 -0
- package/dist/layer1/file-flags.d.ts.map +1 -0
- package/dist/layer1/file-flags.js +112 -0
- package/dist/layer1/file-flags.js.map +1 -0
- package/dist/layer1/index.d.ts +36 -0
- package/dist/layer1/index.d.ts.map +1 -0
- package/dist/layer1/index.js +132 -0
- package/dist/layer1/index.js.map +1 -0
- package/dist/layer1/patterns.d.ts +8 -0
- package/dist/layer1/patterns.d.ts.map +1 -0
- package/dist/layer1/patterns.js +482 -0
- package/dist/layer1/patterns.js.map +1 -0
- package/dist/layer1/urls.d.ts +8 -0
- package/dist/layer1/urls.d.ts.map +1 -0
- package/dist/layer1/urls.js +296 -0
- package/dist/layer1/urls.js.map +1 -0
- package/dist/layer1/weak-crypto.d.ts +7 -0
- package/dist/layer1/weak-crypto.d.ts.map +1 -0
- package/dist/layer1/weak-crypto.js +291 -0
- package/dist/layer1/weak-crypto.js.map +1 -0
- package/dist/layer2/ai-agent-tools.d.ts +19 -0
- package/dist/layer2/ai-agent-tools.d.ts.map +1 -0
- package/dist/layer2/ai-agent-tools.js +528 -0
- package/dist/layer2/ai-agent-tools.js.map +1 -0
- package/dist/layer2/ai-endpoint-protection.d.ts +36 -0
- package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -0
- package/dist/layer2/ai-endpoint-protection.js +332 -0
- package/dist/layer2/ai-endpoint-protection.js.map +1 -0
- package/dist/layer2/ai-execution-sinks.d.ts +18 -0
- package/dist/layer2/ai-execution-sinks.d.ts.map +1 -0
- package/dist/layer2/ai-execution-sinks.js +496 -0
- package/dist/layer2/ai-execution-sinks.js.map +1 -0
- package/dist/layer2/ai-fingerprinting.d.ts +7 -0
- package/dist/layer2/ai-fingerprinting.d.ts.map +1 -0
- package/dist/layer2/ai-fingerprinting.js +654 -0
- package/dist/layer2/ai-fingerprinting.js.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts +19 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.js +356 -0
- package/dist/layer2/ai-prompt-hygiene.js.map +1 -0
- package/dist/layer2/ai-rag-safety.d.ts +21 -0
- package/dist/layer2/ai-rag-safety.d.ts.map +1 -0
- package/dist/layer2/ai-rag-safety.js +459 -0
- package/dist/layer2/ai-rag-safety.js.map +1 -0
- package/dist/layer2/ai-schema-validation.d.ts +25 -0
- package/dist/layer2/ai-schema-validation.d.ts.map +1 -0
- package/dist/layer2/ai-schema-validation.js +375 -0
- package/dist/layer2/ai-schema-validation.js.map +1 -0
- package/dist/layer2/auth-antipatterns.d.ts +20 -0
- package/dist/layer2/auth-antipatterns.d.ts.map +1 -0
- package/dist/layer2/auth-antipatterns.js +333 -0
- package/dist/layer2/auth-antipatterns.js.map +1 -0
- package/dist/layer2/byok-patterns.d.ts +12 -0
- package/dist/layer2/byok-patterns.d.ts.map +1 -0
- package/dist/layer2/byok-patterns.js +299 -0
- package/dist/layer2/byok-patterns.js.map +1 -0
- package/dist/layer2/dangerous-functions.d.ts +7 -0
- package/dist/layer2/dangerous-functions.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions.js +1375 -0
- package/dist/layer2/dangerous-functions.js.map +1 -0
- package/dist/layer2/data-exposure.d.ts +16 -0
- package/dist/layer2/data-exposure.d.ts.map +1 -0
- package/dist/layer2/data-exposure.js +279 -0
- package/dist/layer2/data-exposure.js.map +1 -0
- package/dist/layer2/framework-checks.d.ts +7 -0
- package/dist/layer2/framework-checks.d.ts.map +1 -0
- package/dist/layer2/framework-checks.js +388 -0
- package/dist/layer2/framework-checks.js.map +1 -0
- package/dist/layer2/index.d.ts +58 -0
- package/dist/layer2/index.d.ts.map +1 -0
- package/dist/layer2/index.js +380 -0
- package/dist/layer2/index.js.map +1 -0
- package/dist/layer2/logic-gates.d.ts +7 -0
- package/dist/layer2/logic-gates.d.ts.map +1 -0
- package/dist/layer2/logic-gates.js +182 -0
- package/dist/layer2/logic-gates.js.map +1 -0
- package/dist/layer2/risky-imports.d.ts +7 -0
- package/dist/layer2/risky-imports.d.ts.map +1 -0
- package/dist/layer2/risky-imports.js +161 -0
- package/dist/layer2/risky-imports.js.map +1 -0
- package/dist/layer2/variables.d.ts +8 -0
- package/dist/layer2/variables.d.ts.map +1 -0
- package/dist/layer2/variables.js +152 -0
- package/dist/layer2/variables.js.map +1 -0
- package/dist/layer3/anthropic.d.ts +83 -0
- package/dist/layer3/anthropic.d.ts.map +1 -0
- package/dist/layer3/anthropic.js +1745 -0
- package/dist/layer3/anthropic.js.map +1 -0
- package/dist/layer3/index.d.ts +24 -0
- package/dist/layer3/index.d.ts.map +1 -0
- package/dist/layer3/index.js +119 -0
- package/dist/layer3/index.js.map +1 -0
- package/dist/layer3/openai.d.ts +25 -0
- package/dist/layer3/openai.d.ts.map +1 -0
- package/dist/layer3/openai.js +238 -0
- package/dist/layer3/openai.js.map +1 -0
- package/dist/layer3/package-check.d.ts +63 -0
- package/dist/layer3/package-check.d.ts.map +1 -0
- package/dist/layer3/package-check.js +508 -0
- package/dist/layer3/package-check.js.map +1 -0
- package/dist/modes/incremental.d.ts +66 -0
- package/dist/modes/incremental.d.ts.map +1 -0
- package/dist/modes/incremental.js +200 -0
- package/dist/modes/incremental.js.map +1 -0
- package/dist/tiers.d.ts +125 -0
- package/dist/tiers.d.ts.map +1 -0
- package/dist/tiers.js +234 -0
- package/dist/tiers.js.map +1 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +50 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/auth-helper-detector.d.ts +56 -0
- package/dist/utils/auth-helper-detector.d.ts.map +1 -0
- package/dist/utils/auth-helper-detector.js +360 -0
- package/dist/utils/auth-helper-detector.js.map +1 -0
- package/dist/utils/context-helpers.d.ts +96 -0
- package/dist/utils/context-helpers.d.ts.map +1 -0
- package/dist/utils/context-helpers.js +493 -0
- package/dist/utils/context-helpers.js.map +1 -0
- package/dist/utils/diff-detector.d.ts +53 -0
- package/dist/utils/diff-detector.d.ts.map +1 -0
- package/dist/utils/diff-detector.js +104 -0
- package/dist/utils/diff-detector.js.map +1 -0
- package/dist/utils/diff-parser.d.ts +80 -0
- package/dist/utils/diff-parser.d.ts.map +1 -0
- package/dist/utils/diff-parser.js +202 -0
- package/dist/utils/diff-parser.js.map +1 -0
- package/dist/utils/imported-auth-detector.d.ts +37 -0
- package/dist/utils/imported-auth-detector.d.ts.map +1 -0
- package/dist/utils/imported-auth-detector.js +251 -0
- package/dist/utils/imported-auth-detector.js.map +1 -0
- package/dist/utils/middleware-detector.d.ts +55 -0
- package/dist/utils/middleware-detector.d.ts.map +1 -0
- package/dist/utils/middleware-detector.js +260 -0
- package/dist/utils/middleware-detector.js.map +1 -0
- package/dist/utils/oauth-flow-detector.d.ts +41 -0
- package/dist/utils/oauth-flow-detector.d.ts.map +1 -0
- package/dist/utils/oauth-flow-detector.js +202 -0
- package/dist/utils/oauth-flow-detector.js.map +1 -0
- package/dist/utils/path-exclusions.d.ts +55 -0
- package/dist/utils/path-exclusions.d.ts.map +1 -0
- package/dist/utils/path-exclusions.js +222 -0
- package/dist/utils/path-exclusions.js.map +1 -0
- package/dist/utils/project-context-builder.d.ts +119 -0
- package/dist/utils/project-context-builder.d.ts.map +1 -0
- package/dist/utils/project-context-builder.js +534 -0
- package/dist/utils/project-context-builder.js.map +1 -0
- package/dist/utils/registry-clients.d.ts +93 -0
- package/dist/utils/registry-clients.d.ts.map +1 -0
- package/dist/utils/registry-clients.js +273 -0
- package/dist/utils/registry-clients.js.map +1 -0
- package/dist/utils/trpc-analyzer.d.ts +78 -0
- package/dist/utils/trpc-analyzer.d.ts.map +1 -0
- package/dist/utils/trpc-analyzer.js +297 -0
- package/dist/utils/trpc-analyzer.js.map +1 -0
- package/package.json +45 -0
- package/src/__tests__/benchmark/fixtures/false-positives.ts +227 -0
- package/src/__tests__/benchmark/fixtures/index.ts +68 -0
- package/src/__tests__/benchmark/fixtures/layer1/config-audit.ts +364 -0
- package/src/__tests__/benchmark/fixtures/layer1/hardcoded-secrets.ts +173 -0
- package/src/__tests__/benchmark/fixtures/layer1/high-entropy.ts +234 -0
- package/src/__tests__/benchmark/fixtures/layer1/index.ts +31 -0
- package/src/__tests__/benchmark/fixtures/layer1/sensitive-urls.ts +90 -0
- package/src/__tests__/benchmark/fixtures/layer1/weak-crypto.ts +197 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-agent-tools.ts +170 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-endpoint-protection.ts +418 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +189 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-fingerprinting.ts +316 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +178 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +184 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-schema-validation.ts +434 -0
- package/src/__tests__/benchmark/fixtures/layer2/auth-antipatterns.ts +159 -0
- package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +112 -0
- package/src/__tests__/benchmark/fixtures/layer2/dangerous-functions.ts +246 -0
- package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +168 -0
- package/src/__tests__/benchmark/fixtures/layer2/framework-checks.ts +346 -0
- package/src/__tests__/benchmark/fixtures/layer2/index.ts +67 -0
- package/src/__tests__/benchmark/fixtures/layer2/injection-vulnerabilities.ts +239 -0
- package/src/__tests__/benchmark/fixtures/layer2/logic-gates.ts +246 -0
- package/src/__tests__/benchmark/fixtures/layer2/risky-imports.ts +231 -0
- package/src/__tests__/benchmark/fixtures/layer2/variables.ts +167 -0
- package/src/__tests__/benchmark/index.ts +29 -0
- package/src/__tests__/benchmark/run-benchmark.ts +144 -0
- package/src/__tests__/benchmark/run-depth-validation.ts +206 -0
- package/src/__tests__/benchmark/run-real-world-test.ts +243 -0
- package/src/__tests__/benchmark/security-benchmark-script.ts +1737 -0
- package/src/__tests__/benchmark/tier-integration-script.ts +177 -0
- package/src/__tests__/benchmark/types.ts +144 -0
- package/src/__tests__/benchmark/utils/test-runner.ts +475 -0
- package/src/__tests__/regression/known-false-positives.test.ts +467 -0
- package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +178 -0
- package/src/__tests__/snapshots/scan-depth.test.ts +258 -0
- package/src/__tests__/validation/analyze-results.ts +542 -0
- package/src/__tests__/validation/extract-for-triage.ts +146 -0
- package/src/__tests__/validation/fp-deep-analysis.ts +327 -0
- package/src/__tests__/validation/run-validation.ts +364 -0
- package/src/__tests__/validation/triage-template.md +132 -0
- package/src/formatters/cli-terminal.ts +446 -0
- package/src/formatters/github-comment.ts +382 -0
- package/src/formatters/grouping.ts +190 -0
- package/src/formatters/index.ts +47 -0
- package/src/formatters/vscode-diagnostic.ts +243 -0
- package/src/index.ts +823 -0
- package/src/layer1/comments.ts +218 -0
- package/src/layer1/config-audit.ts +289 -0
- package/src/layer1/entropy.ts +583 -0
- package/src/layer1/file-flags.ts +127 -0
- package/src/layer1/index.ts +181 -0
- package/src/layer1/patterns.ts +516 -0
- package/src/layer1/urls.ts +334 -0
- package/src/layer1/weak-crypto.ts +328 -0
- package/src/layer2/ai-agent-tools.ts +601 -0
- package/src/layer2/ai-endpoint-protection.ts +387 -0
- package/src/layer2/ai-execution-sinks.ts +580 -0
- package/src/layer2/ai-fingerprinting.ts +758 -0
- package/src/layer2/ai-prompt-hygiene.ts +411 -0
- package/src/layer2/ai-rag-safety.ts +511 -0
- package/src/layer2/ai-schema-validation.ts +421 -0
- package/src/layer2/auth-antipatterns.ts +394 -0
- package/src/layer2/byok-patterns.ts +336 -0
- package/src/layer2/dangerous-functions.ts +1563 -0
- package/src/layer2/data-exposure.ts +315 -0
- package/src/layer2/framework-checks.ts +433 -0
- package/src/layer2/index.ts +473 -0
- package/src/layer2/logic-gates.ts +206 -0
- package/src/layer2/risky-imports.ts +186 -0
- package/src/layer2/variables.ts +166 -0
- package/src/layer3/anthropic.ts +2030 -0
- package/src/layer3/index.ts +130 -0
- package/src/layer3/package-check.ts +604 -0
- package/src/modes/incremental.ts +293 -0
- package/src/tiers.ts +318 -0
- package/src/types.ts +284 -0
- package/src/utils/auth-helper-detector.ts +443 -0
- package/src/utils/context-helpers.ts +535 -0
- package/src/utils/diff-detector.ts +135 -0
- package/src/utils/diff-parser.ts +272 -0
- package/src/utils/imported-auth-detector.ts +320 -0
- package/src/utils/middleware-detector.ts +333 -0
- package/src/utils/oauth-flow-detector.ts +246 -0
- package/src/utils/path-exclusions.ts +266 -0
- package/src/utils/project-context-builder.ts +707 -0
- package/src/utils/registry-clients.ts +351 -0
- package/src/utils/trpc-analyzer.ts +382 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Layer 2: Authentication Anti-Pattern Detection
|
|
4
|
+
* Identifies weak or missing authentication/authorization patterns
|
|
5
|
+
*
|
|
6
|
+
* Key improvements:
|
|
7
|
+
* - Respects global middleware protection (caps severity at info)
|
|
8
|
+
* - Detects throwing auth helpers and suppresses redundant null checks
|
|
9
|
+
* - Properly classifies public endpoints
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.detectAuthAntipatterns = detectAuthAntipatterns;
|
|
13
|
+
const middleware_detector_1 = require("../utils/middleware-detector");
|
|
14
|
+
const auth_helper_detector_1 = require("../utils/auth-helper-detector");
|
|
15
|
+
const AUTH_ANTIPATTERNS = [
|
|
16
|
+
// Missing auth checks
|
|
17
|
+
{
|
|
18
|
+
name: 'Unprotected API route',
|
|
19
|
+
pattern: /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)\s*\(/gi,
|
|
20
|
+
severity: 'medium',
|
|
21
|
+
description: 'API route handler may lack authentication - verify auth is checked',
|
|
22
|
+
suggestedFix: 'Add authentication middleware or check session at the start of the handler',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'Express route without auth middleware',
|
|
26
|
+
pattern: /\.(get|post|put|delete|patch)\s*\(\s*['"][^'"]+['"]\s*,\s*(async\s*)?\(\s*(req|request)/gi,
|
|
27
|
+
severity: 'medium',
|
|
28
|
+
description: 'Express route may lack authentication middleware',
|
|
29
|
+
suggestedFix: 'Add authentication middleware before the route handler',
|
|
30
|
+
},
|
|
31
|
+
// Weak authentication patterns
|
|
32
|
+
{
|
|
33
|
+
name: 'Hardcoded credentials check',
|
|
34
|
+
pattern: /if\s*\(\s*(username|user|email)\s*===?\s*['"][^'"]+['"]\s*&&\s*(password|pass|pwd)\s*===?\s*['"][^'"]+['"]/gi,
|
|
35
|
+
severity: 'critical',
|
|
36
|
+
description: 'Hardcoded credentials in authentication logic',
|
|
37
|
+
suggestedFix: 'Use a proper user database with hashed passwords',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'Password in plain text comparison',
|
|
41
|
+
pattern: /password\s*===?\s*user\.password|user\.password\s*===?\s*password/gi,
|
|
42
|
+
severity: 'high',
|
|
43
|
+
description: 'Plain text password comparison detected',
|
|
44
|
+
suggestedFix: 'Use bcrypt.compare() or similar for password verification',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'JWT without verification',
|
|
48
|
+
pattern: /jwt\.decode\s*\([^)]+\)(?!.*verify)/gi,
|
|
49
|
+
severity: 'high',
|
|
50
|
+
description: 'JWT decoded without signature verification',
|
|
51
|
+
suggestedFix: 'Use jwt.verify() instead of jwt.decode() for authentication',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'Weak JWT secret',
|
|
55
|
+
pattern: /jwt\.sign\s*\([^)]+,\s*['"][^'"]{1,20}['"]/gi,
|
|
56
|
+
severity: 'high',
|
|
57
|
+
description: 'JWT signed with a short/weak secret',
|
|
58
|
+
suggestedFix: 'Use a strong, random secret of at least 256 bits from environment variables',
|
|
59
|
+
},
|
|
60
|
+
// Session issues
|
|
61
|
+
{
|
|
62
|
+
name: 'Session without secure flag',
|
|
63
|
+
pattern: /session\s*\(\s*\{[^}]*(?!secure\s*:\s*true)[^}]*\}/gi,
|
|
64
|
+
severity: 'medium',
|
|
65
|
+
description: 'Session configuration may lack secure flag',
|
|
66
|
+
suggestedFix: 'Set secure: true for cookies in production',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'Cookie without httpOnly',
|
|
70
|
+
pattern: /cookie\s*\(\s*['"][^'"]+['"]\s*,[^)]*(?!httpOnly)[^)]*\)|setCookie\s*\([^)]*(?!httpOnly)/gi,
|
|
71
|
+
severity: 'medium',
|
|
72
|
+
description: 'Cookie set without httpOnly flag',
|
|
73
|
+
suggestedFix: 'Add httpOnly: true to prevent XSS access to cookies',
|
|
74
|
+
},
|
|
75
|
+
// Authorization issues
|
|
76
|
+
// NOTE: We intentionally do NOT flag "if (!user)" or "if (!userId)" patterns as issues
|
|
77
|
+
// when throwing auth helpers are in use. Those helpers guarantee the user exists.
|
|
78
|
+
// This pattern is now much more targeted.
|
|
79
|
+
{
|
|
80
|
+
name: 'Missing role check for admin operation',
|
|
81
|
+
pattern: /\b(admin|superuser|moderator|owner)\b.*(?:delete|remove|update|modify|grant|revoke)/gi,
|
|
82
|
+
severity: 'low',
|
|
83
|
+
description: 'Potentially privileged operation - verify role/permission checks are in place',
|
|
84
|
+
suggestedFix: 'Add role-based access control for sensitive operations',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'Client-side only auth check',
|
|
88
|
+
pattern: /if\s*\(\s*!?\s*(isAuthenticated|isLoggedIn|user)\s*\)\s*\{?\s*(router\.push|navigate|redirect|window\.location)/gi,
|
|
89
|
+
severity: 'medium',
|
|
90
|
+
description: 'Client-side only authentication redirect detected',
|
|
91
|
+
suggestedFix: 'Implement server-side authentication checks as well',
|
|
92
|
+
},
|
|
93
|
+
// Insecure token handling
|
|
94
|
+
{
|
|
95
|
+
name: 'Token in URL',
|
|
96
|
+
pattern: /\?.*token=|&token=|\?.*api_key=|&api_key=/gi,
|
|
97
|
+
severity: 'high',
|
|
98
|
+
description: 'Sensitive token passed in URL query parameter',
|
|
99
|
+
suggestedFix: 'Pass tokens in Authorization header or request body',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'Token in localStorage',
|
|
103
|
+
pattern: /localStorage\.(setItem|getItem)\s*\(\s*['"](token|jwt|auth|session|apiKey)/gi,
|
|
104
|
+
severity: 'medium',
|
|
105
|
+
description: 'Sensitive token stored in localStorage (XSS vulnerable)',
|
|
106
|
+
suggestedFix: 'Use httpOnly cookies for token storage',
|
|
107
|
+
},
|
|
108
|
+
// OAuth/Social auth issues
|
|
109
|
+
{
|
|
110
|
+
name: 'OAuth state parameter missing',
|
|
111
|
+
pattern: /oauth|authorize\?.*(?!state=)/gi,
|
|
112
|
+
severity: 'medium',
|
|
113
|
+
description: 'OAuth flow may lack state parameter for CSRF protection',
|
|
114
|
+
suggestedFix: 'Include a random state parameter in OAuth requests',
|
|
115
|
+
},
|
|
116
|
+
// Password handling issues
|
|
117
|
+
{
|
|
118
|
+
name: 'Password logged',
|
|
119
|
+
pattern: /console\.(log|info|debug|warn|error)\s*\([^)]*password/gi,
|
|
120
|
+
severity: 'critical',
|
|
121
|
+
description: 'Password may be logged to console',
|
|
122
|
+
suggestedFix: 'Never log passwords or sensitive credentials',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'Password in error message',
|
|
126
|
+
pattern: /throw\s+new\s+Error\s*\([^)]*password|Error\s*\([^)]*password/gi,
|
|
127
|
+
severity: 'high',
|
|
128
|
+
description: 'Password may be included in error message',
|
|
129
|
+
suggestedFix: 'Never include passwords in error messages',
|
|
130
|
+
},
|
|
131
|
+
// Rate limiting
|
|
132
|
+
{
|
|
133
|
+
name: 'Login without rate limiting',
|
|
134
|
+
pattern: /\/(login|signin|auth|authenticate)\s*['"],\s*(async\s*)?\(/gi,
|
|
135
|
+
severity: 'medium',
|
|
136
|
+
description: 'Login endpoint may lack rate limiting',
|
|
137
|
+
suggestedFix: 'Add rate limiting to prevent brute force attacks',
|
|
138
|
+
},
|
|
139
|
+
// 2FA bypass
|
|
140
|
+
{
|
|
141
|
+
name: 'Potential 2FA bypass',
|
|
142
|
+
pattern: /skip2fa|bypass2fa|disable2fa|twoFactor\s*[=:]\s*false/gi,
|
|
143
|
+
severity: 'high',
|
|
144
|
+
description: 'Two-factor authentication bypass detected',
|
|
145
|
+
suggestedFix: 'Remove 2FA bypass options in production',
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
// Check if line is a comment
|
|
149
|
+
function isComment(line) {
|
|
150
|
+
const trimmed = line.trim();
|
|
151
|
+
return (trimmed.startsWith('//') ||
|
|
152
|
+
trimmed.startsWith('#') ||
|
|
153
|
+
trimmed.startsWith('*') ||
|
|
154
|
+
trimmed.startsWith('/*'));
|
|
155
|
+
}
|
|
156
|
+
// Check if file is likely an auth-related file
|
|
157
|
+
function isAuthRelatedFile(filePath) {
|
|
158
|
+
const authKeywords = ['auth', 'login', 'session', 'user', 'account', 'credential', 'password', 'token', 'jwt', 'oauth'];
|
|
159
|
+
const lowerPath = filePath.toLowerCase();
|
|
160
|
+
return authKeywords.some(keyword => lowerPath.includes(keyword));
|
|
161
|
+
}
|
|
162
|
+
// Check if endpoint is a known public endpoint (health checks, webhooks, cron)
|
|
163
|
+
function isKnownPublicEndpoint(lineContent, filePath) {
|
|
164
|
+
const PUBLIC_ENDPOINTS = [
|
|
165
|
+
// Health checks
|
|
166
|
+
/\/health\b/i,
|
|
167
|
+
/\/healthz\b/i,
|
|
168
|
+
/\/ready\b/i,
|
|
169
|
+
/\/live\b/i,
|
|
170
|
+
/\/ping\b/i,
|
|
171
|
+
/\/status\b/i,
|
|
172
|
+
// Webhooks (receive external calls)
|
|
173
|
+
/\/webhook\b/i,
|
|
174
|
+
/\/webhooks\//i,
|
|
175
|
+
/\/callback\b/i,
|
|
176
|
+
// Cron/scheduled tasks
|
|
177
|
+
/\/cron\//i,
|
|
178
|
+
/\/scheduled\//i,
|
|
179
|
+
/\/tasks\//i,
|
|
180
|
+
// Public APIs
|
|
181
|
+
/\/public\//i,
|
|
182
|
+
/\bGET\b.*\/api\/\w+\/\[id\]/i, // Public resource reads with ID param
|
|
183
|
+
];
|
|
184
|
+
return PUBLIC_ENDPOINTS.some(pattern => pattern.test(lineContent) || pattern.test(filePath));
|
|
185
|
+
}
|
|
186
|
+
// Check if there are auth indicators in nearby lines (within 15 lines)
|
|
187
|
+
function hasAuthCheckNearby(lines, lineIndex) {
|
|
188
|
+
const startLine = Math.max(0, lineIndex);
|
|
189
|
+
const endLine = Math.min(lines.length, lineIndex + 15);
|
|
190
|
+
const searchWindow = lines.slice(startLine, endLine);
|
|
191
|
+
const authIndicators = [
|
|
192
|
+
/authorization/i,
|
|
193
|
+
/bearer\s+token/i,
|
|
194
|
+
/req\.user/,
|
|
195
|
+
/request\.user/,
|
|
196
|
+
/isAuthenticated/,
|
|
197
|
+
/requireAuth/,
|
|
198
|
+
/verifyToken/,
|
|
199
|
+
/checkPermission/,
|
|
200
|
+
/getServerSession/,
|
|
201
|
+
/auth\(\)/,
|
|
202
|
+
/middleware.*auth/i,
|
|
203
|
+
/session\s*\.\s*user/,
|
|
204
|
+
// Internal secret checks (network-level auth)
|
|
205
|
+
/internal.?secret/i,
|
|
206
|
+
/INTERNAL_SECRET/,
|
|
207
|
+
/x-internal-secret/i,
|
|
208
|
+
/admin.?secret/i,
|
|
209
|
+
/service.?token/i,
|
|
210
|
+
// BYOK patterns - user provides their own API key (implicit auth)
|
|
211
|
+
/userApiKey|user_api_key|clientApiKey/i,
|
|
212
|
+
/req\.body\.(?:apiKey|api_key|openaiKey|anthropicKey)/i,
|
|
213
|
+
/headers\[['"`]x-(?:openai|api|anthropic)-key['"`]\]/i,
|
|
214
|
+
];
|
|
215
|
+
return searchWindow.some(line => authIndicators.some(pattern => pattern.test(line)));
|
|
216
|
+
}
|
|
217
|
+
function detectAuthAntipatterns(content, filePath, options = {}) {
|
|
218
|
+
const { middlewareConfig, authHelpers, fileAuthImports } = options;
|
|
219
|
+
const vulnerabilities = [];
|
|
220
|
+
const lines = content.split('\n');
|
|
221
|
+
const isAuthFile = isAuthRelatedFile(filePath);
|
|
222
|
+
// Check if this route is protected by global middleware
|
|
223
|
+
const routePath = (0, middleware_detector_1.getRoutePathFromFile)(filePath);
|
|
224
|
+
const middlewareProtection = routePath && middlewareConfig
|
|
225
|
+
? (0, middleware_detector_1.isRouteProtectedByMiddleware)(routePath, middlewareConfig)
|
|
226
|
+
: { isProtected: false, reason: '' };
|
|
227
|
+
// Check if file uses imported auth middleware/helpers
|
|
228
|
+
const importedAuthProtection = fileAuthImports?.get(filePath);
|
|
229
|
+
const usesImportedAuth = importedAuthProtection?.usesImportedAuth ?? false;
|
|
230
|
+
// Check if file uses throwing auth helpers
|
|
231
|
+
const helpersList = authHelpers?.helpers || [];
|
|
232
|
+
lines.forEach((line, index) => {
|
|
233
|
+
// Skip comment lines
|
|
234
|
+
if (isComment(line))
|
|
235
|
+
return;
|
|
236
|
+
for (const pattern of AUTH_ANTIPATTERNS) {
|
|
237
|
+
const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags);
|
|
238
|
+
if (regex.test(line)) {
|
|
239
|
+
// Special handling for unprotected route patterns
|
|
240
|
+
if (pattern.name === 'Unprotected API route' ||
|
|
241
|
+
pattern.name === 'Express route without auth middleware') {
|
|
242
|
+
// PRIORITY 0: Check if this is actually a route file
|
|
243
|
+
// In Next.js, routes must be in `route.ts/js` files. Files like `handlers.ts`,
|
|
244
|
+
// `safe-handlers.ts`, `utils.ts` etc. are NOT actual API routes even if they
|
|
245
|
+
// export GET/POST functions.
|
|
246
|
+
const isActualRouteFile = /\/(route|page)\.(ts|js|tsx|jsx)$/i.test(filePath) ||
|
|
247
|
+
/\/(api|routes?)\/.*\/(index|route)\.(ts|js)$/i.test(filePath) ||
|
|
248
|
+
// Express/Koa routes typically have 'routes' or 'router' in path
|
|
249
|
+
/\/(routes?|router|controllers?)\.[tj]s$/i.test(filePath);
|
|
250
|
+
// Files explicitly named as handlers, helpers, utils are not routes
|
|
251
|
+
const isUtilityFile = /(handler|helper|util|mock|test|fixture|safe|example)/i.test(filePath);
|
|
252
|
+
if (!isActualRouteFile || isUtilityFile) {
|
|
253
|
+
// Not an actual route file - skip this finding
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
// PRIORITY 1: Check if route is protected by global middleware
|
|
257
|
+
// This is the STRONGEST signal - if middleware protects the route, suppress entirely
|
|
258
|
+
if (middlewareProtection.isProtected) {
|
|
259
|
+
// Route is authenticated by middleware - no finding needed
|
|
260
|
+
break; // Skip this pattern, route is protected
|
|
261
|
+
}
|
|
262
|
+
// PRIORITY 1.5: Check if file imports and uses auth middleware
|
|
263
|
+
// e.g., import { authMiddleware } from '@/lib/auth' + wraps handlers
|
|
264
|
+
if (usesImportedAuth) {
|
|
265
|
+
// File imports auth middleware and uses it - no finding needed
|
|
266
|
+
break; // Skip this pattern, route is protected via imported auth
|
|
267
|
+
}
|
|
268
|
+
// PRIORITY 2: Check if file uses throwing auth helpers
|
|
269
|
+
// If getCurrentUserId() or similar is called, the route is authenticated
|
|
270
|
+
const authHelperCall = (0, auth_helper_detector_1.hasAuthHelperCallBefore)(content, index, helpersList);
|
|
271
|
+
if (authHelperCall.hasCall && authHelperCall.helper) {
|
|
272
|
+
// Route uses a throwing auth helper - no finding needed
|
|
273
|
+
// The auth helper guarantees authenticated context
|
|
274
|
+
break; // Skip this pattern, route is protected
|
|
275
|
+
}
|
|
276
|
+
// PRIORITY 3: Check if this is a known public endpoint
|
|
277
|
+
if (isKnownPublicEndpoint(line, filePath)) {
|
|
278
|
+
vulnerabilities.push({
|
|
279
|
+
id: `auth-antipattern-${filePath}-${index + 1}-${pattern.name}`,
|
|
280
|
+
filePath,
|
|
281
|
+
lineNumber: index + 1,
|
|
282
|
+
lineContent: line.trim(),
|
|
283
|
+
severity: 'info',
|
|
284
|
+
category: 'missing_auth',
|
|
285
|
+
title: pattern.name + ' (public endpoint)',
|
|
286
|
+
description: 'This appears to be a public endpoint (health check, webhook, cron, etc.). Verify this is intentionally public and consider rate limiting if needed.',
|
|
287
|
+
suggestedFix: 'If this is a webhook or cron endpoint, ensure it has appropriate authentication (API keys, signatures, etc.). Health checks typically do not need auth.',
|
|
288
|
+
confidence: 'low',
|
|
289
|
+
layer: 2,
|
|
290
|
+
});
|
|
291
|
+
break; // Only report once per line
|
|
292
|
+
}
|
|
293
|
+
// PRIORITY 4: Check if auth check exists nearby (inline check)
|
|
294
|
+
if (hasAuthCheckNearby(lines, index)) {
|
|
295
|
+
vulnerabilities.push({
|
|
296
|
+
id: `auth-antipattern-${filePath}-${index + 1}-${pattern.name}`,
|
|
297
|
+
filePath,
|
|
298
|
+
lineNumber: index + 1,
|
|
299
|
+
lineContent: line.trim(),
|
|
300
|
+
severity: 'low',
|
|
301
|
+
category: 'missing_auth',
|
|
302
|
+
title: pattern.name,
|
|
303
|
+
description: pattern.description + ' (auth check detected in nearby lines)',
|
|
304
|
+
suggestedFix: pattern.suggestedFix,
|
|
305
|
+
confidence: 'low',
|
|
306
|
+
layer: 2,
|
|
307
|
+
});
|
|
308
|
+
break; // Only report once per line
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// Standard handling for all patterns (or fallback for route patterns)
|
|
312
|
+
// Boost confidence for auth-related files
|
|
313
|
+
const confidence = isAuthFile ? 'high' : 'medium';
|
|
314
|
+
vulnerabilities.push({
|
|
315
|
+
id: `auth-antipattern-${filePath}-${index + 1}-${pattern.name}`,
|
|
316
|
+
filePath,
|
|
317
|
+
lineNumber: index + 1,
|
|
318
|
+
lineContent: line.trim(),
|
|
319
|
+
severity: pattern.severity,
|
|
320
|
+
category: 'missing_auth',
|
|
321
|
+
title: pattern.name,
|
|
322
|
+
description: pattern.description,
|
|
323
|
+
suggestedFix: pattern.suggestedFix,
|
|
324
|
+
confidence,
|
|
325
|
+
layer: 2,
|
|
326
|
+
});
|
|
327
|
+
break; // Only report once per line
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
return vulnerabilities;
|
|
332
|
+
}
|
|
333
|
+
//# sourceMappingURL=auth-antipatterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-antipatterns.js","sourceRoot":"","sources":["../../src/layer2/auth-antipatterns.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 2: BYOK (Bring Your Own Key) Pattern Detection
|
|
3
|
+
* Distinguishes per-user BYOK flows from central/shared key storage
|
|
4
|
+
* BYOK is often a feature, not a vulnerability - severity depends on context
|
|
5
|
+
*/
|
|
6
|
+
import type { Vulnerability } from '../types';
|
|
7
|
+
import type { MiddlewareAuthConfig } from '../utils/middleware-detector';
|
|
8
|
+
/**
|
|
9
|
+
* Detect BYOK patterns and classify their risk level
|
|
10
|
+
*/
|
|
11
|
+
export declare function detectBYOKPatterns(content: string, filePath: string, middlewareConfig?: MiddlewareAuthConfig): Vulnerability[];
|
|
12
|
+
//# sourceMappingURL=byok-patterns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"byok-patterns.d.ts","sourceRoot":"","sources":["../../src/layer2/byok-patterns.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAyB,MAAM,UAAU,CAAA;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAA;AAkNxE;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,gBAAgB,CAAC,EAAE,oBAAoB,GACtC,aAAa,EAAE,CA+GjB"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Layer 2: BYOK (Bring Your Own Key) Pattern Detection
|
|
4
|
+
* Distinguishes per-user BYOK flows from central/shared key storage
|
|
5
|
+
* BYOK is often a feature, not a vulnerability - severity depends on context
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.detectBYOKPatterns = detectBYOKPatterns;
|
|
9
|
+
const context_helpers_1 = require("../utils/context-helpers");
|
|
10
|
+
const middleware_detector_1 = require("../utils/middleware-detector");
|
|
11
|
+
/**
|
|
12
|
+
* Check if line contains example/placeholder API key patterns
|
|
13
|
+
*/
|
|
14
|
+
function isExampleCredential(line) {
|
|
15
|
+
const examplePatterns = [
|
|
16
|
+
/your[-_]?api[-_]?key/i,
|
|
17
|
+
/example[-_]?api[-_]?key/i,
|
|
18
|
+
/sample[-_]?key/i,
|
|
19
|
+
/replace[-_]?with/i,
|
|
20
|
+
/placeholder/i,
|
|
21
|
+
/your[-_]?secret/i,
|
|
22
|
+
/example[-_]?secret/i,
|
|
23
|
+
/<.*api.*key.*>/i, // <YOUR_API_KEY>
|
|
24
|
+
/\[.*api.*key.*\]/i, // [API_KEY]
|
|
25
|
+
/xxx+/i,
|
|
26
|
+
/demo[-_]?key/i,
|
|
27
|
+
/test[-_]?key/i,
|
|
28
|
+
/fake[-_]?key/i,
|
|
29
|
+
/mock[-_]?key/i,
|
|
30
|
+
];
|
|
31
|
+
return examplePatterns.some(p => p.test(line));
|
|
32
|
+
}
|
|
33
|
+
const BYOK_PATTERNS = [
|
|
34
|
+
// OpenAI API key patterns
|
|
35
|
+
{
|
|
36
|
+
name: 'User-provided OpenAI API key',
|
|
37
|
+
pattern: /(?:openai|api).*key.*(?:req\.|body\.|params\.|input\.|user)|(?:req\.|body\.|params\.).*(?:openai|api).*key/gi,
|
|
38
|
+
keyType: 'openai',
|
|
39
|
+
description: 'User-provided OpenAI API key detected',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'OpenAI key from request',
|
|
43
|
+
pattern: /new\s+OpenAI\s*\(\s*\{[^}]*apiKey\s*:\s*(?:req|body|params|input)/gi,
|
|
44
|
+
keyType: 'openai',
|
|
45
|
+
description: 'OpenAI client initialized with user-provided key',
|
|
46
|
+
},
|
|
47
|
+
// Anthropic API key patterns
|
|
48
|
+
{
|
|
49
|
+
name: 'User-provided Anthropic API key',
|
|
50
|
+
pattern: /(?:anthropic|claude).*key.*(?:req\.|body\.|params\.|input\.|user)|(?:req\.|body\.|params\.).*(?:anthropic|claude).*key/gi,
|
|
51
|
+
keyType: 'anthropic',
|
|
52
|
+
description: 'User-provided Anthropic API key detected',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'Anthropic key from request',
|
|
56
|
+
pattern: /new\s+Anthropic\s*\(\s*\{[^}]*apiKey\s*:\s*(?:req|body|params|input)/gi,
|
|
57
|
+
keyType: 'anthropic',
|
|
58
|
+
description: 'Anthropic client initialized with user-provided key',
|
|
59
|
+
},
|
|
60
|
+
// Generic AI key patterns
|
|
61
|
+
{
|
|
62
|
+
name: 'User-provided AI API key',
|
|
63
|
+
pattern: /(?:ai|llm|model).*(?:api)?.*key.*(?:req\.|body\.|params\.|input\.)/gi,
|
|
64
|
+
keyType: 'generic_ai',
|
|
65
|
+
description: 'User-provided AI API key detected',
|
|
66
|
+
},
|
|
67
|
+
// Generic key patterns (more cautious)
|
|
68
|
+
{
|
|
69
|
+
name: 'API key from user input',
|
|
70
|
+
pattern: /apiKey\s*[=:]\s*(?:req|body|params|input)\./gi,
|
|
71
|
+
keyType: 'generic',
|
|
72
|
+
description: 'API key received from user input',
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
/**
|
|
76
|
+
* Check if file has explicit authentication checks
|
|
77
|
+
*/
|
|
78
|
+
function hasExplicitAuthCheck(content) {
|
|
79
|
+
const authPatterns = [
|
|
80
|
+
// Common auth check functions
|
|
81
|
+
/getCurrentUser(Id)?\s*\(/i,
|
|
82
|
+
/getSession\s*\(/i,
|
|
83
|
+
/getServerSession\s*\(/i,
|
|
84
|
+
/auth\s*\(\s*\)/i,
|
|
85
|
+
/requireAuth\s*\(/i,
|
|
86
|
+
/verifyToken\s*\(/i,
|
|
87
|
+
/isAuthenticated/i,
|
|
88
|
+
/checkAuth\s*\(/i,
|
|
89
|
+
/withAuth\s*\(/i,
|
|
90
|
+
// Request user checks
|
|
91
|
+
/req\.user|request\.user/i,
|
|
92
|
+
/context\.user/i,
|
|
93
|
+
// Supabase auth
|
|
94
|
+
/supabase\.auth\.getUser/i,
|
|
95
|
+
/supabase\.auth\.getSession/i,
|
|
96
|
+
// NextAuth
|
|
97
|
+
/getServerSession\s*\(/i,
|
|
98
|
+
/useSession\s*\(/i,
|
|
99
|
+
// Clerk
|
|
100
|
+
/currentUser\s*\(/i,
|
|
101
|
+
/auth\(\)\.protect/i,
|
|
102
|
+
];
|
|
103
|
+
return authPatterns.some(p => p.test(content));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if key appears to be stored to database
|
|
107
|
+
*/
|
|
108
|
+
function isKeyStoredToDatabase(content, lineNumber) {
|
|
109
|
+
const lines = content.split('\n');
|
|
110
|
+
const startLine = Math.max(0, lineNumber - 5);
|
|
111
|
+
const endLine = Math.min(lines.length, lineNumber + 15);
|
|
112
|
+
const context = lines.slice(startLine, endLine).join('\n');
|
|
113
|
+
// Patterns indicating database storage operations
|
|
114
|
+
// NOTE: We use specific patterns to avoid false positives with API calls like
|
|
115
|
+
// openai.chat.completions.create() or anthropic.messages.create()
|
|
116
|
+
const storagePatterns = [
|
|
117
|
+
/\.insert\s*\(/i,
|
|
118
|
+
/\.update\s*\(/i,
|
|
119
|
+
/\.upsert\s*\(/i,
|
|
120
|
+
/\.save\s*\(/i,
|
|
121
|
+
/INSERT\s+INTO/i,
|
|
122
|
+
/UPDATE\s+.*SET/i,
|
|
123
|
+
// ORM-specific patterns - must have table/model context
|
|
124
|
+
/prisma\.\w+\.create\s*\(/i,
|
|
125
|
+
/supabase\.from\(.*\)\.(?:insert|update|upsert)/i,
|
|
126
|
+
/db\.\w+\.create\s*\(/i,
|
|
127
|
+
/knex\(.*\)\.insert/i,
|
|
128
|
+
/\.collection\(.*\)\.(?:insertOne|updateOne|save)/i, // MongoDB
|
|
129
|
+
/sequelize\..*\.create\s*\(/i,
|
|
130
|
+
// localStorage/sessionStorage (client-side storage)
|
|
131
|
+
/localStorage\.setItem/i,
|
|
132
|
+
/sessionStorage\.setItem/i,
|
|
133
|
+
];
|
|
134
|
+
// Exclude AI SDK patterns that use .create() for API calls, not storage
|
|
135
|
+
const aiApiPatterns = [
|
|
136
|
+
/openai\..*\.create/i,
|
|
137
|
+
/anthropic\..*\.create/i,
|
|
138
|
+
/\.chat\.completions\.create/i,
|
|
139
|
+
/\.messages\.create/i,
|
|
140
|
+
/\.completions\.create/i,
|
|
141
|
+
/\.embeddings\.create/i,
|
|
142
|
+
];
|
|
143
|
+
const hasStoragePattern = storagePatterns.some(p => p.test(context));
|
|
144
|
+
const hasAiApiPattern = aiApiPatterns.some(p => p.test(context));
|
|
145
|
+
// Only consider it storage if we have storage patterns but NOT AI API patterns
|
|
146
|
+
// If both are present, check if storage patterns are separate from AI patterns
|
|
147
|
+
if (hasStoragePattern && hasAiApiPattern) {
|
|
148
|
+
// Check if there's a clear database storage operation separate from AI calls
|
|
149
|
+
// Look for keywords like 'user', 'key', 'credential' near storage patterns
|
|
150
|
+
return /(?:insert|update|upsert|save).*(?:apiKey|api_key|key|credential)/i.test(context) ||
|
|
151
|
+
/(?:apiKey|api_key|key|credential).*(?:insert|update|upsert|save)/i.test(context);
|
|
152
|
+
}
|
|
153
|
+
return hasStoragePattern;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Check if key is only used transiently (same request, not stored)
|
|
157
|
+
*/
|
|
158
|
+
function isTransientKeyUsage(content, lineNumber) {
|
|
159
|
+
const lines = content.split('\n');
|
|
160
|
+
const startLine = Math.max(0, lineNumber - 3);
|
|
161
|
+
const endLine = Math.min(lines.length, lineNumber + 20);
|
|
162
|
+
const context = lines.slice(startLine, endLine).join('\n');
|
|
163
|
+
// Transient usage patterns - key used immediately then discarded
|
|
164
|
+
const transientPatterns = [
|
|
165
|
+
/new\s+(?:OpenAI|Anthropic)\s*\(/i, // Immediate client creation
|
|
166
|
+
/\.create\s*\(\s*\{[^}]*messages/i, // Immediate API call
|
|
167
|
+
/\.completions\.create/i,
|
|
168
|
+
/\.messages\.create/i,
|
|
169
|
+
/await\s+(?:openai|anthropic|client)\./i,
|
|
170
|
+
];
|
|
171
|
+
// If we see API calls but no storage, it's transient
|
|
172
|
+
const hasApiCalls = transientPatterns.some(p => p.test(context));
|
|
173
|
+
const hasStorage = isKeyStoredToDatabase(content, lineNumber);
|
|
174
|
+
return hasApiCalls && !hasStorage;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Check if key appears to be logged (bad practice)
|
|
178
|
+
*/
|
|
179
|
+
function isKeyLogged(content, lineNumber) {
|
|
180
|
+
const lines = content.split('\n');
|
|
181
|
+
const startLine = Math.max(0, lineNumber - 3);
|
|
182
|
+
const endLine = Math.min(lines.length, lineNumber + 10);
|
|
183
|
+
const context = lines.slice(startLine, endLine).join('\n');
|
|
184
|
+
const loggingPatterns = [
|
|
185
|
+
/console\.(log|info|debug|warn|error)\s*\([^)]*(?:apiKey|api_key|key)/i,
|
|
186
|
+
/logger\.(log|info|debug|warn|error)\s*\([^)]*(?:apiKey|api_key|key)/i,
|
|
187
|
+
/\.(log|info|debug|warn|error)\s*\(.*(?:apiKey|api_key|key)/i,
|
|
188
|
+
];
|
|
189
|
+
return loggingPatterns.some(p => p.test(context));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Detect BYOK patterns and classify their risk level
|
|
193
|
+
*/
|
|
194
|
+
function detectBYOKPatterns(content, filePath, middlewareConfig) {
|
|
195
|
+
const vulnerabilities = [];
|
|
196
|
+
const lines = content.split('\n');
|
|
197
|
+
const isTestFile = (0, context_helpers_1.isTestOrMockFile)(filePath);
|
|
198
|
+
// Skip example/demo files entirely - they contain placeholder credentials by design
|
|
199
|
+
if ((0, context_helpers_1.isExampleFile)(filePath)) {
|
|
200
|
+
return vulnerabilities;
|
|
201
|
+
}
|
|
202
|
+
// Check route protection
|
|
203
|
+
const routePath = (0, middleware_detector_1.getRoutePathFromFile)(filePath);
|
|
204
|
+
const middlewareProtection = routePath && middlewareConfig
|
|
205
|
+
? (0, middleware_detector_1.isRouteProtectedByMiddleware)(routePath, middlewareConfig)
|
|
206
|
+
: { isProtected: false, reason: '' };
|
|
207
|
+
// Check for user scoping patterns in the file
|
|
208
|
+
const userScoping = (0, middleware_detector_1.detectUserScopingPatterns)(content);
|
|
209
|
+
// Check for explicit auth in file content (more reliable than middleware detection)
|
|
210
|
+
const hasExplicitAuth = hasExplicitAuthCheck(content);
|
|
211
|
+
lines.forEach((line, index) => {
|
|
212
|
+
if ((0, context_helpers_1.isComment)(line))
|
|
213
|
+
return;
|
|
214
|
+
// Skip lines with example/placeholder credentials
|
|
215
|
+
if (isExampleCredential(line) || (0, context_helpers_1.isPlaceholderValue)('', line)) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
for (const pattern of BYOK_PATTERNS) {
|
|
219
|
+
const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags);
|
|
220
|
+
if (regex.test(line)) {
|
|
221
|
+
// Determine context
|
|
222
|
+
const isAuthenticated = middlewareProtection.isProtected || hasExplicitAuth;
|
|
223
|
+
const isUserScoped = userScoping.hasUserScoping;
|
|
224
|
+
const isStoredCentrally = isKeyStoredToDatabase(content, index);
|
|
225
|
+
const isTransient = isTransientKeyUsage(content, index);
|
|
226
|
+
// Determine severity based on context
|
|
227
|
+
let severity;
|
|
228
|
+
let description;
|
|
229
|
+
let suggestedFix;
|
|
230
|
+
if (isAuthenticated && isTransient) {
|
|
231
|
+
// Authenticated and transient - this is the IDEAL BYOK pattern
|
|
232
|
+
// Check if key is being logged (bad practice)
|
|
233
|
+
const keyLogged = isKeyLogged(content, index);
|
|
234
|
+
if (keyLogged) {
|
|
235
|
+
severity = 'low';
|
|
236
|
+
description = `BYOK feature detected: ${pattern.description}. Keys are used transiently (good!) but appear to be logged (avoid logging API keys, even in debug).`;
|
|
237
|
+
suggestedFix = 'Remove logging of API keys. Best practices: (1) Validate API key format, (2) Add per-user rate limiting.';
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// IDEAL PATTERN: Authenticated + transient + no logging = no issue
|
|
241
|
+
// Skip emitting a finding entirely for the ideal case
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (!isAuthenticated && isTransient) {
|
|
246
|
+
// Unauthenticated but transient - still lower risk than storage
|
|
247
|
+
severity = 'low';
|
|
248
|
+
description = `BYOK feature detected: ${pattern.description}. Keys are used transiently (not stored). Consider adding authentication or rate limiting.`;
|
|
249
|
+
suggestedFix = 'Consider adding authentication. If intentionally public: add rate limiting, key format validation, and usage tracking.';
|
|
250
|
+
}
|
|
251
|
+
else if (!isAuthenticated && isStoredCentrally) {
|
|
252
|
+
// Unauthenticated AND storing keys - this is the real risk
|
|
253
|
+
severity = 'medium';
|
|
254
|
+
description = `${pattern.description}. This endpoint appears to lack authentication AND stores keys. This could allow unauthorized key storage.`;
|
|
255
|
+
suggestedFix = 'Add authentication. Ensure stored keys are scoped by user_id with proper access controls.';
|
|
256
|
+
}
|
|
257
|
+
else if (isStoredCentrally && !isUserScoped) {
|
|
258
|
+
// Authenticated but keys stored without user scoping - medium risk
|
|
259
|
+
severity = 'medium';
|
|
260
|
+
description = `${pattern.description}. Keys appear to be stored centrally without user-scoping, which could lead to cross-tenant key access.`;
|
|
261
|
+
suggestedFix = 'Ensure stored keys are scoped by user_id. Add proper access controls to prevent users from accessing other users\' keys.';
|
|
262
|
+
}
|
|
263
|
+
else if (isAuthenticated && isUserScoped) {
|
|
264
|
+
// Authenticated and user-scoped with storage - generally okay
|
|
265
|
+
severity = 'info';
|
|
266
|
+
description = `${pattern.description}. Route is authenticated and operations appear user-scoped. If keys are stored, consider encryption at rest.`;
|
|
267
|
+
suggestedFix = 'If storing keys: consider encryption at rest. Add rate limiting to prevent cost abuse.';
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
// Authenticated but unclear scoping - needs review, but low priority
|
|
271
|
+
severity = 'info';
|
|
272
|
+
description = `${pattern.description}. Route is authenticated. This appears to be a BYOK feature.`;
|
|
273
|
+
suggestedFix = 'Verify user-scoping for stored keys. Add rate limiting for cost control.';
|
|
274
|
+
}
|
|
275
|
+
// Downgrade test files
|
|
276
|
+
if (isTestFile) {
|
|
277
|
+
severity = 'info';
|
|
278
|
+
description = `${description} (in test file)`;
|
|
279
|
+
}
|
|
280
|
+
vulnerabilities.push({
|
|
281
|
+
id: `byok-${filePath}-${index + 1}-${pattern.name}`,
|
|
282
|
+
filePath,
|
|
283
|
+
lineNumber: index + 1,
|
|
284
|
+
lineContent: line.trim(),
|
|
285
|
+
severity,
|
|
286
|
+
category: 'ai_pattern',
|
|
287
|
+
title: `BYOK: ${pattern.name}`,
|
|
288
|
+
description,
|
|
289
|
+
suggestedFix,
|
|
290
|
+
confidence: isTestFile ? 'low' : 'medium',
|
|
291
|
+
layer: 2,
|
|
292
|
+
});
|
|
293
|
+
break; // One finding per line
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
return vulnerabilities;
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=byok-patterns.js.map
|