@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,516 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 1: Known Pattern Matching
|
|
3
|
+
* Curated library of high-fidelity regex patterns for detecting secrets
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { SecretPattern, Vulnerability } from '../types'
|
|
7
|
+
import {
|
|
8
|
+
isServerOnlyFile,
|
|
9
|
+
isExampleFile,
|
|
10
|
+
isEnvVarReference,
|
|
11
|
+
isNextPublicEnvVar,
|
|
12
|
+
isComment,
|
|
13
|
+
isPlaceholderValue,
|
|
14
|
+
isTestOrMockFile,
|
|
15
|
+
isScannerOrFixtureFile,
|
|
16
|
+
isBYOKContext,
|
|
17
|
+
getServiceRoleKeyContext,
|
|
18
|
+
} from '../utils/context-helpers'
|
|
19
|
+
|
|
20
|
+
// Check if file is documentation/README
|
|
21
|
+
function isDocumentationFile(filePath: string): boolean {
|
|
22
|
+
const docPatterns = [
|
|
23
|
+
/README/i,
|
|
24
|
+
/CHANGELOG/i,
|
|
25
|
+
/CONTRIBUTING/i,
|
|
26
|
+
/LICENSE/i,
|
|
27
|
+
/CODE_OF_CONDUCT/i,
|
|
28
|
+
/SECURITY/i,
|
|
29
|
+
/AUTHORS/i,
|
|
30
|
+
/HISTORY/i,
|
|
31
|
+
/\.md$/i,
|
|
32
|
+
/\.mdx$/i,
|
|
33
|
+
/\.rst$/i, // reStructuredText
|
|
34
|
+
/\.adoc$/i, // AsciiDoc
|
|
35
|
+
/\.txt$/i, // Plain text docs
|
|
36
|
+
/\/docs\//i,
|
|
37
|
+
/\/documentation\//i,
|
|
38
|
+
/\/wiki\//i,
|
|
39
|
+
/\/guides?\//i,
|
|
40
|
+
/\/tutorials?\//i,
|
|
41
|
+
/\/examples?\//i, // Example directories often have sample configs
|
|
42
|
+
]
|
|
43
|
+
return docPatterns.some(p => p.test(filePath))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check if line contains example/sample placeholder values (not real secrets)
|
|
47
|
+
function isExamplePlaceholder(line: string, value: string): boolean {
|
|
48
|
+
const placeholderPatterns = [
|
|
49
|
+
// Common placeholder indicators in the line context
|
|
50
|
+
/example/i,
|
|
51
|
+
/sample/i,
|
|
52
|
+
/demo/i,
|
|
53
|
+
/placeholder/i,
|
|
54
|
+
/your[_-]?api[_-]?key/i,
|
|
55
|
+
/your[_-]?secret/i,
|
|
56
|
+
/replace[_-]?with/i,
|
|
57
|
+
/insert[_-]?here/i,
|
|
58
|
+
/xxx+/i,
|
|
59
|
+
/yyy+/i,
|
|
60
|
+
/todo/i,
|
|
61
|
+
/fixme/i,
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
// Check if the value itself looks like a placeholder
|
|
65
|
+
const valuePlaceholderPatterns = [
|
|
66
|
+
/^your[_-]/i,
|
|
67
|
+
/^my[_-]/i,
|
|
68
|
+
/^test[_-]/i,
|
|
69
|
+
/^sample[_-]/i,
|
|
70
|
+
/^example[_-]/i,
|
|
71
|
+
/^demo[_-]/i,
|
|
72
|
+
/^placeholder/i,
|
|
73
|
+
/^xxx+$/i,
|
|
74
|
+
/^yyy+$/i,
|
|
75
|
+
/^\*+$/, // Just asterisks
|
|
76
|
+
/^\.{3,}$/, // Just dots
|
|
77
|
+
/^<.*>$/, // Angle bracket placeholders like <YOUR_KEY>
|
|
78
|
+
/^\[.*\]$/, // Square bracket placeholders like [YOUR_KEY]
|
|
79
|
+
/^\{.*\}$/, // Curly bracket placeholders like {YOUR_KEY}
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
return placeholderPatterns.some(p => p.test(line)) ||
|
|
83
|
+
valuePlaceholderPatterns.some(p => p.test(value))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check if the variable name indicates test/mock data
|
|
87
|
+
function hasTestVariableName(line: string): boolean {
|
|
88
|
+
const varNamePatterns = [
|
|
89
|
+
// JS/TS variable declarations: const TEST_API_KEY = "..."
|
|
90
|
+
/(?:const|let|var|export\s+const|export\s+let)\s+([A-Z_][A-Z0-9_]*)\s*=/i,
|
|
91
|
+
// Object property shorthand or assignment: TEST_KEY: "..." or "testKey": "..."
|
|
92
|
+
/([A-Z_][A-Z0-9_]*)\s*:\s*['"`]/,
|
|
93
|
+
// JSON-style keys: "test_key": or 'testKey':
|
|
94
|
+
/['"]([a-zA-Z_][a-zA-Z0-9_]*)['"]\s*:/,
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
// Keywords that indicate test/mock data when in variable names
|
|
98
|
+
const testKeywords = /^(TEST|MOCK|EXAMPLE|DUMMY|FAKE|SAMPLE|PLACEHOLDER|DEMO)[_A-Z0-9]*$/i
|
|
99
|
+
const testSuffixes = /_?(TEST|MOCK|EXAMPLE|DUMMY|FAKE|SAMPLE)$/i
|
|
100
|
+
|
|
101
|
+
for (const pattern of varNamePatterns) {
|
|
102
|
+
const match = line.match(pattern)
|
|
103
|
+
if (match && match[1]) {
|
|
104
|
+
const varName = match[1]
|
|
105
|
+
if (testKeywords.test(varName) || testSuffixes.test(varName)) {
|
|
106
|
+
return true
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Comprehensive list of secret patterns with specific prefixes
|
|
114
|
+
export const SECRET_PATTERNS: SecretPattern[] = [
|
|
115
|
+
// API Keys with known prefixes
|
|
116
|
+
{
|
|
117
|
+
name: 'OpenAI API Key',
|
|
118
|
+
pattern: /sk-[a-zA-Z0-9]{20,}/g,
|
|
119
|
+
severity: 'critical',
|
|
120
|
+
description: 'OpenAI API key detected',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'OpenAI Project API Key',
|
|
124
|
+
pattern: /sk-proj-[a-zA-Z0-9]{48,}/g,
|
|
125
|
+
severity: 'critical',
|
|
126
|
+
description: 'OpenAI project API key detected (new format)',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'Anthropic API Key',
|
|
130
|
+
pattern: /sk-ant-[a-zA-Z0-9-]{20,}/g,
|
|
131
|
+
severity: 'critical',
|
|
132
|
+
description: 'Anthropic API key detected',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'Anthropic API Key (Full)',
|
|
136
|
+
pattern: /sk-ant-api03-[a-zA-Z0-9_-]{90,}/g,
|
|
137
|
+
severity: 'critical',
|
|
138
|
+
description: 'Anthropic API key detected (full format)',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'GitHub Token',
|
|
142
|
+
pattern: /ghp_[a-zA-Z0-9]{36,}/g,
|
|
143
|
+
severity: 'critical',
|
|
144
|
+
description: 'GitHub personal access token detected',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'GitHub OAuth Token',
|
|
148
|
+
pattern: /gho_[a-zA-Z0-9]{36,}/g,
|
|
149
|
+
severity: 'critical',
|
|
150
|
+
description: 'GitHub OAuth token detected',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'GitHub App Token',
|
|
154
|
+
pattern: /ghu_[a-zA-Z0-9]{36,}/g,
|
|
155
|
+
severity: 'critical',
|
|
156
|
+
description: 'GitHub App user token detected',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'GitHub Refresh Token',
|
|
160
|
+
pattern: /ghr_[a-zA-Z0-9]{36,}/g,
|
|
161
|
+
severity: 'critical',
|
|
162
|
+
description: 'GitHub refresh token detected',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'Stripe Secret Key',
|
|
166
|
+
pattern: /sk_live_[a-zA-Z0-9]{24,}/g,
|
|
167
|
+
severity: 'critical',
|
|
168
|
+
description: 'Stripe live secret key detected',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'Stripe Test Key',
|
|
172
|
+
pattern: /sk_test_[a-zA-Z0-9]{24,}/g,
|
|
173
|
+
severity: 'medium',
|
|
174
|
+
description: 'Stripe test secret key detected',
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: 'Stripe Publishable Key',
|
|
178
|
+
pattern: /pk_(live|test)_[a-zA-Z0-9]{24,}/g,
|
|
179
|
+
severity: 'low',
|
|
180
|
+
description: 'Stripe publishable key detected (usually safe but verify)',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: 'Square Access Token',
|
|
184
|
+
pattern: /sq0csp-[a-zA-Z0-9-_]{43}/g,
|
|
185
|
+
severity: 'critical',
|
|
186
|
+
description: 'Square access token detected',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'AWS Access Key ID',
|
|
190
|
+
pattern: /AKIA[0-9A-Z]{16}/g,
|
|
191
|
+
severity: 'critical',
|
|
192
|
+
description: 'AWS Access Key ID detected',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: 'AWS Secret Access Key',
|
|
196
|
+
// AWS secret keys are exactly 40 chars, base64-like, and typically appear near AWS context
|
|
197
|
+
// The pattern requires it to be in an AWS-related context (variable name, nearby AKIA, etc.)
|
|
198
|
+
pattern: /(?:aws[_-]?secret[_-]?(?:access[_-]?)?key|secret[_-]?access[_-]?key|AWS_SECRET)\s*[:=]\s*['"`]?([a-zA-Z0-9/+=]{40})['"`]?/gi,
|
|
199
|
+
severity: 'critical',
|
|
200
|
+
description: 'AWS Secret Access Key detected',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'Google API Key',
|
|
204
|
+
pattern: /AIza[0-9A-Za-z-_]{35}/g,
|
|
205
|
+
severity: 'high',
|
|
206
|
+
description: 'Google API key detected',
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: 'Slack Token',
|
|
210
|
+
pattern: /xox[baprs]-[0-9a-zA-Z]{10,}/g,
|
|
211
|
+
severity: 'critical',
|
|
212
|
+
description: 'Slack token detected',
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'Slack Webhook',
|
|
216
|
+
pattern: /https:\/\/hooks\.slack\.com\/services\/T[a-zA-Z0-9_]+\/B[a-zA-Z0-9_]+\/[a-zA-Z0-9_]+/g,
|
|
217
|
+
severity: 'high',
|
|
218
|
+
description: 'Slack webhook URL detected',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'Discord Webhook',
|
|
222
|
+
pattern: /https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[a-zA-Z0-9_-]+/g,
|
|
223
|
+
severity: 'high',
|
|
224
|
+
description: 'Discord webhook URL detected',
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: 'Twilio API Key',
|
|
228
|
+
pattern: /SK[a-f0-9]{32}/g,
|
|
229
|
+
severity: 'critical',
|
|
230
|
+
description: 'Twilio API key detected',
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: 'SendGrid API Key',
|
|
234
|
+
pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/g,
|
|
235
|
+
severity: 'critical',
|
|
236
|
+
description: 'SendGrid API key detected',
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'Mailgun API Key',
|
|
240
|
+
pattern: /key-[a-zA-Z0-9]{32}/g,
|
|
241
|
+
severity: 'critical',
|
|
242
|
+
description: 'Mailgun API key detected',
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'Firebase API Key',
|
|
246
|
+
pattern: /AIza[0-9A-Za-z-_]{35}/g,
|
|
247
|
+
severity: 'high',
|
|
248
|
+
description: 'Firebase API key detected',
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: 'Supabase Anon Key',
|
|
252
|
+
pattern: /eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,
|
|
253
|
+
severity: 'low',
|
|
254
|
+
description: 'Supabase anon key detected (usually safe for client-side)',
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: 'Supabase Service Role Key',
|
|
258
|
+
pattern: /eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,
|
|
259
|
+
severity: 'critical',
|
|
260
|
+
description: 'JWT token detected - verify if this is a service role key',
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: 'Heroku API Key',
|
|
264
|
+
pattern: /[h|H][e|E][r|R][o|O][k|K][u|U].*[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/gi,
|
|
265
|
+
severity: 'critical',
|
|
266
|
+
description: 'Heroku API key detected',
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: 'NPM Token',
|
|
270
|
+
pattern: /npm_[a-zA-Z0-9]{36}/g,
|
|
271
|
+
severity: 'critical',
|
|
272
|
+
description: 'NPM access token detected',
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: 'PyPI Token',
|
|
276
|
+
pattern: /pypi-[a-zA-Z0-9]{32,}/g,
|
|
277
|
+
severity: 'critical',
|
|
278
|
+
description: 'PyPI API token detected',
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'Private Key',
|
|
282
|
+
pattern: /-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
|
|
283
|
+
severity: 'critical',
|
|
284
|
+
description: 'Private key detected',
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: 'Generic Password in URL',
|
|
288
|
+
pattern: /[a-zA-Z]{3,10}:\/\/[^:]+:[^@]+@/g,
|
|
289
|
+
severity: 'high',
|
|
290
|
+
description: 'Password in URL detected',
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: 'Database Connection String',
|
|
294
|
+
pattern: /(mongodb|postgres|mysql|redis|amqp):\/\/[^\s"']+/gi,
|
|
295
|
+
severity: 'high',
|
|
296
|
+
description: 'Database connection string detected - may contain credentials',
|
|
297
|
+
},
|
|
298
|
+
// Additional patterns from checklist
|
|
299
|
+
{
|
|
300
|
+
name: 'Hardcoded JWT Token',
|
|
301
|
+
pattern: /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g,
|
|
302
|
+
severity: 'high',
|
|
303
|
+
description: 'Hardcoded JWT token detected',
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: 'PostgreSQL Connection String',
|
|
307
|
+
pattern: /postgres:\/\/[^:]+:[^@]+@[^\s"']+/gi,
|
|
308
|
+
severity: 'critical',
|
|
309
|
+
description: 'PostgreSQL connection string with credentials detected',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: 'MongoDB Connection String',
|
|
313
|
+
pattern: /mongodb(\+srv)?:\/\/[^:]+:[^@]+@[^\s"']+/gi,
|
|
314
|
+
severity: 'critical',
|
|
315
|
+
description: 'MongoDB connection string with credentials detected',
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: 'MySQL Connection String',
|
|
319
|
+
pattern: /mysql:\/\/[^:]+:[^@]+@[^\s"']+/gi,
|
|
320
|
+
severity: 'critical',
|
|
321
|
+
description: 'MySQL connection string with credentials detected',
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: 'Generic API Key Assignment',
|
|
325
|
+
pattern: /['"']?api[_-]?key['"']?\s*[:=]\s*['"][a-zA-Z0-9_-]{20,}['"]/gi,
|
|
326
|
+
severity: 'medium',
|
|
327
|
+
description: 'Possible API key assignment detected - requires validation',
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: 'Generic Secret Key Assignment',
|
|
331
|
+
pattern: /['"']?secret[_-]?key['"']?\s*[:=]\s*['"][a-zA-Z0-9_-]{20,}['"]/gi,
|
|
332
|
+
severity: 'medium',
|
|
333
|
+
description: 'Possible secret key assignment detected - requires validation',
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
name: 'Vercel Token',
|
|
337
|
+
pattern: /vercel_[a-zA-Z0-9]{24,}/gi,
|
|
338
|
+
severity: 'critical',
|
|
339
|
+
description: 'Vercel API token detected',
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: 'Netlify Token',
|
|
343
|
+
pattern: /nfp_[a-zA-Z0-9]{40,}/gi,
|
|
344
|
+
severity: 'critical',
|
|
345
|
+
description: 'Netlify personal access token detected',
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: 'Cloudflare API Token',
|
|
349
|
+
pattern: /[a-zA-Z0-9_-]{40}(?=.*cloudflare)/gi,
|
|
350
|
+
severity: 'high',
|
|
351
|
+
description: 'Potential Cloudflare API token detected',
|
|
352
|
+
},
|
|
353
|
+
]
|
|
354
|
+
|
|
355
|
+
export function detectKnownPatterns(
|
|
356
|
+
content: string,
|
|
357
|
+
filePath: string
|
|
358
|
+
): Vulnerability[] {
|
|
359
|
+
const vulnerabilities: Vulnerability[] = []
|
|
360
|
+
const lines = content.split('\n')
|
|
361
|
+
|
|
362
|
+
// Skip example files entirely
|
|
363
|
+
if (isExampleFile(filePath)) {
|
|
364
|
+
return vulnerabilities
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Skip scanner/fixture files to avoid self-detection
|
|
368
|
+
if (isScannerOrFixtureFile(filePath)) {
|
|
369
|
+
return vulnerabilities
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Skip documentation/README files
|
|
373
|
+
if (isDocumentationFile(filePath)) {
|
|
374
|
+
return vulnerabilities
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Check context for file-level decisions
|
|
378
|
+
const isServerFile = isServerOnlyFile(filePath)
|
|
379
|
+
const isTestFile = isTestOrMockFile(filePath)
|
|
380
|
+
|
|
381
|
+
for (const secretPattern of SECRET_PATTERNS) {
|
|
382
|
+
lines.forEach((line, index) => {
|
|
383
|
+
// Skip comments
|
|
384
|
+
if (isComment(line)) return
|
|
385
|
+
|
|
386
|
+
// Reset regex state
|
|
387
|
+
const regex = new RegExp(secretPattern.pattern.source, secretPattern.pattern.flags)
|
|
388
|
+
let match
|
|
389
|
+
|
|
390
|
+
while ((match = regex.exec(line)) !== null) {
|
|
391
|
+
const value = match[0]
|
|
392
|
+
|
|
393
|
+
// Skip placeholder values
|
|
394
|
+
if (isPlaceholderValue(value, line)) {
|
|
395
|
+
continue
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Skip obvious example/sample values
|
|
399
|
+
if (/example|sample|demo|test|dummy|fake|mock/i.test(value)) {
|
|
400
|
+
continue
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Skip values that look like format descriptions
|
|
404
|
+
if (/^[a-z]+_[a-z]+_[a-z]+$/i.test(value) || /^your[_-]/i.test(value)) {
|
|
405
|
+
continue
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Skip example/placeholder values (more comprehensive check)
|
|
409
|
+
if (isExamplePlaceholder(line, value)) {
|
|
410
|
+
continue
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Skip secrets in variables with test/mock names (e.g., TEST_API_KEY, MOCK_SECRET)
|
|
414
|
+
if (hasTestVariableName(line)) {
|
|
415
|
+
continue
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Check for BYOK (Bring Your Own Key) context - this is a feature, not a vulnerability
|
|
419
|
+
if (isBYOKContext(line, filePath)) {
|
|
420
|
+
// Skip BYOK patterns entirely if they're properly handled in server context
|
|
421
|
+
if (isServerFile && isEnvVarReference(line)) {
|
|
422
|
+
continue
|
|
423
|
+
}
|
|
424
|
+
// For client-side BYOK forms, we still don't flag - it's user input
|
|
425
|
+
// Only flag if it's a hardcoded key being exposed
|
|
426
|
+
if (!isEnvVarReference(line) && line.includes('=') && /['"`][a-zA-Z0-9_-]{20,}['"`]/.test(line)) {
|
|
427
|
+
// This might be a hardcoded default key in a BYOK context - needs review
|
|
428
|
+
} else {
|
|
429
|
+
continue
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Determine severity based on context
|
|
434
|
+
let adjustedSeverity = secretPattern.severity
|
|
435
|
+
let requiresAIValidation = false
|
|
436
|
+
let adjustedDescription = secretPattern.description
|
|
437
|
+
let adjustedConfidence: 'high' | 'medium' | 'low' = 'high'
|
|
438
|
+
|
|
439
|
+
// Check if this is a Supabase service role key or JWT
|
|
440
|
+
const isSupabaseOrJWT =
|
|
441
|
+
secretPattern.name.includes('Supabase') ||
|
|
442
|
+
secretPattern.name.includes('JWT') ||
|
|
443
|
+
/eyJ[a-zA-Z0-9_-]+\.eyJ/.test(value)
|
|
444
|
+
|
|
445
|
+
if (isSupabaseOrJWT) {
|
|
446
|
+
// Use the comprehensive service role context checker
|
|
447
|
+
const serviceRoleContext = getServiceRoleKeyContext(line, filePath)
|
|
448
|
+
|
|
449
|
+
if (serviceRoleContext === 'safe_server') {
|
|
450
|
+
// Server-only + env var = expected pattern, skip entirely
|
|
451
|
+
continue
|
|
452
|
+
} else if (serviceRoleContext === 'client_exposure') {
|
|
453
|
+
// Client exposure = critical
|
|
454
|
+
adjustedSeverity = 'critical'
|
|
455
|
+
requiresAIValidation = true
|
|
456
|
+
adjustedDescription = isNextPublicEnvVar(line)
|
|
457
|
+
? `${secretPattern.description} - EXPOSED via NEXT_PUBLIC_ prefix (client-accessible)`
|
|
458
|
+
: `${secretPattern.description} - may be exposed to client bundle`
|
|
459
|
+
} else {
|
|
460
|
+
// needs_review - check more context
|
|
461
|
+
if (isEnvVarReference(line)) {
|
|
462
|
+
// Using env var but context unclear - validate
|
|
463
|
+
adjustedSeverity = 'medium'
|
|
464
|
+
requiresAIValidation = true
|
|
465
|
+
adjustedDescription = `${secretPattern.description} - verify this is not exposed to client`
|
|
466
|
+
} else if (isServerFile) {
|
|
467
|
+
// Hardcoded in server file - bad but not critical
|
|
468
|
+
adjustedSeverity = 'high'
|
|
469
|
+
requiresAIValidation = true
|
|
470
|
+
adjustedDescription = `${secretPattern.description} - hardcoded in server file, should use env var`
|
|
471
|
+
} else {
|
|
472
|
+
// Hardcoded in unknown context - critical + needs validation
|
|
473
|
+
adjustedSeverity = 'critical'
|
|
474
|
+
requiresAIValidation = true
|
|
475
|
+
adjustedDescription = `${secretPattern.description} - hardcoded secret may be exposed`
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Downgrade test file severity
|
|
481
|
+
if (isTestFile) {
|
|
482
|
+
if (adjustedSeverity === 'critical') {
|
|
483
|
+
adjustedSeverity = 'medium'
|
|
484
|
+
} else if (adjustedSeverity === 'high') {
|
|
485
|
+
adjustedSeverity = 'low'
|
|
486
|
+
} else {
|
|
487
|
+
adjustedSeverity = 'info'
|
|
488
|
+
}
|
|
489
|
+
adjustedConfidence = 'low'
|
|
490
|
+
adjustedDescription = `${adjustedDescription} (in test file)`
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Generic patterns always require AI validation
|
|
494
|
+
const isGenericPattern = secretPattern.name.includes('Generic')
|
|
495
|
+
const finalRequiresAIValidation = requiresAIValidation || isGenericPattern
|
|
496
|
+
|
|
497
|
+
vulnerabilities.push({
|
|
498
|
+
id: `pattern-${filePath}-${index + 1}-${secretPattern.name}`,
|
|
499
|
+
filePath,
|
|
500
|
+
lineNumber: index + 1,
|
|
501
|
+
lineContent: line.trim(),
|
|
502
|
+
severity: adjustedSeverity,
|
|
503
|
+
category: 'hardcoded_secret',
|
|
504
|
+
title: secretPattern.name,
|
|
505
|
+
description: adjustedDescription,
|
|
506
|
+
suggestedFix: 'Move this secret to an environment variable. Never commit secrets to version control.',
|
|
507
|
+
confidence: adjustedConfidence,
|
|
508
|
+
layer: 1,
|
|
509
|
+
requiresAIValidation: finalRequiresAIValidation,
|
|
510
|
+
})
|
|
511
|
+
}
|
|
512
|
+
})
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return vulnerabilities
|
|
516
|
+
}
|