@oculum/scanner 1.0.11 → 1.0.12
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/ai-context/index.d.ts +6 -0
- package/dist/ai-context/index.d.ts.map +1 -0
- package/dist/ai-context/index.js +13 -0
- package/dist/ai-context/index.js.map +1 -0
- package/dist/ai-context/manager.d.ts +67 -0
- package/dist/ai-context/manager.d.ts.map +1 -0
- package/dist/ai-context/manager.js +104 -0
- package/dist/ai-context/manager.js.map +1 -0
- package/dist/category-filter.d.ts +125 -0
- package/dist/category-filter.d.ts.map +1 -0
- package/dist/category-filter.js +360 -0
- package/dist/category-filter.js.map +1 -0
- package/dist/filtering/context-adjustments.d.ts +23 -0
- package/dist/filtering/context-adjustments.d.ts.map +1 -0
- package/dist/filtering/context-adjustments.js +100 -0
- package/dist/filtering/context-adjustments.js.map +1 -0
- package/dist/filtering/index.d.ts +3 -0
- package/dist/filtering/index.d.ts.map +1 -0
- package/dist/filtering/index.js +8 -0
- package/dist/filtering/index.js.map +1 -0
- package/dist/filtering/pipeline.d.ts +48 -0
- package/dist/filtering/pipeline.d.ts.map +1 -0
- package/dist/filtering/pipeline.js +76 -0
- package/dist/filtering/pipeline.js.map +1 -0
- package/dist/formatters/ai-context.d.ts +23 -0
- package/dist/formatters/ai-context.d.ts.map +1 -0
- package/dist/formatters/ai-context.js +238 -0
- package/dist/formatters/ai-context.js.map +1 -0
- package/dist/formatters/github-comment.d.ts +1 -1
- package/dist/formatters/github-comment.d.ts.map +1 -1
- package/dist/formatters/github-comment.js +2 -2
- package/dist/formatters/github-comment.js.map +1 -1
- package/dist/formatters/ide/claude-code.d.ts +17 -0
- package/dist/formatters/ide/claude-code.d.ts.map +1 -0
- package/dist/formatters/ide/claude-code.js +94 -0
- package/dist/formatters/ide/claude-code.js.map +1 -0
- package/dist/formatters/ide/cursor.d.ts +13 -0
- package/dist/formatters/ide/cursor.d.ts.map +1 -0
- package/dist/formatters/ide/cursor.js +125 -0
- package/dist/formatters/ide/cursor.js.map +1 -0
- package/dist/formatters/ide/index.d.ts +62 -0
- package/dist/formatters/ide/index.d.ts.map +1 -0
- package/dist/formatters/ide/index.js +184 -0
- package/dist/formatters/ide/index.js.map +1 -0
- package/dist/formatters/ide/windsurf.d.ts +13 -0
- package/dist/formatters/ide/windsurf.d.ts.map +1 -0
- package/dist/formatters/ide/windsurf.js +117 -0
- package/dist/formatters/ide/windsurf.js.map +1 -0
- package/dist/formatters/index.d.ts +2 -0
- package/dist/formatters/index.d.ts.map +1 -1
- package/dist/formatters/index.js +17 -1
- package/dist/formatters/index.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +272 -44
- package/dist/index.js.map +1 -1
- package/dist/layer1/comments.d.ts +4 -1
- package/dist/layer1/comments.d.ts.map +1 -1
- package/dist/layer1/comments.js +1 -1
- package/dist/layer1/comments.js.map +1 -1
- package/dist/layer1/config-audit.d.ts +4 -1
- package/dist/layer1/config-audit.d.ts.map +1 -1
- package/dist/layer1/config-audit.js +45 -11
- package/dist/layer1/config-audit.js.map +1 -1
- package/dist/layer1/config-mcp-audit.d.ts +4 -1
- package/dist/layer1/config-mcp-audit.d.ts.map +1 -1
- package/dist/layer1/config-mcp-audit.js +2 -2
- package/dist/layer1/config-mcp-audit.js.map +1 -1
- package/dist/layer1/entropy.d.ts +4 -1
- package/dist/layer1/entropy.d.ts.map +1 -1
- package/dist/layer1/entropy.js +212 -1
- package/dist/layer1/entropy.js.map +1 -1
- package/dist/layer1/file-flags.d.ts +4 -1
- package/dist/layer1/file-flags.d.ts.map +1 -1
- package/dist/layer1/file-flags.js +12 -5
- package/dist/layer1/file-flags.js.map +1 -1
- package/dist/layer1/index.d.ts.map +1 -1
- package/dist/layer1/index.js +14 -19
- package/dist/layer1/index.js.map +1 -1
- package/dist/layer1/patterns.d.ts +4 -1
- package/dist/layer1/patterns.d.ts.map +1 -1
- package/dist/layer1/patterns.js +34 -4
- package/dist/layer1/patterns.js.map +1 -1
- package/dist/layer1/urls.d.ts +4 -1
- package/dist/layer1/urls.d.ts.map +1 -1
- package/dist/layer1/urls.js +162 -14
- package/dist/layer1/urls.js.map +1 -1
- package/dist/layer1/weak-crypto.d.ts +4 -1
- package/dist/layer1/weak-crypto.d.ts.map +1 -1
- package/dist/layer1/weak-crypto.js +144 -7
- package/dist/layer1/weak-crypto.js.map +1 -1
- package/dist/layer2/ai-agent-tools.d.ts +4 -1
- package/dist/layer2/ai-agent-tools.d.ts.map +1 -1
- package/dist/layer2/ai-agent-tools.js +661 -2
- package/dist/layer2/ai-agent-tools.js.map +1 -1
- package/dist/layer2/ai-endpoint-protection.d.ts +2 -0
- package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -1
- package/dist/layer2/ai-endpoint-protection.js +1 -1
- package/dist/layer2/ai-endpoint-protection.js.map +1 -1
- package/dist/layer2/ai-execution-sinks.d.ts +4 -1
- package/dist/layer2/ai-execution-sinks.d.ts.map +1 -1
- package/dist/layer2/ai-execution-sinks.js +252 -43
- package/dist/layer2/ai-execution-sinks.js.map +1 -1
- package/dist/layer2/ai-fingerprinting.d.ts +4 -1
- package/dist/layer2/ai-fingerprinting.d.ts.map +1 -1
- package/dist/layer2/ai-fingerprinting.js +25 -32
- package/dist/layer2/ai-fingerprinting.js.map +1 -1
- package/dist/layer2/ai-mcp-security.d.ts +4 -1
- package/dist/layer2/ai-mcp-security.d.ts.map +1 -1
- package/dist/layer2/ai-mcp-security.js +200 -2
- package/dist/layer2/ai-mcp-security.js.map +1 -1
- package/dist/layer2/ai-package-hallucination.d.ts +4 -1
- package/dist/layer2/ai-package-hallucination.d.ts.map +1 -1
- package/dist/layer2/ai-package-hallucination.js +136 -4
- package/dist/layer2/ai-package-hallucination.js.map +1 -1
- package/dist/layer2/ai-prompt-hygiene.d.ts +4 -1
- package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -1
- package/dist/layer2/ai-prompt-hygiene.js +342 -28
- package/dist/layer2/ai-prompt-hygiene.js.map +1 -1
- package/dist/layer2/ai-rag-safety.d.ts +4 -1
- package/dist/layer2/ai-rag-safety.d.ts.map +1 -1
- package/dist/layer2/ai-rag-safety.js +82 -2
- package/dist/layer2/ai-rag-safety.js.map +1 -1
- package/dist/layer2/ai-schema-validation.d.ts +4 -1
- package/dist/layer2/ai-schema-validation.d.ts.map +1 -1
- package/dist/layer2/ai-schema-validation.js +2 -2
- package/dist/layer2/ai-schema-validation.js.map +1 -1
- package/dist/layer2/auth-antipatterns.d.ts +2 -0
- package/dist/layer2/auth-antipatterns.d.ts.map +1 -1
- package/dist/layer2/auth-antipatterns.js +205 -20
- package/dist/layer2/auth-antipatterns.js.map +1 -1
- package/dist/layer2/byok-patterns.d.ts +4 -1
- package/dist/layer2/byok-patterns.d.ts.map +1 -1
- package/dist/layer2/byok-patterns.js +2 -2
- package/dist/layer2/byok-patterns.js.map +1 -1
- package/dist/layer2/dangerous-functions/dom-xss.d.ts +9 -4
- package/dist/layer2/dangerous-functions/dom-xss.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/dom-xss.js +73 -22
- package/dist/layer2/dangerous-functions/dom-xss.js.map +1 -1
- package/dist/layer2/dangerous-functions/index.d.ts +4 -1
- package/dist/layer2/dangerous-functions/index.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/index.js +551 -20
- package/dist/layer2/dangerous-functions/index.js.map +1 -1
- package/dist/layer2/dangerous-functions/math-random.d.ts +54 -4
- package/dist/layer2/dangerous-functions/math-random.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/math-random.js +241 -16
- package/dist/layer2/dangerous-functions/math-random.js.map +1 -1
- package/dist/layer2/dangerous-functions/patterns.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/patterns.js +3 -1
- package/dist/layer2/dangerous-functions/patterns.js.map +1 -1
- package/dist/layer2/dangerous-functions/utils/control-flow.d.ts +3 -2
- package/dist/layer2/dangerous-functions/utils/control-flow.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/utils/control-flow.js +41 -120
- package/dist/layer2/dangerous-functions/utils/control-flow.js.map +1 -1
- package/dist/layer2/dangerous-functions/utils/helpers.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/utils/helpers.js +26 -3
- package/dist/layer2/dangerous-functions/utils/helpers.js.map +1 -1
- package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/utils/schema-validation.js +14 -1
- package/dist/layer2/dangerous-functions/utils/schema-validation.js.map +1 -1
- package/dist/layer2/data-exposure.d.ts +4 -1
- package/dist/layer2/data-exposure.d.ts.map +1 -1
- package/dist/layer2/data-exposure.js +11 -38
- package/dist/layer2/data-exposure.js.map +1 -1
- package/dist/layer2/framework-checks.d.ts +4 -1
- package/dist/layer2/framework-checks.d.ts.map +1 -1
- package/dist/layer2/framework-checks.js +2 -2
- package/dist/layer2/framework-checks.js.map +1 -1
- package/dist/layer2/index.d.ts +9 -1
- package/dist/layer2/index.d.ts.map +1 -1
- package/dist/layer2/index.js +57 -51
- package/dist/layer2/index.js.map +1 -1
- package/dist/layer2/logic-gates.d.ts +4 -1
- package/dist/layer2/logic-gates.d.ts.map +1 -1
- package/dist/layer2/logic-gates.js +54 -20
- package/dist/layer2/logic-gates.js.map +1 -1
- package/dist/layer2/model-supply-chain.d.ts +4 -1
- package/dist/layer2/model-supply-chain.d.ts.map +1 -1
- package/dist/layer2/model-supply-chain.js +72 -4
- package/dist/layer2/model-supply-chain.js.map +1 -1
- package/dist/layer2/risky-imports.d.ts +4 -1
- package/dist/layer2/risky-imports.d.ts.map +1 -1
- package/dist/layer2/risky-imports.js +2 -2
- package/dist/layer2/risky-imports.js.map +1 -1
- package/dist/layer2/variables.d.ts +4 -1
- package/dist/layer2/variables.d.ts.map +1 -1
- package/dist/layer2/variables.js +2 -2
- package/dist/layer2/variables.js.map +1 -1
- package/dist/layer3/anthropic/auto-dismiss.d.ts.map +1 -1
- package/dist/layer3/anthropic/auto-dismiss.js +11 -0
- package/dist/layer3/anthropic/auto-dismiss.js.map +1 -1
- package/dist/modes/incremental.js +1 -1
- package/dist/tiers.d.ts +2 -2
- package/dist/tiers.d.ts.map +1 -1
- package/dist/tiers.js +7 -7
- package/dist/tiers.js.map +1 -1
- package/dist/types.d.ts +78 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +34 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/code-analysis.d.ts +39 -0
- package/dist/utils/code-analysis.d.ts.map +1 -0
- package/dist/utils/code-analysis.js +159 -0
- package/dist/utils/code-analysis.js.map +1 -0
- package/dist/utils/comment-analyzer.d.ts +38 -0
- package/dist/utils/comment-analyzer.d.ts.map +1 -0
- package/dist/utils/comment-analyzer.js +218 -0
- package/dist/utils/comment-analyzer.js.map +1 -0
- package/dist/utils/context-helpers.d.ts +108 -1
- package/dist/utils/context-helpers.d.ts.map +1 -1
- package/dist/utils/context-helpers.js +351 -2
- package/dist/utils/context-helpers.js.map +1 -1
- package/dist/utils/environment-context.d.ts +76 -0
- package/dist/utils/environment-context.d.ts.map +1 -0
- package/dist/utils/environment-context.js +271 -0
- package/dist/utils/environment-context.js.map +1 -0
- package/dist/utils/intent-detector.d.ts +66 -0
- package/dist/utils/intent-detector.d.ts.map +1 -0
- package/dist/utils/intent-detector.js +282 -0
- package/dist/utils/intent-detector.js.map +1 -0
- package/dist/utils/parsed-file.d.ts +51 -0
- package/dist/utils/parsed-file.d.ts.map +1 -0
- package/dist/utils/parsed-file.js +95 -0
- package/dist/utils/parsed-file.js.map +1 -0
- package/dist/utils/route-hierarchy.d.ts +50 -0
- package/dist/utils/route-hierarchy.d.ts.map +1 -0
- package/dist/utils/route-hierarchy.js +226 -0
- package/dist/utils/route-hierarchy.js.map +1 -0
- package/dist/utils/schema-semantics.d.ts +45 -0
- package/dist/utils/schema-semantics.d.ts.map +1 -0
- package/dist/utils/schema-semantics.js +193 -0
- package/dist/utils/schema-semantics.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/benchmark/fixtures/layer2/index.ts +12 -0
- package/src/__tests__/benchmark/fixtures/layer2/phase5-excessive-agency.ts +580 -0
- package/src/__tests__/benchmark/fixtures/layer2/sprint6-ai-enhancements.ts +515 -0
- package/src/__tests__/benchmark/run-depth-validation.ts +9 -9
- package/src/__tests__/category-filter.test.ts +478 -0
- package/src/__tests__/regression/known-false-positives.test.ts +490 -0
- package/src/__tests__/snapshots/__snapshots__/anthropic-validation-refactor.test.ts.snap +18 -14
- package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +0 -9
- package/src/__tests__/snapshots/anthropic-validation-refactor.test.ts +1 -1
- package/src/__tests__/validation/run-validation.ts +7 -7
- package/src/ai-context/__tests__/manager.test.ts +193 -0
- package/src/ai-context/index.ts +15 -0
- package/src/ai-context/manager.ts +145 -0
- package/src/baseline/__tests__/manager.test.ts +2 -2
- package/src/category-filter.ts +400 -0
- package/src/filtering/__tests__/pipeline.test.ts +134 -0
- package/src/filtering/context-adjustments.ts +111 -0
- package/src/filtering/index.ts +10 -0
- package/src/filtering/pipeline.ts +130 -0
- package/src/formatters/__tests__/ai-context.test.ts +254 -0
- package/src/formatters/ai-context.ts +302 -0
- package/src/formatters/github-comment.ts +3 -3
- package/src/formatters/ide/__tests__/ide.test.ts +319 -0
- package/src/formatters/ide/claude-code.ts +110 -0
- package/src/formatters/ide/cursor.ts +147 -0
- package/src/formatters/ide/index.ts +216 -0
- package/src/formatters/ide/windsurf.ts +135 -0
- package/src/formatters/index.ts +24 -0
- package/src/index.ts +312 -34
- package/src/layer1/comments.ts +3 -1
- package/src/layer1/config-audit.ts +50 -11
- package/src/layer1/config-mcp-audit.ts +4 -2
- package/src/layer1/entropy.ts +234 -1
- package/src/layer1/file-flags.ts +17 -6
- package/src/layer1/index.ts +14 -18
- package/src/layer1/patterns.ts +42 -4
- package/src/layer1/urls.ts +188 -14
- package/src/layer1/weak-crypto.ts +168 -16
- package/src/layer2/ai-agent-tools.ts +707 -2
- package/src/layer2/ai-endpoint-protection.ts +3 -1
- package/src/layer2/ai-execution-sinks.ts +265 -43
- package/src/layer2/ai-fingerprinting.ts +28 -32
- package/src/layer2/ai-mcp-security.ts +206 -3
- package/src/layer2/ai-package-hallucination.ts +153 -4
- package/src/layer2/ai-prompt-hygiene.ts +369 -26
- package/src/layer2/ai-rag-safety.ts +85 -2
- package/src/layer2/ai-schema-validation.ts +4 -2
- package/src/layer2/auth-antipatterns.ts +230 -20
- package/src/layer2/byok-patterns.ts +4 -2
- package/src/layer2/dangerous-functions/dom-xss.ts +94 -22
- package/src/layer2/dangerous-functions/index.ts +635 -51
- package/src/layer2/dangerous-functions/math-random.ts +268 -16
- package/src/layer2/dangerous-functions/patterns.ts +3 -1
- package/src/layer2/dangerous-functions/utils/control-flow.ts +8 -135
- package/src/layer2/dangerous-functions/utils/schema-validation.ts +16 -1
- package/src/layer2/data-exposure.ts +13 -38
- package/src/layer2/framework-checks.ts +4 -2
- package/src/layer2/index.ts +69 -50
- package/src/layer2/logic-gates.ts +59 -22
- package/src/layer2/model-supply-chain.ts +79 -4
- package/src/layer2/risky-imports.ts +4 -2
- package/src/layer2/variables.ts +4 -2
- package/src/layer3/anthropic/auto-dismiss.ts +11 -0
- package/src/modes/incremental.ts +1 -1
- package/src/tiers.ts +9 -9
- package/src/types.ts +122 -8
- package/src/utils/__tests__/code-analysis.test.ts +165 -0
- package/src/utils/__tests__/parsed-file.test.ts +124 -0
- package/src/utils/code-analysis.ts +179 -0
- package/src/utils/comment-analyzer.ts +249 -0
- package/src/utils/context-helpers.ts +408 -2
- package/src/utils/environment-context.ts +304 -0
- package/src/utils/intent-detector.ts +318 -0
- package/src/utils/parsed-file.ts +103 -0
- package/src/utils/route-hierarchy.ts +250 -0
- package/src/utils/schema-semantics.ts +233 -0
|
@@ -10,8 +10,87 @@ import {
|
|
|
10
10
|
isSeedOrDataGenFile,
|
|
11
11
|
isEducationalVulnerabilityFile,
|
|
12
12
|
} from '../../utils/context-helpers'
|
|
13
|
+
import type { ParsedFile } from '../../utils/parsed-file'
|
|
13
14
|
import { extractFunctionContext } from './utils/control-flow'
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Check if Math.random() is used in a jitter/backoff/retry context
|
|
18
|
+
* These are legitimate uses of Math.random() for network resilience,
|
|
19
|
+
* not security-sensitive randomness.
|
|
20
|
+
*
|
|
21
|
+
* Examples:
|
|
22
|
+
* const delay = baseDelay + Math.random() * jitter
|
|
23
|
+
* setTimeout(retry, delay * Math.random())
|
|
24
|
+
* await sleep(backoff * (1 + Math.random() * 0.1))
|
|
25
|
+
*
|
|
26
|
+
* @param content - Full file content
|
|
27
|
+
* @param lineNumber - The 0-indexed line number where Math.random() was found
|
|
28
|
+
*/
|
|
29
|
+
export function isJitterOrBackoffContext(
|
|
30
|
+
content: string,
|
|
31
|
+
lineNumber: number,
|
|
32
|
+
lines?: string[]
|
|
33
|
+
): boolean {
|
|
34
|
+
const _lines = lines ?? content.split('\n')
|
|
35
|
+
const start = Math.max(0, lineNumber - 10)
|
|
36
|
+
const end = Math.min(_lines.length, lineNumber + 5)
|
|
37
|
+
const context = _lines.slice(start, end).join('\n')
|
|
38
|
+
|
|
39
|
+
// Patterns indicating jitter/backoff/retry context
|
|
40
|
+
const jitterPatterns = [
|
|
41
|
+
// Direct keyword matches
|
|
42
|
+
/\b(jitter|backoff|retry|retries|exponential)\b/i,
|
|
43
|
+
// Delay/timeout with random
|
|
44
|
+
/setTimeout.*Math\.random/i,
|
|
45
|
+
/setInterval.*Math\.random/i,
|
|
46
|
+
/sleep.*Math\.random/i,
|
|
47
|
+
/await.*delay.*Math\.random/i,
|
|
48
|
+
// Common backoff patterns
|
|
49
|
+
/\* Math\.random\(\).*delay/i,
|
|
50
|
+
/delay\s*\*\s*Math\.random/i,
|
|
51
|
+
/Math\.random\(\)\s*\*\s*\d+.*delay/i,
|
|
52
|
+
// Retry-related function names
|
|
53
|
+
/function\s+(retry|withRetry|backoff|withBackoff|exponentialBackoff)/i,
|
|
54
|
+
// Common retry library patterns
|
|
55
|
+
/retryPolicy|retryConfig|retryOptions|maxRetries|retryCount/i,
|
|
56
|
+
// Network resilience patterns
|
|
57
|
+
/\b(throttle|debounce|rateLimit)\b.*Math\.random/i,
|
|
58
|
+
// Sampling/probability for non-security uses
|
|
59
|
+
/sampleRate|samplingRate|probability\s*[<>]=?\s*Math\.random/i,
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
return jitterPatterns.some(p => p.test(context))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if Math.random() is used in a React key prop context
|
|
67
|
+
* This is a common pattern to force re-renders, not a security issue.
|
|
68
|
+
*
|
|
69
|
+
* Examples:
|
|
70
|
+
* key={Math.random()}
|
|
71
|
+
* key={`prefix-${Math.random()}`}
|
|
72
|
+
* key={Math.random().toString()}
|
|
73
|
+
*
|
|
74
|
+
* @param lineContent - The line of code to check
|
|
75
|
+
* @param filePath - The file path (only check JSX/TSX files)
|
|
76
|
+
*/
|
|
77
|
+
export function isReactKeyPattern(lineContent: string, filePath: string): boolean {
|
|
78
|
+
// Only check in JSX/TSX files
|
|
79
|
+
if (!/\.[jt]sx$/.test(filePath)) {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Pattern: key={...Math.random()...}
|
|
84
|
+
// Matches:
|
|
85
|
+
// key={Math.random()}
|
|
86
|
+
// key={`foo-${Math.random()}`}
|
|
87
|
+
// key={Math.random().toString()}
|
|
88
|
+
// key={`${Math.random()}-bar`}
|
|
89
|
+
// key={something + Math.random()}
|
|
90
|
+
const keyPattern = /key\s*=\s*\{[^}]*Math\.random\(\)/
|
|
91
|
+
return keyPattern.test(lineContent)
|
|
92
|
+
}
|
|
93
|
+
|
|
15
94
|
/**
|
|
16
95
|
* Check if Math.random() is used for cosmetic/UI purposes (not security)
|
|
17
96
|
* Cosmetic uses: CSS values, animations, UI variations, demo data
|
|
@@ -20,9 +99,10 @@ import { extractFunctionContext } from './utils/control-flow'
|
|
|
20
99
|
export function isCosmeticMathRandom(
|
|
21
100
|
lineContent: string,
|
|
22
101
|
content: string,
|
|
23
|
-
lineNumber: number
|
|
102
|
+
lineNumber: number,
|
|
103
|
+
lines?: string[]
|
|
24
104
|
): boolean {
|
|
25
|
-
const
|
|
105
|
+
const _lines = lines ?? content.split('\n')
|
|
26
106
|
|
|
27
107
|
// Check the line itself for cosmetic indicators
|
|
28
108
|
const cosmeticLinePatterns = [
|
|
@@ -61,8 +141,8 @@ export function isCosmeticMathRandom(
|
|
|
61
141
|
|
|
62
142
|
// Check surrounding context (5 lines before and after)
|
|
63
143
|
const contextStart = Math.max(0, lineNumber - 5)
|
|
64
|
-
const contextEnd = Math.min(
|
|
65
|
-
const context =
|
|
144
|
+
const contextEnd = Math.min(_lines.length, lineNumber + 5)
|
|
145
|
+
const context = _lines.slice(contextStart, contextEnd).join('\n')
|
|
66
146
|
|
|
67
147
|
// Context indicators of cosmetic use
|
|
68
148
|
const cosmeticContextPatterns = [
|
|
@@ -402,7 +482,8 @@ export function classifyVariableNameRisk(
|
|
|
402
482
|
export function analyzeMathRandomContext(
|
|
403
483
|
content: string,
|
|
404
484
|
filePath: string,
|
|
405
|
-
lineNumber: number
|
|
485
|
+
lineNumber: number,
|
|
486
|
+
lines?: string[]
|
|
406
487
|
): {
|
|
407
488
|
inSecurityContext: boolean
|
|
408
489
|
inTestContext: boolean
|
|
@@ -410,10 +491,10 @@ export function analyzeMathRandomContext(
|
|
|
410
491
|
inBusinessLogicContext: boolean
|
|
411
492
|
contextDescription: string
|
|
412
493
|
} {
|
|
413
|
-
const
|
|
494
|
+
const _lines = lines ?? content.split('\n')
|
|
414
495
|
const start = Math.max(0, lineNumber - 10)
|
|
415
|
-
const end = Math.min(
|
|
416
|
-
const context =
|
|
496
|
+
const end = Math.min(_lines.length, lineNumber + 5)
|
|
497
|
+
const context = _lines.slice(start, end).join('\n')
|
|
417
498
|
|
|
418
499
|
// Security context indicators (functions, imports, comments)
|
|
419
500
|
const securityPatterns = [
|
|
@@ -438,8 +519,8 @@ export function analyzeMathRandomContext(
|
|
|
438
519
|
testContextPatterns.some(p => p.test(context))
|
|
439
520
|
|
|
440
521
|
// UI/cosmetic context (reuse existing logic)
|
|
441
|
-
const lineContent =
|
|
442
|
-
const inUIContext = isCosmeticMathRandom(lineContent, content, lineNumber)
|
|
522
|
+
const lineContent = _lines[lineNumber]
|
|
523
|
+
const inUIContext = isCosmeticMathRandom(lineContent, content, lineNumber, _lines)
|
|
443
524
|
|
|
444
525
|
// Business logic context (non-security ID generation)
|
|
445
526
|
// Note: UUID/CAPTCHA patterns excluded - handled by functionIntent classification
|
|
@@ -448,6 +529,14 @@ export function analyzeMathRandomContext(
|
|
|
448
529
|
/\b(reference|tracking|confirmation)Number\b/i,
|
|
449
530
|
/\b(backoff|retry|jitter|delay|timeout|latency)\b/i,
|
|
450
531
|
/\b(sample|sampling|probability|chance|rollout|experiment|abtest|cohort|bucket|variant)\b/i,
|
|
532
|
+
// Load balancing and selection patterns
|
|
533
|
+
/mode\s*===?\s*['"]random['"]/i, // mode === 'random'
|
|
534
|
+
/\.\w*index\s*%/i, // round-robin patterns
|
|
535
|
+
/keys?\[.*Math\.random/i, // keys[Math.floor(Math.random() * keys.length)]
|
|
536
|
+
/\[\s*Math\.floor\s*\(\s*Math\.random/i, // array[Math.floor(Math.random()...)]
|
|
537
|
+
/shuffle/i, // shuffle functions
|
|
538
|
+
/pickRandom/i, // pickRandom helpers
|
|
539
|
+
/randomElement/i, // randomElement helpers
|
|
451
540
|
]
|
|
452
541
|
const inBusinessLogicContext =
|
|
453
542
|
businessLogicPatterns.some(p => p.test(context)) && !inSecurityContext
|
|
@@ -473,29 +562,169 @@ export function analyzeMathRandomContext(
|
|
|
473
562
|
}
|
|
474
563
|
}
|
|
475
564
|
|
|
565
|
+
/**
|
|
566
|
+
* Check if file is an animation/motion component based on file name
|
|
567
|
+
* Files with animation-related names typically use Math.random for visual effects
|
|
568
|
+
*/
|
|
569
|
+
export function isAnimationFile(filePath: string): boolean {
|
|
570
|
+
const animationPatterns = [
|
|
571
|
+
/animated[-_]/i, // animated-document-scanner.tsx
|
|
572
|
+
/[-_]animation/i, // document-animation.tsx
|
|
573
|
+
/motion[-_]/i, // motion-component.tsx
|
|
574
|
+
/[-_]motion/i, // scroll-motion.tsx
|
|
575
|
+
/particles?[-_]/i, // particles-background.tsx
|
|
576
|
+
/confetti/i, // confetti.tsx
|
|
577
|
+
/[-_]effect/i, // hover-effect.tsx
|
|
578
|
+
/transition[-_]/i, // transition-wrapper.tsx
|
|
579
|
+
/visual[-_]/i, // visual-effects.tsx
|
|
580
|
+
/canvas[-_]/i, // canvas-animation.tsx
|
|
581
|
+
]
|
|
582
|
+
return animationPatterns.some(p => p.test(filePath))
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Check if Math.random() is used for animation/motion coordinates
|
|
587
|
+
* Common in animation libraries like framer-motion, react-spring, Three.js, etc.
|
|
588
|
+
*/
|
|
589
|
+
export function isAnimationCoordinateUsage(lineContent: string, context: string): boolean {
|
|
590
|
+
// Object property assignments for coordinates
|
|
591
|
+
const coordinatePatterns = [
|
|
592
|
+
/\bx:\s*Math\.random/i, // x: Math.random()
|
|
593
|
+
/\by:\s*Math\.random/i, // y: Math.random()
|
|
594
|
+
/\bz:\s*Math\.random/i, // z: Math.random()
|
|
595
|
+
/\bleft:\s*.*Math\.random/i, // left: Math.random()
|
|
596
|
+
/\btop:\s*.*Math\.random/i, // top: Math.random()
|
|
597
|
+
/\bright:\s*.*Math\.random/i, // right: Math.random()
|
|
598
|
+
/\bbottom:\s*.*Math\.random/i, // bottom: Math.random()
|
|
599
|
+
/\brotation:\s*.*Math\.random/i, // rotation: Math.random()
|
|
600
|
+
/\brotateX:\s*.*Math\.random/i, // rotateX: Math.random()
|
|
601
|
+
/\brotateY:\s*.*Math\.random/i, // rotateY: Math.random()
|
|
602
|
+
/\brotateZ:\s*.*Math\.random/i, // rotateZ: Math.random()
|
|
603
|
+
/\bscaleX?:\s*.*Math\.random/i, // scale/scaleX: Math.random()
|
|
604
|
+
/\bscaleY:\s*.*Math\.random/i, // scaleY: Math.random()
|
|
605
|
+
/\bopacity:\s*.*Math\.random/i, // opacity: Math.random()
|
|
606
|
+
/\bduration:\s*.*Math\.random/i, // duration: Math.random()
|
|
607
|
+
/\bdelay:\s*.*Math\.random/i, // delay: Math.random()
|
|
608
|
+
// 3D/Three.js specific patterns
|
|
609
|
+
/\boffset\s*=.*Math\.random/i, // offset = Math.random()
|
|
610
|
+
/useMemo\s*\(\s*\(\s*\)\s*=>\s*Math\.random/i, // useMemo(() => Math.random(), [])
|
|
611
|
+
/\bphase\s*[:=].*Math\.random/i, // phase: Math.random() or phase = Math.random()
|
|
612
|
+
/\bspeed\s*[:=].*Math\.random/i, // speed: Math.random()
|
|
613
|
+
/\bangle\s*[:=].*Math\.random/i, // angle: Math.random()
|
|
614
|
+
]
|
|
615
|
+
|
|
616
|
+
if (coordinatePatterns.some(p => p.test(lineContent))) {
|
|
617
|
+
return true
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Motion/animation library context patterns
|
|
621
|
+
const motionLibraryPatterns = [
|
|
622
|
+
/framer-motion/i,
|
|
623
|
+
/react-spring/i,
|
|
624
|
+
/react-motion/i,
|
|
625
|
+
/gsap/i,
|
|
626
|
+
/animejs/i,
|
|
627
|
+
/popmotion/i,
|
|
628
|
+
/motion\.div/i,
|
|
629
|
+
/useSpring/i,
|
|
630
|
+
/useAnimation/i,
|
|
631
|
+
/animate\s*\(/i,
|
|
632
|
+
/keyframes/i,
|
|
633
|
+
// Three.js and React Three Fiber patterns
|
|
634
|
+
/three/i,
|
|
635
|
+
/useFrame/i,
|
|
636
|
+
/@react-three/i,
|
|
637
|
+
/Canvas/i, // React Three Fiber Canvas
|
|
638
|
+
/mesh/i, // Three.js mesh
|
|
639
|
+
/geometry/i, // Three.js geometry
|
|
640
|
+
/material/i, // Three.js material
|
|
641
|
+
]
|
|
642
|
+
|
|
643
|
+
return motionLibraryPatterns.some(p => p.test(context))
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Check if Math.random() is used in a template placeholder generator context
|
|
648
|
+
* Template systems often use random generators for placeholder values
|
|
649
|
+
*
|
|
650
|
+
* Examples:
|
|
651
|
+
* const templates = { random: () => Math.random().toString() }
|
|
652
|
+
* random_hex: () => Math.random().toString(16)
|
|
653
|
+
* {{random}} placeholder generation
|
|
654
|
+
*/
|
|
655
|
+
export function isTemplatePlaceholderGenerator(
|
|
656
|
+
line: string,
|
|
657
|
+
content: string,
|
|
658
|
+
lineNumber: number,
|
|
659
|
+
lines?: string[]
|
|
660
|
+
): boolean {
|
|
661
|
+
const _lines = lines ?? content.split('\n')
|
|
662
|
+
const contextStart = Math.max(0, lineNumber - 10)
|
|
663
|
+
const contextEnd = Math.min(_lines.length, lineNumber + 5)
|
|
664
|
+
const context = _lines.slice(contextStart, contextEnd).join('\n')
|
|
665
|
+
|
|
666
|
+
const templatePatterns = [
|
|
667
|
+
/\{\{random\w*\}\}/i, // {{random}}, {{random_hex}}, etc.
|
|
668
|
+
/random:\s*\(\s*\)\s*=>\s*Math\.random/i, // random: () => Math.random()
|
|
669
|
+
/random_\w+:\s*\(\s*\)\s*=>/i, // random_int: () => ...
|
|
670
|
+
/placeholder.*random/i, // placeholder context
|
|
671
|
+
/templates?\s*[=:]\s*\{/i, // templates = { or template: {
|
|
672
|
+
/generatePlaceholder/i, // generatePlaceholder function
|
|
673
|
+
/placeholder\s*[:=]/i, // placeholder: or placeholder =
|
|
674
|
+
]
|
|
675
|
+
|
|
676
|
+
return templatePatterns.some(p => p.test(context) || p.test(line))
|
|
677
|
+
}
|
|
678
|
+
|
|
476
679
|
/**
|
|
477
680
|
* Check if Math.random() should be skipped entirely
|
|
478
|
-
* Returns true for seed files, test fixtures, captcha/puzzle, uuid, and pure cosmetic uses
|
|
681
|
+
* Returns true for seed files, test fixtures, captcha/puzzle, uuid, React keys, jitter/backoff, and pure cosmetic uses
|
|
479
682
|
*/
|
|
480
683
|
export function shouldSkipMathRandom(
|
|
481
684
|
content: string,
|
|
482
685
|
filePath: string,
|
|
483
|
-
lineNumber: number
|
|
686
|
+
lineNumber: number,
|
|
687
|
+
options?: { parsed?: ParsedFile }
|
|
484
688
|
): boolean {
|
|
485
689
|
// Seed/data generation files - skip entirely
|
|
486
690
|
if (isSeedOrDataGenFile(filePath)) {
|
|
487
691
|
return true
|
|
488
692
|
}
|
|
489
693
|
|
|
694
|
+
// Animation/motion component files - skip entirely
|
|
695
|
+
// These use Math.random for visual effects, not security
|
|
696
|
+
if (isAnimationFile(filePath)) {
|
|
697
|
+
return true
|
|
698
|
+
}
|
|
699
|
+
|
|
490
700
|
// Educational/intentional vulnerability files - skip entirely
|
|
491
701
|
// These include OWASP Juice Shop, intentionally-vulnerable examples, etc.
|
|
492
702
|
if (isEducationalVulnerabilityFile(filePath)) {
|
|
493
703
|
return true
|
|
494
704
|
}
|
|
495
705
|
|
|
706
|
+
// Check for React key pattern - this is a common pattern to force re-renders
|
|
707
|
+
// It's not a security issue, just a way to reset component state
|
|
708
|
+
const lines = options?.parsed?.lines ?? content.split('\n')
|
|
709
|
+
const lineContent = lines[lineNumber] || ''
|
|
710
|
+
if (isReactKeyPattern(lineContent, filePath)) {
|
|
711
|
+
return true
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Template placeholder generators - skip entirely
|
|
715
|
+
// These generate placeholder values for templates, not security tokens
|
|
716
|
+
if (isTemplatePlaceholderGenerator(lineContent, content, lineNumber, lines)) {
|
|
717
|
+
return true
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Jitter/backoff/retry patterns - legitimate non-security use of randomness
|
|
721
|
+
// Used for network resilience, rate limiting, exponential backoff, etc.
|
|
722
|
+
if (isJitterOrBackoffContext(content, lineNumber, lines)) {
|
|
723
|
+
return true
|
|
724
|
+
}
|
|
725
|
+
|
|
496
726
|
// Test files with test fixture patterns
|
|
497
727
|
if (isTestOrMockFile(filePath)) {
|
|
498
|
-
const lines = content.split('\n')
|
|
499
728
|
const line = lines[lineNumber]
|
|
500
729
|
// If in a test file and generating test data, skip
|
|
501
730
|
if (
|
|
@@ -507,9 +736,7 @@ export function shouldSkipMathRandom(
|
|
|
507
736
|
}
|
|
508
737
|
|
|
509
738
|
// Pure cosmetic usage (CSS values, animations)
|
|
510
|
-
|
|
511
|
-
const lineContent = lines[lineNumber] || ''
|
|
512
|
-
if (isCosmeticMathRandom(lineContent, content, lineNumber)) {
|
|
739
|
+
if (isCosmeticMathRandom(lineContent, content, lineNumber, lines)) {
|
|
513
740
|
// Additional check: if this is for animation/style, truly skip
|
|
514
741
|
const pureStylePatterns = [
|
|
515
742
|
/\.style\./,
|
|
@@ -524,6 +751,15 @@ export function shouldSkipMathRandom(
|
|
|
524
751
|
}
|
|
525
752
|
}
|
|
526
753
|
|
|
754
|
+
// Animation coordinate usage (x, y, rotation, etc.)
|
|
755
|
+
// Get surrounding context for animation library detection
|
|
756
|
+
const contextStart = Math.max(0, lineNumber - 15)
|
|
757
|
+
const contextEnd = Math.min(lines.length, lineNumber + 5)
|
|
758
|
+
const animContext = lines.slice(contextStart, contextEnd).join('\n')
|
|
759
|
+
if (isAnimationCoordinateUsage(lineContent, animContext)) {
|
|
760
|
+
return true
|
|
761
|
+
}
|
|
762
|
+
|
|
527
763
|
// Check function context for demo/seed/captcha/uuid functions
|
|
528
764
|
const functionName = extractFunctionContext(content, lineNumber)
|
|
529
765
|
const functionIntent = classifyFunctionIntent(functionName)
|
|
@@ -533,5 +769,21 @@ export function shouldSkipMathRandom(
|
|
|
533
769
|
return true
|
|
534
770
|
}
|
|
535
771
|
|
|
772
|
+
// Check for fallback pattern: crypto.randomUUID?.() ?? Math.random()
|
|
773
|
+
// When a secure primary method is used with Math.random as fallback,
|
|
774
|
+
// the code is safe (Math.random only runs in environments without crypto API)
|
|
775
|
+
const prevLines = lines.slice(Math.max(0, lineNumber - 2), lineNumber + 1).join(' ')
|
|
776
|
+
const multiLineFallbackPatterns = [
|
|
777
|
+
/crypto\??\.?\s*randomUUID\??\.?\s*\(\s*\)\s*\?\?/i, // crypto.randomUUID() ??
|
|
778
|
+
/globalThis\.crypto\??\.?\s*randomUUID\??\.?\s*\(/i, // globalThis.crypto?.randomUUID?.()
|
|
779
|
+
/window\.crypto\??\.?\s*randomUUID\??\.?\s*\(/i, // window.crypto?.randomUUID?.()
|
|
780
|
+
/\?\.\s*randomUUID\??\.?\s*\(\s*\)\s*\?\?/i, // ?.randomUUID?.() ??
|
|
781
|
+
/crypto\??\.?\s*getRandomValues\s*\(/i, // crypto.getRandomValues()
|
|
782
|
+
/randomUUID\??\.?\s*\(\s*\)\s*\?\?/i, // randomUUID?.() ?? (generic)
|
|
783
|
+
]
|
|
784
|
+
if (multiLineFallbackPatterns.some(p => p.test(prevLines))) {
|
|
785
|
+
return true // Skip - Math.random is only a fallback for missing crypto API
|
|
786
|
+
}
|
|
787
|
+
|
|
536
788
|
return false
|
|
537
789
|
}
|
|
@@ -58,9 +58,11 @@ export const DANGEROUS_FUNCTIONS: DangerousFunctionPattern[] = [
|
|
|
58
58
|
},
|
|
59
59
|
|
|
60
60
|
// SQL injection risks
|
|
61
|
+
// Note: .execute is too generic (used by axios, tRPC, request utilities)
|
|
62
|
+
// Focus on .query and .raw which are more SQL-specific
|
|
61
63
|
{
|
|
62
64
|
name: 'Raw SQL query construction',
|
|
63
|
-
pattern: /\.(query|
|
|
65
|
+
pattern: /\.(query|raw)\s*\(\s*[`'"].*\$\{|\.query\s*\(\s*['"].*\+/gi,
|
|
64
66
|
severity: 'critical',
|
|
65
67
|
description: 'String concatenation in SQL queries can lead to SQL injection',
|
|
66
68
|
suggestedFix: 'Use parameterized queries or prepared statements',
|
|
@@ -1,65 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Control Flow Analysis Utilities
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Backward-compatible wrappers around shared code-analysis utilities.
|
|
5
|
+
* Existing callers pass (content: string, lineNumber: number) — these
|
|
6
|
+
* wrappers create a temporary ParsedFile to delegate to the shared implementation.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
import {
|
|
9
|
+
import { ParsedFile } from '../../../utils/parsed-file'
|
|
10
|
+
import * as codeAnalysis from '../../../utils/code-analysis'
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Check if a line is inside a try-catch block
|
|
12
14
|
* Looks for enclosing try { ... } catch pattern
|
|
13
15
|
*/
|
|
14
16
|
export function isInsideTryCatch(content: string, lineNumber: number): boolean {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// Track brace depth and whether we're in a try block
|
|
18
|
-
let tryDepth = 0
|
|
19
|
-
let inTryBlock = false
|
|
20
|
-
const braceStack: Array<'try' | 'other'> = []
|
|
21
|
-
|
|
22
|
-
// Scan from start to the target line
|
|
23
|
-
for (let i = 0; i < lineNumber && i < lines.length; i++) {
|
|
24
|
-
const line = lines[i]
|
|
25
|
-
|
|
26
|
-
// Check for try keyword (not in a comment)
|
|
27
|
-
if (/\btry\s*\{/.test(line) && !isComment(line)) {
|
|
28
|
-
inTryBlock = true
|
|
29
|
-
tryDepth++
|
|
30
|
-
// Count opening braces on this line
|
|
31
|
-
const openBraces = (line.match(/\{/g) || []).length
|
|
32
|
-
const closeBraces = (line.match(/\}/g) || []).length
|
|
33
|
-
for (let j = 0; j < openBraces - closeBraces; j++) {
|
|
34
|
-
braceStack.push('try')
|
|
35
|
-
}
|
|
36
|
-
} else if (/\bcatch\s*\(/.test(line) && !isComment(line)) {
|
|
37
|
-
// Entering catch block - still protected
|
|
38
|
-
// Don't decrement tryDepth yet
|
|
39
|
-
} else if (/\bfinally\s*\{/.test(line) && !isComment(line)) {
|
|
40
|
-
// Entering finally block - still protected
|
|
41
|
-
} else {
|
|
42
|
-
// Track regular braces
|
|
43
|
-
const openBraces = (line.match(/\{/g) || []).length
|
|
44
|
-
const closeBraces = (line.match(/\}/g) || []).length
|
|
45
|
-
|
|
46
|
-
for (let j = 0; j < openBraces; j++) {
|
|
47
|
-
braceStack.push(inTryBlock && tryDepth > 0 ? 'try' : 'other')
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
for (let j = 0; j < closeBraces; j++) {
|
|
51
|
-
const popped = braceStack.pop()
|
|
52
|
-
if (popped === 'try') {
|
|
53
|
-
tryDepth--
|
|
54
|
-
if (tryDepth === 0) {
|
|
55
|
-
inTryBlock = false
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return tryDepth > 0
|
|
17
|
+
return codeAnalysis.isInsideTryCatch(new ParsedFile(content, ''), lineNumber)
|
|
63
18
|
}
|
|
64
19
|
|
|
65
20
|
/**
|
|
@@ -67,39 +22,7 @@ export function isInsideTryCatch(content: string, lineNumber: number): boolean {
|
|
|
67
22
|
* Looks for try { before the line and } catch after, within reasonable bounds
|
|
68
23
|
*/
|
|
69
24
|
export function hasTryCatchNearby(content: string, lineNumber: number, windowSize: number = 20): boolean {
|
|
70
|
-
|
|
71
|
-
const startLine = Math.max(0, lineNumber - windowSize)
|
|
72
|
-
const endLine = Math.min(lines.length, lineNumber + windowSize)
|
|
73
|
-
|
|
74
|
-
// Look backward for 'try {'
|
|
75
|
-
let foundTry = false
|
|
76
|
-
for (let i = lineNumber - 1; i >= startLine; i--) {
|
|
77
|
-
const line = lines[i]
|
|
78
|
-
if (/\btry\s*\{/.test(line) && !isComment(line)) {
|
|
79
|
-
foundTry = true
|
|
80
|
-
break
|
|
81
|
-
}
|
|
82
|
-
// Stop if we hit a function boundary
|
|
83
|
-
if (/\b(function|async function|=>|class)\b/.test(line) && /\{/.test(line)) {
|
|
84
|
-
break
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (!foundTry) return false
|
|
89
|
-
|
|
90
|
-
// Look forward for '} catch'
|
|
91
|
-
for (let i = lineNumber; i < endLine; i++) {
|
|
92
|
-
const line = lines[i]
|
|
93
|
-
if (/\}\s*catch\s*\(/.test(line) && !isComment(line)) {
|
|
94
|
-
return true
|
|
95
|
-
}
|
|
96
|
-
// Stop if we hit another function boundary
|
|
97
|
-
if (i > lineNumber && /\b(function|async function|class)\b/.test(line) && /\{/.test(line)) {
|
|
98
|
-
break
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return false
|
|
25
|
+
return codeAnalysis.hasTryCatchNearby(new ParsedFile(content, ''), lineNumber, windowSize)
|
|
103
26
|
}
|
|
104
27
|
|
|
105
28
|
/**
|
|
@@ -108,55 +31,5 @@ export function hasTryCatchNearby(content: string, lineNumber: number, windowSiz
|
|
|
108
31
|
* Returns lowercase function name or null if not found
|
|
109
32
|
*/
|
|
110
33
|
export function extractFunctionContext(content: string, lineNumber: number): string | null {
|
|
111
|
-
|
|
112
|
-
const start = Math.max(0, lineNumber - 20) // Increased from 10 to 20 for nested callbacks
|
|
113
|
-
|
|
114
|
-
// Look backwards for function declaration
|
|
115
|
-
for (let i = lineNumber; i >= start; i--) {
|
|
116
|
-
const line = lines[i]
|
|
117
|
-
|
|
118
|
-
// Skip anonymous arrow functions in callbacks (e.g., .map((x) => ...), .replace(/x/g, (c) => ...))
|
|
119
|
-
// These are not the function context we're looking for
|
|
120
|
-
// Look for pattern: .methodName(..., (param) => or .methodName(...(param) =>
|
|
121
|
-
const hasMethodCallWithArrowCallback = /\.\w+\(.*\([^)]*\)\s*=>/.test(line)
|
|
122
|
-
|
|
123
|
-
// Skip lines that only have arrow callbacks in method calls
|
|
124
|
-
if (hasMethodCallWithArrowCallback && !/^(const|let|var|function|async|export)/.test(line.trim())) {
|
|
125
|
-
continue
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Named function declaration: function funcName(
|
|
129
|
-
const funcMatch = line.match(/function\s+(\w+)\s*\(/)
|
|
130
|
-
if (funcMatch) {
|
|
131
|
-
return funcMatch[1].toLowerCase()
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Arrow function with const/let/var: const funcName = () => | const funcName = async () =>
|
|
135
|
-
// Must have => after the parameters to distinguish from const x = (expression)
|
|
136
|
-
// Also handles TypeScript return type annotations: const funcName = (): string =>
|
|
137
|
-
const arrowMatch = line.match(/(const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)(?:\s*:\s*\w+)?\s*=>/)
|
|
138
|
-
if (arrowMatch) {
|
|
139
|
-
return arrowMatch[2].toLowerCase()
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Method declaration: methodName() { or async methodName() {
|
|
143
|
-
const methodMatch = line.match(/^\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/)
|
|
144
|
-
if (methodMatch) {
|
|
145
|
-
return methodMatch[1].toLowerCase()
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Export function: export function funcName( or export const funcName = () =>
|
|
149
|
-
const exportFuncMatch = line.match(/export\s+(?:async\s+)?function\s+(\w+)\s*\(/)
|
|
150
|
-
if (exportFuncMatch) {
|
|
151
|
-
return exportFuncMatch[1].toLowerCase()
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Also handles TypeScript return type annotations: export const funcName = (): string =>
|
|
155
|
-
const exportConstMatch = line.match(/export\s+const\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)(?:\s*:\s*\w+)?\s*=>/)
|
|
156
|
-
if (exportConstMatch) {
|
|
157
|
-
return exportConstMatch[1].toLowerCase()
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return null
|
|
34
|
+
return codeAnalysis.extractFunctionContext(new ParsedFile(content, ''), lineNumber)
|
|
162
35
|
}
|
|
@@ -72,12 +72,13 @@ export function hasManualValidation(content: string): boolean {
|
|
|
72
72
|
*/
|
|
73
73
|
export function hasSQLWhitelistValidation(content: string, lineNumber: number): boolean {
|
|
74
74
|
const lines = content.split('\n')
|
|
75
|
-
const contextStart = Math.max(0, lineNumber -
|
|
75
|
+
const contextStart = Math.max(0, lineNumber - 20)
|
|
76
76
|
const contextEnd = Math.min(lines.length, lineNumber + 5)
|
|
77
77
|
const context = lines.slice(contextStart, contextEnd).join('\n')
|
|
78
78
|
|
|
79
79
|
// Whitelist/allowlist validation patterns
|
|
80
80
|
const whitelistPatterns = [
|
|
81
|
+
// Array-based whitelists
|
|
81
82
|
/allowed\w*\s*=\s*\[/i, // allowedColumns = [...]
|
|
82
83
|
/whitelist\w*\s*=\s*\[/i, // whitelistFields = [...]
|
|
83
84
|
/valid\w*\s*=\s*\[/i, // validColumns = [...]
|
|
@@ -85,6 +86,20 @@ export function hasSQLWhitelistValidation(content: string, lineNumber: number):
|
|
|
85
86
|
/\.includes\s*\([^)]*\)/i, // allowedColumns.includes(col)
|
|
86
87
|
/\.every\s*\([^)]*\.includes/i, // columns.every(c => allowed.includes(c))
|
|
87
88
|
/if\s*\(\s*!.*\.includes/i, // if (!allowed.includes(...))
|
|
89
|
+
|
|
90
|
+
// Object-based whitelists (Record<string, string>)
|
|
91
|
+
/\w+\s+in\s+\w*(?:sortable|allowed|valid|whitelist)\w*/i, // sorter in sortableFields
|
|
92
|
+
/\w+\s+in\s+\w+Fields/i, // key in someFields
|
|
93
|
+
/:\s*Record<string,\s*string>/i, // Type annotation: Record<string, string>
|
|
94
|
+
/const\s+\w+Fields\s*:\s*\{[^}]*\}\s*=/i, // const xyzFields: {...} = (inline type)
|
|
95
|
+
/const\s+\w+Fields\s*=\s*\{[^}]*\}/i, // const xyzFields = { ... }
|
|
96
|
+
/if\s*\([^)]*\s+in\s+\w+Fields\s*\)/i, // if (x in yFields)
|
|
97
|
+
/&&\s*\w+\s+in\s+\w+/i, // && sorter in sortableFields
|
|
98
|
+
|
|
99
|
+
// Enum-based validation (ASC/DESC, etc.)
|
|
100
|
+
/===?\s*['"](?:ASC|DESC)['"]/i, // === 'ASC' or === 'DESC'
|
|
101
|
+
/===?\s*\w+\.(?:Asc|Desc|ASC|DESC)/i, // === SortType.Asc
|
|
102
|
+
/toLowerCase\s*\(\s*\)\s*===?\s*\w+\.(?:asc|desc)/i, // .toLowerCase() === SortType.asc
|
|
88
103
|
]
|
|
89
104
|
|
|
90
105
|
return whitelistPatterns.some(p => p.test(context))
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Vulnerability, VulnerabilitySeverity } from '../types'
|
|
8
|
+
import type { ParsedFile } from '../utils/parsed-file'
|
|
8
9
|
import { isComment, isTestOrMockFile, isScannerOrFixtureFile } from '../utils/context-helpers'
|
|
9
10
|
|
|
10
11
|
interface DataExposurePattern {
|
|
@@ -18,44 +19,17 @@ interface DataExposurePattern {
|
|
|
18
19
|
|
|
19
20
|
const DATA_EXPOSURE_PATTERNS: DataExposurePattern[] = [
|
|
20
21
|
// ============================================================================
|
|
21
|
-
// LOG SINKS
|
|
22
|
-
//
|
|
22
|
+
// LOG SINKS - DISABLED
|
|
23
|
+
// Server logs are never exposed to users. After analysis of real codebases,
|
|
24
|
+
// 100% of console.error/warn findings were false positives.
|
|
25
|
+
// Logging is a standard debugging practice, not a security vulnerability.
|
|
23
26
|
// ============================================================================
|
|
24
27
|
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
sink: 'log',
|
|
31
|
-
severity: 'info',
|
|
32
|
-
description: 'User ID logged to console. Common debugging practice - verify logs are secured.',
|
|
33
|
-
suggestedFix: 'Use structured logging with appropriate access controls. Remove before production if not needed.',
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
name: 'Logging error objects',
|
|
37
|
-
pattern: /console\.(log|error|warn)\s*\(\s*(err|error|e)\s*\)/gi,
|
|
38
|
-
sink: 'log',
|
|
39
|
-
severity: 'info', // Downgraded from 'low' - this is standard practice
|
|
40
|
-
description: 'Error object logged to console. Standard debugging practice, but may expose stack traces in logs.',
|
|
41
|
-
suggestedFix: 'Consider logging only error.message in production. Use structured logging for better control.',
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
name: 'Logging request body',
|
|
45
|
-
pattern: /console\.(log|info|debug)\s*\([^)]*\b(req\.body|request\.body|body)\b/gi,
|
|
46
|
-
sink: 'log',
|
|
47
|
-
severity: 'low',
|
|
48
|
-
description: 'Request body logged to console. May contain sensitive user input.',
|
|
49
|
-
suggestedFix: 'Redact sensitive fields (passwords, tokens) before logging.',
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
name: 'JSON.stringify error to log',
|
|
53
|
-
pattern: /console\.(log|error|warn)\s*\([^)]*JSON\.stringify\s*\(\s*(err|error|e)\s*\)/gi,
|
|
54
|
-
sink: 'log',
|
|
55
|
-
severity: 'info',
|
|
56
|
-
description: 'Error object serialized to JSON for logging. May include stack traces.',
|
|
57
|
-
suggestedFix: 'Consider logging specific error properties instead of the full serialized object.',
|
|
58
|
-
},
|
|
28
|
+
// NOTE: The following patterns have been REMOVED to eliminate false positives:
|
|
29
|
+
// - 'Logging user ID' - user IDs in logs are standard practice
|
|
30
|
+
// - 'Logging error objects' - console.error(err) is correct error handling
|
|
31
|
+
// - 'Logging request body' - server logs are not exposed to clients
|
|
32
|
+
// - 'JSON.stringify error to log' - serializing errors for logs is fine
|
|
59
33
|
|
|
60
34
|
// ============================================================================
|
|
61
35
|
// RESPONSE SINKS (higher severity - exposed to clients)
|
|
@@ -171,14 +145,15 @@ function isLowRiskLoggingFile(filePath: string): boolean {
|
|
|
171
145
|
*/
|
|
172
146
|
export function detectDataExposure(
|
|
173
147
|
content: string,
|
|
174
|
-
filePath: string
|
|
148
|
+
filePath: string,
|
|
149
|
+
options?: { parsed?: ParsedFile }
|
|
175
150
|
): Vulnerability[] {
|
|
176
151
|
const vulnerabilities: Vulnerability[] = []
|
|
177
152
|
|
|
178
153
|
// Skip scanner/fixture files to avoid self-detection
|
|
179
154
|
if (isScannerOrFixtureFile(filePath)) return vulnerabilities
|
|
180
155
|
|
|
181
|
-
const lines = content.split('\n')
|
|
156
|
+
const lines = options?.parsed?.lines ?? content.split('\n')
|
|
182
157
|
const isTestFile = isTestOrMockFile(filePath)
|
|
183
158
|
const isLowRiskFile = isLowRiskLoggingFile(filePath)
|
|
184
159
|
|