@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,336 @@
|
|
|
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
|
+
|
|
7
|
+
import type { Vulnerability, VulnerabilitySeverity } from '../types'
|
|
8
|
+
import type { MiddlewareAuthConfig } from '../utils/middleware-detector'
|
|
9
|
+
import { isComment, isTestOrMockFile, isExampleFile, isPlaceholderValue } from '../utils/context-helpers'
|
|
10
|
+
import { isRouteProtectedByMiddleware, getRoutePathFromFile, detectUserScopingPatterns } from '../utils/middleware-detector'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if line contains example/placeholder API key patterns
|
|
14
|
+
*/
|
|
15
|
+
function isExampleCredential(line: string): boolean {
|
|
16
|
+
const examplePatterns = [
|
|
17
|
+
/your[-_]?api[-_]?key/i,
|
|
18
|
+
/example[-_]?api[-_]?key/i,
|
|
19
|
+
/sample[-_]?key/i,
|
|
20
|
+
/replace[-_]?with/i,
|
|
21
|
+
/placeholder/i,
|
|
22
|
+
/your[-_]?secret/i,
|
|
23
|
+
/example[-_]?secret/i,
|
|
24
|
+
/<.*api.*key.*>/i, // <YOUR_API_KEY>
|
|
25
|
+
/\[.*api.*key.*\]/i, // [API_KEY]
|
|
26
|
+
/xxx+/i,
|
|
27
|
+
/demo[-_]?key/i,
|
|
28
|
+
/test[-_]?key/i,
|
|
29
|
+
/fake[-_]?key/i,
|
|
30
|
+
/mock[-_]?key/i,
|
|
31
|
+
]
|
|
32
|
+
return examplePatterns.some(p => p.test(line))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface BYOKPattern {
|
|
36
|
+
name: string
|
|
37
|
+
pattern: RegExp
|
|
38
|
+
keyType: 'openai' | 'anthropic' | 'generic_ai' | 'generic'
|
|
39
|
+
description: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const BYOK_PATTERNS: BYOKPattern[] = [
|
|
43
|
+
// OpenAI API key patterns
|
|
44
|
+
{
|
|
45
|
+
name: 'User-provided OpenAI API key',
|
|
46
|
+
pattern: /(?:openai|api).*key.*(?:req\.|body\.|params\.|input\.|user)|(?:req\.|body\.|params\.).*(?:openai|api).*key/gi,
|
|
47
|
+
keyType: 'openai',
|
|
48
|
+
description: 'User-provided OpenAI API key detected',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'OpenAI key from request',
|
|
52
|
+
pattern: /new\s+OpenAI\s*\(\s*\{[^}]*apiKey\s*:\s*(?:req|body|params|input)/gi,
|
|
53
|
+
keyType: 'openai',
|
|
54
|
+
description: 'OpenAI client initialized with user-provided key',
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// Anthropic API key patterns
|
|
58
|
+
{
|
|
59
|
+
name: 'User-provided Anthropic API key',
|
|
60
|
+
pattern: /(?:anthropic|claude).*key.*(?:req\.|body\.|params\.|input\.|user)|(?:req\.|body\.|params\.).*(?:anthropic|claude).*key/gi,
|
|
61
|
+
keyType: 'anthropic',
|
|
62
|
+
description: 'User-provided Anthropic API key detected',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'Anthropic key from request',
|
|
66
|
+
pattern: /new\s+Anthropic\s*\(\s*\{[^}]*apiKey\s*:\s*(?:req|body|params|input)/gi,
|
|
67
|
+
keyType: 'anthropic',
|
|
68
|
+
description: 'Anthropic client initialized with user-provided key',
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// Generic AI key patterns
|
|
72
|
+
{
|
|
73
|
+
name: 'User-provided AI API key',
|
|
74
|
+
pattern: /(?:ai|llm|model).*(?:api)?.*key.*(?:req\.|body\.|params\.|input\.)/gi,
|
|
75
|
+
keyType: 'generic_ai',
|
|
76
|
+
description: 'User-provided AI API key detected',
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
// Generic key patterns (more cautious)
|
|
80
|
+
{
|
|
81
|
+
name: 'API key from user input',
|
|
82
|
+
pattern: /apiKey\s*[=:]\s*(?:req|body|params|input)\./gi,
|
|
83
|
+
keyType: 'generic',
|
|
84
|
+
description: 'API key received from user input',
|
|
85
|
+
},
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if file has explicit authentication checks
|
|
90
|
+
*/
|
|
91
|
+
function hasExplicitAuthCheck(content: string): boolean {
|
|
92
|
+
const authPatterns = [
|
|
93
|
+
// Common auth check functions
|
|
94
|
+
/getCurrentUser(Id)?\s*\(/i,
|
|
95
|
+
/getSession\s*\(/i,
|
|
96
|
+
/getServerSession\s*\(/i,
|
|
97
|
+
/auth\s*\(\s*\)/i,
|
|
98
|
+
/requireAuth\s*\(/i,
|
|
99
|
+
/verifyToken\s*\(/i,
|
|
100
|
+
/isAuthenticated/i,
|
|
101
|
+
/checkAuth\s*\(/i,
|
|
102
|
+
/withAuth\s*\(/i,
|
|
103
|
+
// Request user checks
|
|
104
|
+
/req\.user|request\.user/i,
|
|
105
|
+
/context\.user/i,
|
|
106
|
+
// Supabase auth
|
|
107
|
+
/supabase\.auth\.getUser/i,
|
|
108
|
+
/supabase\.auth\.getSession/i,
|
|
109
|
+
// NextAuth
|
|
110
|
+
/getServerSession\s*\(/i,
|
|
111
|
+
/useSession\s*\(/i,
|
|
112
|
+
// Clerk
|
|
113
|
+
/currentUser\s*\(/i,
|
|
114
|
+
/auth\(\)\.protect/i,
|
|
115
|
+
]
|
|
116
|
+
return authPatterns.some(p => p.test(content))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check if key appears to be stored to database
|
|
121
|
+
*/
|
|
122
|
+
function isKeyStoredToDatabase(content: string, lineNumber: number): boolean {
|
|
123
|
+
const lines = content.split('\n')
|
|
124
|
+
const startLine = Math.max(0, lineNumber - 5)
|
|
125
|
+
const endLine = Math.min(lines.length, lineNumber + 15)
|
|
126
|
+
const context = lines.slice(startLine, endLine).join('\n')
|
|
127
|
+
|
|
128
|
+
// Patterns indicating database storage operations
|
|
129
|
+
// NOTE: We use specific patterns to avoid false positives with API calls like
|
|
130
|
+
// openai.chat.completions.create() or anthropic.messages.create()
|
|
131
|
+
const storagePatterns = [
|
|
132
|
+
/\.insert\s*\(/i,
|
|
133
|
+
/\.update\s*\(/i,
|
|
134
|
+
/\.upsert\s*\(/i,
|
|
135
|
+
/\.save\s*\(/i,
|
|
136
|
+
/INSERT\s+INTO/i,
|
|
137
|
+
/UPDATE\s+.*SET/i,
|
|
138
|
+
// ORM-specific patterns - must have table/model context
|
|
139
|
+
/prisma\.\w+\.create\s*\(/i,
|
|
140
|
+
/supabase\.from\(.*\)\.(?:insert|update|upsert)/i,
|
|
141
|
+
/db\.\w+\.create\s*\(/i,
|
|
142
|
+
/knex\(.*\)\.insert/i,
|
|
143
|
+
/\.collection\(.*\)\.(?:insertOne|updateOne|save)/i, // MongoDB
|
|
144
|
+
/sequelize\..*\.create\s*\(/i,
|
|
145
|
+
// localStorage/sessionStorage (client-side storage)
|
|
146
|
+
/localStorage\.setItem/i,
|
|
147
|
+
/sessionStorage\.setItem/i,
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
// Exclude AI SDK patterns that use .create() for API calls, not storage
|
|
151
|
+
const aiApiPatterns = [
|
|
152
|
+
/openai\..*\.create/i,
|
|
153
|
+
/anthropic\..*\.create/i,
|
|
154
|
+
/\.chat\.completions\.create/i,
|
|
155
|
+
/\.messages\.create/i,
|
|
156
|
+
/\.completions\.create/i,
|
|
157
|
+
/\.embeddings\.create/i,
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
const hasStoragePattern = storagePatterns.some(p => p.test(context))
|
|
161
|
+
const hasAiApiPattern = aiApiPatterns.some(p => p.test(context))
|
|
162
|
+
|
|
163
|
+
// Only consider it storage if we have storage patterns but NOT AI API patterns
|
|
164
|
+
// If both are present, check if storage patterns are separate from AI patterns
|
|
165
|
+
if (hasStoragePattern && hasAiApiPattern) {
|
|
166
|
+
// Check if there's a clear database storage operation separate from AI calls
|
|
167
|
+
// Look for keywords like 'user', 'key', 'credential' near storage patterns
|
|
168
|
+
return /(?:insert|update|upsert|save).*(?:apiKey|api_key|key|credential)/i.test(context) ||
|
|
169
|
+
/(?:apiKey|api_key|key|credential).*(?:insert|update|upsert|save)/i.test(context)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return hasStoragePattern
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check if key is only used transiently (same request, not stored)
|
|
177
|
+
*/
|
|
178
|
+
function isTransientKeyUsage(content: string, lineNumber: number): boolean {
|
|
179
|
+
const lines = content.split('\n')
|
|
180
|
+
const startLine = Math.max(0, lineNumber - 3)
|
|
181
|
+
const endLine = Math.min(lines.length, lineNumber + 20)
|
|
182
|
+
const context = lines.slice(startLine, endLine).join('\n')
|
|
183
|
+
|
|
184
|
+
// Transient usage patterns - key used immediately then discarded
|
|
185
|
+
const transientPatterns = [
|
|
186
|
+
/new\s+(?:OpenAI|Anthropic)\s*\(/i, // Immediate client creation
|
|
187
|
+
/\.create\s*\(\s*\{[^}]*messages/i, // Immediate API call
|
|
188
|
+
/\.completions\.create/i,
|
|
189
|
+
/\.messages\.create/i,
|
|
190
|
+
/await\s+(?:openai|anthropic|client)\./i,
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
// If we see API calls but no storage, it's transient
|
|
194
|
+
const hasApiCalls = transientPatterns.some(p => p.test(context))
|
|
195
|
+
const hasStorage = isKeyStoredToDatabase(content, lineNumber)
|
|
196
|
+
|
|
197
|
+
return hasApiCalls && !hasStorage
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Check if key appears to be logged (bad practice)
|
|
202
|
+
*/
|
|
203
|
+
function isKeyLogged(content: string, lineNumber: number): boolean {
|
|
204
|
+
const lines = content.split('\n')
|
|
205
|
+
const startLine = Math.max(0, lineNumber - 3)
|
|
206
|
+
const endLine = Math.min(lines.length, lineNumber + 10)
|
|
207
|
+
const context = lines.slice(startLine, endLine).join('\n')
|
|
208
|
+
|
|
209
|
+
const loggingPatterns = [
|
|
210
|
+
/console\.(log|info|debug|warn|error)\s*\([^)]*(?:apiKey|api_key|key)/i,
|
|
211
|
+
/logger\.(log|info|debug|warn|error)\s*\([^)]*(?:apiKey|api_key|key)/i,
|
|
212
|
+
/\.(log|info|debug|warn|error)\s*\(.*(?:apiKey|api_key|key)/i,
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
return loggingPatterns.some(p => p.test(context))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Detect BYOK patterns and classify their risk level
|
|
220
|
+
*/
|
|
221
|
+
export function detectBYOKPatterns(
|
|
222
|
+
content: string,
|
|
223
|
+
filePath: string,
|
|
224
|
+
middlewareConfig?: MiddlewareAuthConfig
|
|
225
|
+
): Vulnerability[] {
|
|
226
|
+
const vulnerabilities: Vulnerability[] = []
|
|
227
|
+
const lines = content.split('\n')
|
|
228
|
+
const isTestFile = isTestOrMockFile(filePath)
|
|
229
|
+
|
|
230
|
+
// Skip example/demo files entirely - they contain placeholder credentials by design
|
|
231
|
+
if (isExampleFile(filePath)) {
|
|
232
|
+
return vulnerabilities
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Check route protection
|
|
236
|
+
const routePath = getRoutePathFromFile(filePath)
|
|
237
|
+
const middlewareProtection = routePath && middlewareConfig
|
|
238
|
+
? isRouteProtectedByMiddleware(routePath, middlewareConfig)
|
|
239
|
+
: { isProtected: false, reason: '' }
|
|
240
|
+
|
|
241
|
+
// Check for user scoping patterns in the file
|
|
242
|
+
const userScoping = detectUserScopingPatterns(content)
|
|
243
|
+
|
|
244
|
+
// Check for explicit auth in file content (more reliable than middleware detection)
|
|
245
|
+
const hasExplicitAuth = hasExplicitAuthCheck(content)
|
|
246
|
+
|
|
247
|
+
lines.forEach((line, index) => {
|
|
248
|
+
if (isComment(line)) return
|
|
249
|
+
|
|
250
|
+
// Skip lines with example/placeholder credentials
|
|
251
|
+
if (isExampleCredential(line) || isPlaceholderValue('', line)) {
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
for (const pattern of BYOK_PATTERNS) {
|
|
256
|
+
const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
|
|
257
|
+
|
|
258
|
+
if (regex.test(line)) {
|
|
259
|
+
// Determine context
|
|
260
|
+
const isAuthenticated = middlewareProtection.isProtected || hasExplicitAuth
|
|
261
|
+
const isUserScoped = userScoping.hasUserScoping
|
|
262
|
+
const isStoredCentrally = isKeyStoredToDatabase(content, index)
|
|
263
|
+
const isTransient = isTransientKeyUsage(content, index)
|
|
264
|
+
|
|
265
|
+
// Determine severity based on context
|
|
266
|
+
let severity: VulnerabilitySeverity
|
|
267
|
+
let description: string
|
|
268
|
+
let suggestedFix: string
|
|
269
|
+
|
|
270
|
+
if (isAuthenticated && isTransient) {
|
|
271
|
+
// Authenticated and transient - this is the IDEAL BYOK pattern
|
|
272
|
+
// Check if key is being logged (bad practice)
|
|
273
|
+
const keyLogged = isKeyLogged(content, index)
|
|
274
|
+
if (keyLogged) {
|
|
275
|
+
severity = 'low'
|
|
276
|
+
description = `BYOK feature detected: ${pattern.description}. Keys are used transiently (good!) but appear to be logged (avoid logging API keys, even in debug).`
|
|
277
|
+
suggestedFix = 'Remove logging of API keys. Best practices: (1) Validate API key format, (2) Add per-user rate limiting.'
|
|
278
|
+
} else {
|
|
279
|
+
// IDEAL PATTERN: Authenticated + transient + no logging = no issue
|
|
280
|
+
// Skip emitting a finding entirely for the ideal case
|
|
281
|
+
continue
|
|
282
|
+
}
|
|
283
|
+
} else if (!isAuthenticated && isTransient) {
|
|
284
|
+
// Unauthenticated but transient - still lower risk than storage
|
|
285
|
+
severity = 'low'
|
|
286
|
+
description = `BYOK feature detected: ${pattern.description}. Keys are used transiently (not stored). Consider adding authentication or rate limiting.`
|
|
287
|
+
suggestedFix = 'Consider adding authentication. If intentionally public: add rate limiting, key format validation, and usage tracking.'
|
|
288
|
+
} else if (!isAuthenticated && isStoredCentrally) {
|
|
289
|
+
// Unauthenticated AND storing keys - this is the real risk
|
|
290
|
+
severity = 'medium'
|
|
291
|
+
description = `${pattern.description}. This endpoint appears to lack authentication AND stores keys. This could allow unauthorized key storage.`
|
|
292
|
+
suggestedFix = 'Add authentication. Ensure stored keys are scoped by user_id with proper access controls.'
|
|
293
|
+
} else if (isStoredCentrally && !isUserScoped) {
|
|
294
|
+
// Authenticated but keys stored without user scoping - medium risk
|
|
295
|
+
severity = 'medium'
|
|
296
|
+
description = `${pattern.description}. Keys appear to be stored centrally without user-scoping, which could lead to cross-tenant key access.`
|
|
297
|
+
suggestedFix = 'Ensure stored keys are scoped by user_id. Add proper access controls to prevent users from accessing other users\' keys.'
|
|
298
|
+
} else if (isAuthenticated && isUserScoped) {
|
|
299
|
+
// Authenticated and user-scoped with storage - generally okay
|
|
300
|
+
severity = 'info'
|
|
301
|
+
description = `${pattern.description}. Route is authenticated and operations appear user-scoped. If keys are stored, consider encryption at rest.`
|
|
302
|
+
suggestedFix = 'If storing keys: consider encryption at rest. Add rate limiting to prevent cost abuse.'
|
|
303
|
+
} else {
|
|
304
|
+
// Authenticated but unclear scoping - needs review, but low priority
|
|
305
|
+
severity = 'info'
|
|
306
|
+
description = `${pattern.description}. Route is authenticated. This appears to be a BYOK feature.`
|
|
307
|
+
suggestedFix = 'Verify user-scoping for stored keys. Add rate limiting for cost control.'
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Downgrade test files
|
|
311
|
+
if (isTestFile) {
|
|
312
|
+
severity = 'info'
|
|
313
|
+
description = `${description} (in test file)`
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
vulnerabilities.push({
|
|
317
|
+
id: `byok-${filePath}-${index + 1}-${pattern.name}`,
|
|
318
|
+
filePath,
|
|
319
|
+
lineNumber: index + 1,
|
|
320
|
+
lineContent: line.trim(),
|
|
321
|
+
severity,
|
|
322
|
+
category: 'ai_pattern',
|
|
323
|
+
title: `BYOK: ${pattern.name}`,
|
|
324
|
+
description,
|
|
325
|
+
suggestedFix,
|
|
326
|
+
confidence: isTestFile ? 'low' : 'medium',
|
|
327
|
+
layer: 2,
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
break // One finding per line
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
return vulnerabilities
|
|
336
|
+
}
|