@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,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 1: URL Pattern Matching
|
|
3
|
+
* Detects hardcoded sensitive URLs that may indicate security issues
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Vulnerability } from '../types'
|
|
7
|
+
|
|
8
|
+
// Check if file is documentation/README/example
|
|
9
|
+
function isDocumentationFile(filePath: string): boolean {
|
|
10
|
+
const docPatterns = [
|
|
11
|
+
/README/i,
|
|
12
|
+
/CHANGELOG/i,
|
|
13
|
+
/CONTRIBUTING/i,
|
|
14
|
+
/LICENSE/i,
|
|
15
|
+
/\.md$/i,
|
|
16
|
+
/\.mdx$/i,
|
|
17
|
+
/\.rst$/i,
|
|
18
|
+
/\.adoc$/i,
|
|
19
|
+
/\/docs\//i,
|
|
20
|
+
/\/documentation\//i,
|
|
21
|
+
/\/wiki\//i,
|
|
22
|
+
/\/guides?\//i,
|
|
23
|
+
/\/tutorials?\//i,
|
|
24
|
+
/\/examples?\//i,
|
|
25
|
+
]
|
|
26
|
+
return docPatterns.some(p => p.test(filePath))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check if file is a config/constants file where localhost is expected
|
|
30
|
+
function isDevConfigFile(filePath: string): boolean {
|
|
31
|
+
const devConfigPatterns = [
|
|
32
|
+
/\.env\.local$/i,
|
|
33
|
+
/\.env\.development$/i,
|
|
34
|
+
/\.env\.dev$/i,
|
|
35
|
+
/\.env\.example$/i,
|
|
36
|
+
/\.env\.sample$/i,
|
|
37
|
+
/config\.dev\./i,
|
|
38
|
+
/config\.development\./i,
|
|
39
|
+
/config\.local\./i,
|
|
40
|
+
/\/config\/development\//i,
|
|
41
|
+
/\/config\/local\//i,
|
|
42
|
+
/constants\.dev\./i,
|
|
43
|
+
]
|
|
44
|
+
return devConfigPatterns.some(p => p.test(filePath))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// URL patterns that may indicate security issues
|
|
48
|
+
const URL_PATTERNS = [
|
|
49
|
+
// Internal/staging endpoints in production code
|
|
50
|
+
{
|
|
51
|
+
pattern: /https?:\/\/localhost[:\d]*/gi,
|
|
52
|
+
name: 'Localhost URL',
|
|
53
|
+
severity: 'medium' as const,
|
|
54
|
+
description: 'Hardcoded localhost URL found - may cause issues in production',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
pattern: /https?:\/\/127\.0\.0\.1[:\d]*/gi,
|
|
58
|
+
name: 'Loopback URL',
|
|
59
|
+
severity: 'medium' as const,
|
|
60
|
+
description: 'Hardcoded loopback IP address found',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
pattern: /https?:\/\/[^\/]*staging[^\/]*\.[a-z]+/gi,
|
|
64
|
+
name: 'Staging URL',
|
|
65
|
+
severity: 'medium' as const,
|
|
66
|
+
description: 'Hardcoded staging environment URL found',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
pattern: /https?:\/\/[^\/]*\bdev\b[^\/]*\.[a-z]+/gi,
|
|
70
|
+
name: 'Development URL',
|
|
71
|
+
severity: 'low' as const,
|
|
72
|
+
description: 'Hardcoded development environment URL found',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
pattern: /https?:\/\/[^\/]*internal[^\/]*\.[a-z]+/gi,
|
|
76
|
+
name: 'Internal URL',
|
|
77
|
+
severity: 'high' as const,
|
|
78
|
+
description: 'Hardcoded internal URL found - may expose internal infrastructure',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
pattern: /https?:\/\/[^\/]*\btest\b[^\/]*\.[a-z]+/gi,
|
|
82
|
+
name: 'Test Environment URL',
|
|
83
|
+
severity: 'low' as const,
|
|
84
|
+
description: 'Hardcoded test environment URL found',
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
// Admin/sensitive endpoints - downgraded to info (these are often intentional)
|
|
88
|
+
{
|
|
89
|
+
pattern: /['"`]\/admin(?:\/|['"`])/gi,
|
|
90
|
+
name: 'Admin Endpoint',
|
|
91
|
+
severity: 'info' as const,
|
|
92
|
+
description: 'Admin endpoint path found - verify access control',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
pattern: /['"`]\/api\/admin/gi,
|
|
96
|
+
name: 'Admin API Endpoint',
|
|
97
|
+
severity: 'low' as const, // Downgraded from medium
|
|
98
|
+
description: 'Admin API endpoint found - verify access control',
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// API keys in URLs
|
|
102
|
+
{
|
|
103
|
+
pattern: /\?api[_-]?key=[a-zA-Z0-9_-]{10,}/gi,
|
|
104
|
+
name: 'API Key in URL Query',
|
|
105
|
+
severity: 'high' as const,
|
|
106
|
+
description: 'API key exposed in URL query parameter',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
pattern: /&api[_-]?key=[a-zA-Z0-9_-]{10,}/gi,
|
|
110
|
+
name: 'API Key in URL Query',
|
|
111
|
+
severity: 'high' as const,
|
|
112
|
+
description: 'API key exposed in URL query parameter',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
pattern: /\?token=[a-zA-Z0-9_-]{10,}/gi,
|
|
116
|
+
name: 'Token in URL Query',
|
|
117
|
+
severity: 'high' as const,
|
|
118
|
+
description: 'Token exposed in URL query parameter',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
pattern: /\?secret=[a-zA-Z0-9_-]{10,}/gi,
|
|
122
|
+
name: 'Secret in URL Query',
|
|
123
|
+
severity: 'critical' as const,
|
|
124
|
+
description: 'Secret exposed in URL query parameter',
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
// Webhook URLs (may contain secrets)
|
|
128
|
+
{
|
|
129
|
+
pattern: /https:\/\/hooks\.slack\.com\/services\/[a-zA-Z0-9\/]+/gi,
|
|
130
|
+
name: 'Slack Webhook URL',
|
|
131
|
+
severity: 'high' as const,
|
|
132
|
+
description: 'Slack webhook URL found - should be stored as environment variable',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
pattern: /https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[a-zA-Z0-9_-]+/gi,
|
|
136
|
+
name: 'Discord Webhook URL',
|
|
137
|
+
severity: 'high' as const,
|
|
138
|
+
description: 'Discord webhook URL found - should be stored as environment variable',
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
// Debug/test endpoints - downgraded (often in test files or intentional)
|
|
142
|
+
{
|
|
143
|
+
pattern: /['"`]\/debug(?:\/|['"`])/gi,
|
|
144
|
+
name: 'Debug Endpoint',
|
|
145
|
+
severity: 'low' as const, // Downgraded from medium
|
|
146
|
+
description: 'Debug endpoint found - verify not accessible in production',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
pattern: /['"`]\/test(?:\/|['"`])/gi,
|
|
150
|
+
name: 'Test Endpoint',
|
|
151
|
+
severity: 'info' as const, // Downgraded from low
|
|
152
|
+
description: 'Test endpoint found - typically safe in test context',
|
|
153
|
+
},
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
// Check if line is a comment
|
|
157
|
+
function isComment(lineContent: string): boolean {
|
|
158
|
+
const trimmed = lineContent.trim()
|
|
159
|
+
return (
|
|
160
|
+
trimmed.startsWith('//') ||
|
|
161
|
+
trimmed.startsWith('#') ||
|
|
162
|
+
trimmed.startsWith('*') ||
|
|
163
|
+
trimmed.startsWith('/*')
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check if it's in a test file
|
|
168
|
+
function isTestFile(filePath: string): boolean {
|
|
169
|
+
return /\.(test|spec)\.(ts|tsx|js|jsx)$/i.test(filePath) ||
|
|
170
|
+
/\/__tests__\//i.test(filePath) ||
|
|
171
|
+
/\/test\//i.test(filePath)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check if URL is in an environment variable reference
|
|
175
|
+
function isEnvVarReference(lineContent: string): boolean {
|
|
176
|
+
return lineContent.includes('process.env') ||
|
|
177
|
+
lineContent.includes('${') ||
|
|
178
|
+
lineContent.includes('import.meta.env')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Get URL context for smarter detection
|
|
182
|
+
function getURLContext(lineContent: string, filePath: string): {
|
|
183
|
+
isDevOnly: boolean
|
|
184
|
+
isConfigFile: boolean
|
|
185
|
+
isTestFile: boolean
|
|
186
|
+
isEnvRef: boolean
|
|
187
|
+
} {
|
|
188
|
+
return {
|
|
189
|
+
isDevOnly: /['"]?BASE_URL['"]?|['"]?API_URL['"]?|['"]?NEXT_PUBLIC_/.test(lineContent),
|
|
190
|
+
isConfigFile: /config|settings|constants/.test(filePath.toLowerCase()),
|
|
191
|
+
isTestFile: isTestFile(filePath),
|
|
192
|
+
isEnvRef: isEnvVarReference(lineContent)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Aggregate repeated localhost findings per file
|
|
197
|
+
export function aggregateLocalhostFindings(
|
|
198
|
+
vulnerabilities: Vulnerability[]
|
|
199
|
+
): Vulnerability[] {
|
|
200
|
+
const localhostByFile = new Map<string, {
|
|
201
|
+
lines: number[]
|
|
202
|
+
urls: string[]
|
|
203
|
+
original: Vulnerability
|
|
204
|
+
}>()
|
|
205
|
+
|
|
206
|
+
const result: Vulnerability[] = []
|
|
207
|
+
|
|
208
|
+
for (const vuln of vulnerabilities) {
|
|
209
|
+
// Check if this is a localhost/127.0.0.1 URL
|
|
210
|
+
if (vuln.category === 'sensitive_url' &&
|
|
211
|
+
/localhost|127\.0\.0\.1/i.test(vuln.lineContent)) {
|
|
212
|
+
|
|
213
|
+
const key = vuln.filePath
|
|
214
|
+
if (!localhostByFile.has(key)) {
|
|
215
|
+
localhostByFile.set(key, { lines: [], urls: [], original: vuln })
|
|
216
|
+
}
|
|
217
|
+
const entry = localhostByFile.get(key)!
|
|
218
|
+
entry.lines.push(vuln.lineNumber)
|
|
219
|
+
entry.urls.push(vuln.lineContent)
|
|
220
|
+
} else {
|
|
221
|
+
result.push(vuln)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Create aggregated findings for localhost URLs
|
|
226
|
+
for (const [filePath, data] of localhostByFile) {
|
|
227
|
+
const aggregated: Vulnerability = {
|
|
228
|
+
...data.original,
|
|
229
|
+
title: `Localhost URLs in development code (${data.lines.length} instances)`,
|
|
230
|
+
description: `Found ${data.lines.length} localhost references on lines ${data.lines.join(', ')}. ` +
|
|
231
|
+
`This is typically safe in development but should use environment variables.`,
|
|
232
|
+
lineNumber: data.lines[0],
|
|
233
|
+
severity: 'info', // Downgraded to info
|
|
234
|
+
}
|
|
235
|
+
result.push(aggregated)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return result
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function detectSensitiveURLs(
|
|
242
|
+
content: string,
|
|
243
|
+
filePath: string
|
|
244
|
+
): Vulnerability[] {
|
|
245
|
+
const vulnerabilities: Vulnerability[] = []
|
|
246
|
+
|
|
247
|
+
// Skip documentation files entirely - they often contain example URLs
|
|
248
|
+
if (isDocumentationFile(filePath)) {
|
|
249
|
+
return vulnerabilities
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const lines = content.split('\n')
|
|
253
|
+
const inTestFile = isTestFile(filePath)
|
|
254
|
+
const inDevConfig = isDevConfigFile(filePath)
|
|
255
|
+
|
|
256
|
+
for (let i = 0; i < lines.length; i++) {
|
|
257
|
+
const line = lines[i]
|
|
258
|
+
|
|
259
|
+
// Skip comments
|
|
260
|
+
if (isComment(line)) continue
|
|
261
|
+
|
|
262
|
+
// Get context for this line
|
|
263
|
+
const context = getURLContext(line, filePath)
|
|
264
|
+
|
|
265
|
+
// Skip environment variable references entirely
|
|
266
|
+
if (context.isEnvRef) continue
|
|
267
|
+
|
|
268
|
+
for (const { pattern, name, severity, description } of URL_PATTERNS) {
|
|
269
|
+
// Reset regex state
|
|
270
|
+
const regex = new RegExp(pattern.source, pattern.flags)
|
|
271
|
+
|
|
272
|
+
const match = regex.exec(line)
|
|
273
|
+
if (match) {
|
|
274
|
+
const url = match[0]
|
|
275
|
+
|
|
276
|
+
// Special handling for localhost URLs
|
|
277
|
+
if (/localhost|127\.0\.0\.1/i.test(url)) {
|
|
278
|
+
// Skip localhost in test files, dev-only contexts, or dev config files
|
|
279
|
+
if (context.isTestFile || context.isDevOnly || inDevConfig) {
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Only flag localhost in production env files as high, otherwise info
|
|
284
|
+
const isProduction = filePath.includes('.env.production')
|
|
285
|
+
const adjustedSeverity = isProduction ? 'high' as const : 'info' as const
|
|
286
|
+
|
|
287
|
+
vulnerabilities.push({
|
|
288
|
+
id: `url-${filePath}-${i + 1}-${name}`,
|
|
289
|
+
filePath,
|
|
290
|
+
lineNumber: i + 1,
|
|
291
|
+
lineContent: line.trim(),
|
|
292
|
+
severity: adjustedSeverity,
|
|
293
|
+
category: 'sensitive_url',
|
|
294
|
+
title: name,
|
|
295
|
+
description: description + (isProduction ? ' (in production config!)' : ' (in dev/config file)'),
|
|
296
|
+
suggestedFix: 'Move URLs to environment variables or configuration files. Use process.env.API_URL pattern.',
|
|
297
|
+
confidence: isProduction ? 'high' : 'low',
|
|
298
|
+
layer: 1,
|
|
299
|
+
})
|
|
300
|
+
} else {
|
|
301
|
+
// Normal URL handling (non-localhost)
|
|
302
|
+
// Lower severity for test files - downgrade more aggressively
|
|
303
|
+
let adjustedSeverity = severity
|
|
304
|
+
if (inTestFile) {
|
|
305
|
+
if (severity === 'critical') adjustedSeverity = 'high'
|
|
306
|
+
else if (severity === 'high') adjustedSeverity = 'low'
|
|
307
|
+
else adjustedSeverity = 'info'
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Non-critical URL findings require AI validation
|
|
311
|
+
const requiresAIValidation = severity !== 'critical'
|
|
312
|
+
|
|
313
|
+
vulnerabilities.push({
|
|
314
|
+
id: `url-${filePath}-${i + 1}-${name}`,
|
|
315
|
+
filePath,
|
|
316
|
+
lineNumber: i + 1,
|
|
317
|
+
lineContent: line.trim(),
|
|
318
|
+
severity: adjustedSeverity,
|
|
319
|
+
category: 'sensitive_url',
|
|
320
|
+
title: name,
|
|
321
|
+
description: description + (inTestFile ? ' (in test file)' : ''),
|
|
322
|
+
suggestedFix: 'Move URLs to environment variables or configuration files. Use process.env.API_URL pattern.',
|
|
323
|
+
confidence: inTestFile ? 'low' : 'medium',
|
|
324
|
+
layer: 1,
|
|
325
|
+
requiresAIValidation,
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
break // Only report one URL issue per line
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return vulnerabilities
|
|
334
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 1: Weak Cryptography Detection
|
|
3
|
+
* Detects usage of deprecated or weak cryptographic algorithms
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Vulnerability } from '../types'
|
|
7
|
+
|
|
8
|
+
// Weak/deprecated cryptographic patterns
|
|
9
|
+
const WEAK_CRYPTO_PATTERNS = [
|
|
10
|
+
// Weak hash algorithms
|
|
11
|
+
{
|
|
12
|
+
pattern: /\bMD5\s*\(/gi,
|
|
13
|
+
name: 'MD5 Hash Usage',
|
|
14
|
+
severity: 'high' as const,
|
|
15
|
+
description: 'MD5 is cryptographically broken and should not be used for security purposes',
|
|
16
|
+
fix: 'Use SHA-256 or SHA-3 for hashing. For passwords, use bcrypt, scrypt, or Argon2.',
|
|
17
|
+
contextCheck: (line: string) => {
|
|
18
|
+
// Only flag as high if used in security context (passwords, tokens, etc.)
|
|
19
|
+
// Checksum/etag usage is acceptable for non-security purposes
|
|
20
|
+
const securityContexts = [/password/i, /token/i, /secret/i, /credential/i, /auth/i, /session/i, /key/i]
|
|
21
|
+
const checksumContexts = [/checksum/i, /hash.*file/i, /file.*hash/i, /etag/i, /content.*hash/i, /integrity/i, /digest/i, /fingerprint/i]
|
|
22
|
+
|
|
23
|
+
const isSecurityContext = securityContexts.some(p => p.test(line))
|
|
24
|
+
const isChecksumContext = checksumContexts.some(p => p.test(line))
|
|
25
|
+
|
|
26
|
+
// If it's clearly a checksum context, don't flag
|
|
27
|
+
if (isChecksumContext && !isSecurityContext) {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
return true
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
pattern: /createHash\s*\(\s*['"]md5['"]\s*\)/gi,
|
|
35
|
+
name: 'MD5 Hash Creation',
|
|
36
|
+
severity: 'high' as const,
|
|
37
|
+
description: 'MD5 is cryptographically broken and should not be used for security purposes',
|
|
38
|
+
fix: 'Use createHash(\'sha256\') or createHash(\'sha3-256\') instead.',
|
|
39
|
+
contextCheck: (line: string, _match: RegExpMatchArray, funcName?: string) => {
|
|
40
|
+
// Check function name and line context for checksum indicators
|
|
41
|
+
const checksumIndicators = [/checksum/i, /etag/i, /content.*hash/i, /file.*hash/i, /integrity/i, /digest/i, /fingerprint/i, /verify.*file/i]
|
|
42
|
+
const securityIndicators = [/password/i, /token/i, /secret/i, /credential/i, /auth/i, /session/i]
|
|
43
|
+
|
|
44
|
+
const lineAndFunc = funcName ? `${funcName} ${line}` : line
|
|
45
|
+
|
|
46
|
+
const isChecksum = checksumIndicators.some(p => p.test(lineAndFunc))
|
|
47
|
+
const isSecurity = securityIndicators.some(p => p.test(lineAndFunc))
|
|
48
|
+
|
|
49
|
+
// Checksum use without security context = acceptable, don't flag
|
|
50
|
+
if (isChecksum && !isSecurity) {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
return true
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
pattern: /\bSHA1\s*\(/gi,
|
|
58
|
+
name: 'SHA1 Hash Usage',
|
|
59
|
+
severity: 'medium' as const,
|
|
60
|
+
description: 'SHA1 is deprecated and vulnerable to collision attacks',
|
|
61
|
+
fix: 'Use SHA-256 or SHA-3 for hashing.',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
pattern: /createHash\s*\(\s*['"]sha1['"]\s*\)/gi,
|
|
65
|
+
name: 'SHA1 Hash Creation',
|
|
66
|
+
severity: 'medium' as const,
|
|
67
|
+
description: 'SHA1 is deprecated and vulnerable to collision attacks',
|
|
68
|
+
fix: 'Use createHash(\'sha256\') or createHash(\'sha3-256\') instead.',
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// Weak encryption algorithms
|
|
72
|
+
{
|
|
73
|
+
pattern: /\bDES\b(?!ede3|3)/gi,
|
|
74
|
+
name: 'DES Encryption',
|
|
75
|
+
severity: 'high' as const,
|
|
76
|
+
description: 'DES is obsolete and easily broken. Use AES instead.',
|
|
77
|
+
fix: 'Use AES-256-GCM for symmetric encryption.',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
pattern: /createCipher(?:iv)?\s*\(\s*['"]des['"]/gi,
|
|
81
|
+
name: 'DES Cipher Creation',
|
|
82
|
+
severity: 'high' as const,
|
|
83
|
+
description: 'DES is obsolete and easily broken',
|
|
84
|
+
fix: 'Use createCipheriv(\'aes-256-gcm\', ...) instead.',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
pattern: /\bRC4\b/gi,
|
|
88
|
+
name: 'RC4 Encryption',
|
|
89
|
+
severity: 'high' as const,
|
|
90
|
+
description: 'RC4 is broken and should never be used',
|
|
91
|
+
fix: 'Use AES-256-GCM for symmetric encryption.',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
pattern: /createCipher(?:iv)?\s*\(\s*['"]rc4['"]/gi,
|
|
95
|
+
name: 'RC4 Cipher Creation',
|
|
96
|
+
severity: 'high' as const,
|
|
97
|
+
description: 'RC4 is broken and should never be used',
|
|
98
|
+
fix: 'Use createCipheriv(\'aes-256-gcm\', ...) instead.',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
pattern: /\bBlowfish\b/gi,
|
|
102
|
+
name: 'Blowfish Encryption',
|
|
103
|
+
severity: 'medium' as const,
|
|
104
|
+
description: 'Blowfish has a small block size and is not recommended for new applications',
|
|
105
|
+
fix: 'Use AES-256-GCM for symmetric encryption.',
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// Insecure random number generation
|
|
109
|
+
{
|
|
110
|
+
pattern: /Math\.random\s*\(\s*\)/g,
|
|
111
|
+
name: 'Math.random() for Security',
|
|
112
|
+
severity: 'high' as const,
|
|
113
|
+
description: 'Math.random() is not cryptographically secure and should not be used for security purposes',
|
|
114
|
+
fix: 'Use crypto.randomBytes() or crypto.getRandomValues() for cryptographic operations.',
|
|
115
|
+
contextCheck: (line: string) => {
|
|
116
|
+
// Only flag if it looks like it's being used for security
|
|
117
|
+
const securityContexts = [
|
|
118
|
+
/token/i, /secret/i, /key/i, /password/i, /salt/i,
|
|
119
|
+
/nonce/i, /iv/i, /random.*id/i, /uuid/i, /session/i,
|
|
120
|
+
]
|
|
121
|
+
return securityContexts.some(ctx => ctx.test(line))
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// Weak key derivation
|
|
126
|
+
{
|
|
127
|
+
pattern: /pbkdf2.*iterations?\s*[=:]\s*(\d+)/gi,
|
|
128
|
+
name: 'Weak PBKDF2 Iterations',
|
|
129
|
+
severity: 'medium' as const,
|
|
130
|
+
description: 'PBKDF2 with low iteration count is vulnerable to brute force attacks',
|
|
131
|
+
fix: 'Use at least 100,000 iterations for PBKDF2, or switch to Argon2.',
|
|
132
|
+
contextCheck: (line: string, match: RegExpMatchArray) => {
|
|
133
|
+
const iterations = parseInt(match[1], 10)
|
|
134
|
+
return iterations < 10000
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// Weak bcrypt rounds
|
|
139
|
+
{
|
|
140
|
+
pattern: /bcrypt\.hash\s*\([^,]+,\s*(\d+)/gi,
|
|
141
|
+
name: 'Weak bcrypt Rounds',
|
|
142
|
+
severity: 'medium' as const,
|
|
143
|
+
description: 'bcrypt with low cost factor is vulnerable to brute force attacks',
|
|
144
|
+
fix: 'Use at least 10 rounds for bcrypt (12 recommended).',
|
|
145
|
+
contextCheck: (line: string, match: RegExpMatchArray) => {
|
|
146
|
+
const rounds = parseInt(match[1], 10)
|
|
147
|
+
return rounds < 10
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
// ECB mode (insecure)
|
|
152
|
+
{
|
|
153
|
+
pattern: /['"]aes-\d+-ecb['"]/gi,
|
|
154
|
+
name: 'AES ECB Mode',
|
|
155
|
+
severity: 'high' as const,
|
|
156
|
+
description: 'ECB mode is insecure as it does not provide semantic security',
|
|
157
|
+
fix: 'Use AES-GCM or AES-CBC with proper IV handling.',
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// Deprecated createCipher (no IV)
|
|
161
|
+
{
|
|
162
|
+
pattern: /createCipher\s*\(/g,
|
|
163
|
+
name: 'Deprecated createCipher',
|
|
164
|
+
severity: 'high' as const,
|
|
165
|
+
description: 'createCipher is deprecated and does not use an IV, making it insecure',
|
|
166
|
+
fix: 'Use createCipheriv() with a random IV instead.',
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
// Hardcoded encryption keys
|
|
170
|
+
{
|
|
171
|
+
pattern: /(?:encryption|cipher|aes)[_-]?key\s*[=:]\s*['"][a-zA-Z0-9+/=]{16,}['"]/gi,
|
|
172
|
+
name: 'Hardcoded Encryption Key',
|
|
173
|
+
severity: 'critical' as const,
|
|
174
|
+
description: 'Encryption key is hardcoded in source code',
|
|
175
|
+
fix: 'Store encryption keys in environment variables or a secure key management system.',
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
// Hardcoded IVs
|
|
179
|
+
{
|
|
180
|
+
pattern: /\biv\s*[=:]\s*['"][a-zA-Z0-9+/=]{16,}['"]/gi,
|
|
181
|
+
name: 'Hardcoded IV',
|
|
182
|
+
severity: 'high' as const,
|
|
183
|
+
description: 'Initialization vector (IV) should be random for each encryption operation',
|
|
184
|
+
fix: 'Generate a random IV using crypto.randomBytes() for each encryption.',
|
|
185
|
+
},
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
// Check if line is a comment
|
|
189
|
+
function isComment(lineContent: string): boolean {
|
|
190
|
+
const trimmed = lineContent.trim()
|
|
191
|
+
return (
|
|
192
|
+
trimmed.startsWith('//') ||
|
|
193
|
+
trimmed.startsWith('#') ||
|
|
194
|
+
trimmed.startsWith('*') ||
|
|
195
|
+
trimmed.startsWith('/*')
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Check if line is a pattern definition (regex or string literal in detector code)
|
|
200
|
+
function isPatternDefinition(lineContent: string): boolean {
|
|
201
|
+
const trimmed = lineContent.trim()
|
|
202
|
+
// Pattern definitions typically look like:
|
|
203
|
+
// pattern: /regex/
|
|
204
|
+
// name: 'string'
|
|
205
|
+
// description: 'string'
|
|
206
|
+
// fix: 'string'
|
|
207
|
+
return (
|
|
208
|
+
trimmed.startsWith('pattern:') ||
|
|
209
|
+
trimmed.startsWith('name:') ||
|
|
210
|
+
trimmed.startsWith('description:') ||
|
|
211
|
+
trimmed.startsWith('fix:') ||
|
|
212
|
+
// Also check for object property assignments with these names
|
|
213
|
+
/^\s*(pattern|name|description|fix|severity)\s*:/.test(lineContent)
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Check if file is part of the scanner's own detection code
|
|
218
|
+
function isScannerDetectorFile(filePath: string): boolean {
|
|
219
|
+
return (
|
|
220
|
+
filePath.includes('scanner/src/layer1/') ||
|
|
221
|
+
filePath.includes('scanner/src/layer2/') ||
|
|
222
|
+
filePath.includes('scanner/src/layer3/') ||
|
|
223
|
+
filePath.includes('/lib/scanner/layer1/') ||
|
|
224
|
+
filePath.includes('/lib/scanner/layer2/') ||
|
|
225
|
+
filePath.includes('/lib/scanner/layer3/')
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Check if file is a test file or fixture
|
|
230
|
+
function isTestOrFixtureFile(filePath: string): boolean {
|
|
231
|
+
const lowerPath = filePath.toLowerCase()
|
|
232
|
+
return (
|
|
233
|
+
lowerPath.includes('__tests__') ||
|
|
234
|
+
lowerPath.includes('__mocks__') ||
|
|
235
|
+
lowerPath.includes('/test/') ||
|
|
236
|
+
lowerPath.includes('/tests/') ||
|
|
237
|
+
lowerPath.includes('/fixtures/') ||
|
|
238
|
+
lowerPath.includes('/fixture/') ||
|
|
239
|
+
lowerPath.includes('.test.') ||
|
|
240
|
+
lowerPath.includes('.spec.') ||
|
|
241
|
+
lowerPath.includes('-test.') ||
|
|
242
|
+
lowerPath.includes('-spec.') ||
|
|
243
|
+
lowerPath.includes('benchmark')
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Find the enclosing function name for a given line
|
|
249
|
+
*/
|
|
250
|
+
function findEnclosingFunctionName(lines: string[], lineIndex: number): string | undefined {
|
|
251
|
+
// Look backwards for function declaration
|
|
252
|
+
for (let i = lineIndex; i >= 0 && i >= lineIndex - 20; i--) {
|
|
253
|
+
const line = lines[i]
|
|
254
|
+
// Match function declarations: function name(), const name = (), async function name()
|
|
255
|
+
const funcMatch = line.match(/(?:function\s+|const\s+|let\s+|var\s+)(\w+)\s*(?:=\s*(?:async\s*)?\(|=\s*(?:async\s+)?function|\()/)
|
|
256
|
+
if (funcMatch) {
|
|
257
|
+
return funcMatch[1]
|
|
258
|
+
}
|
|
259
|
+
// Match method declarations: name() { or name: function()
|
|
260
|
+
const methodMatch = line.match(/^\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/)
|
|
261
|
+
if (methodMatch) {
|
|
262
|
+
return methodMatch[1]
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return undefined
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function detectWeakCrypto(
|
|
269
|
+
content: string,
|
|
270
|
+
filePath: string
|
|
271
|
+
): Vulnerability[] {
|
|
272
|
+
const vulnerabilities: Vulnerability[] = []
|
|
273
|
+
const lines = content.split('\n')
|
|
274
|
+
|
|
275
|
+
// Skip scanner's own detector files to avoid self-detection
|
|
276
|
+
if (isScannerDetectorFile(filePath)) {
|
|
277
|
+
return vulnerabilities
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Skip test files and fixtures (intentional vulnerable code for testing)
|
|
281
|
+
if (isTestOrFixtureFile(filePath)) {
|
|
282
|
+
return vulnerabilities
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
for (let i = 0; i < lines.length; i++) {
|
|
286
|
+
const line = lines[i]
|
|
287
|
+
|
|
288
|
+
// Skip comments
|
|
289
|
+
if (isComment(line)) continue
|
|
290
|
+
|
|
291
|
+
// Skip pattern definitions (detector rule definitions)
|
|
292
|
+
if (isPatternDefinition(line)) continue
|
|
293
|
+
|
|
294
|
+
for (const cryptoPattern of WEAK_CRYPTO_PATTERNS) {
|
|
295
|
+
const { pattern, name, severity, description, fix, contextCheck } = cryptoPattern
|
|
296
|
+
|
|
297
|
+
// Reset regex state
|
|
298
|
+
const regex = new RegExp(pattern.source, pattern.flags)
|
|
299
|
+
const match = regex.exec(line)
|
|
300
|
+
|
|
301
|
+
if (match) {
|
|
302
|
+
// If there's a context check, apply it
|
|
303
|
+
// Pass function name for additional context
|
|
304
|
+
const funcName = findEnclosingFunctionName(lines, i)
|
|
305
|
+
if (contextCheck && !contextCheck(line, match, funcName)) {
|
|
306
|
+
continue
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
vulnerabilities.push({
|
|
310
|
+
id: `weak-crypto-${filePath}-${i + 1}-${name}`,
|
|
311
|
+
filePath,
|
|
312
|
+
lineNumber: i + 1,
|
|
313
|
+
lineContent: line.trim(),
|
|
314
|
+
severity,
|
|
315
|
+
category: 'weak_crypto',
|
|
316
|
+
title: name,
|
|
317
|
+
description,
|
|
318
|
+
suggestedFix: fix,
|
|
319
|
+
confidence: 'high',
|
|
320
|
+
layer: 1,
|
|
321
|
+
})
|
|
322
|
+
break // Only report one crypto issue per line
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return vulnerabilities
|
|
328
|
+
}
|