@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,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 2: Structural Scan
|
|
3
|
+
* Contextual analysis using variable heuristics, logic gate detection,
|
|
4
|
+
* dangerous functions, risky imports, auth anti-patterns, framework checks,
|
|
5
|
+
* and AI code fingerprinting
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Vulnerability, ScanFile } from '../types'
|
|
9
|
+
import type { MiddlewareAuthConfig } from '../utils/middleware-detector'
|
|
10
|
+
import { detectAuthHelpers, type AuthHelperContext } from '../utils/auth-helper-detector'
|
|
11
|
+
import type { FileAuthImports } from '../utils/imported-auth-detector'
|
|
12
|
+
import {
|
|
13
|
+
filterFindingsByPath,
|
|
14
|
+
type ExclusionConfig,
|
|
15
|
+
} from '../utils/path-exclusions'
|
|
16
|
+
import { detectSensitiveVariables } from './variables'
|
|
17
|
+
import { detectLogicGates } from './logic-gates'
|
|
18
|
+
import { detectDangerousFunctions } from './dangerous-functions'
|
|
19
|
+
import { detectRiskyImports } from './risky-imports'
|
|
20
|
+
import { detectAuthAntipatterns } from './auth-antipatterns'
|
|
21
|
+
import { detectFrameworkIssues } from './framework-checks'
|
|
22
|
+
import { detectAIFingerprints } from './ai-fingerprinting'
|
|
23
|
+
import { detectDataExposure } from './data-exposure'
|
|
24
|
+
import { detectBYOKPatterns } from './byok-patterns'
|
|
25
|
+
// Story B: AI-specific detection modules
|
|
26
|
+
import { detectAIPromptHygiene } from './ai-prompt-hygiene'
|
|
27
|
+
import { detectAIExecutionSinks } from './ai-execution-sinks'
|
|
28
|
+
import { detectAIAgentTools } from './ai-agent-tools'
|
|
29
|
+
// M5: New AI-era detectors
|
|
30
|
+
import { detectRAGSafetyIssues } from './ai-rag-safety'
|
|
31
|
+
import { detectAIEndpointProtection } from './ai-endpoint-protection'
|
|
32
|
+
import { detectAISchemaValidation } from './ai-schema-validation'
|
|
33
|
+
// Tier system imports
|
|
34
|
+
import {
|
|
35
|
+
type TierStats,
|
|
36
|
+
computeTierStats,
|
|
37
|
+
formatTierStats,
|
|
38
|
+
getLayer2DetectorTier,
|
|
39
|
+
type Layer2DetectorName,
|
|
40
|
+
} from '../tiers'
|
|
41
|
+
|
|
42
|
+
export interface Layer2Options {
|
|
43
|
+
middlewareConfig?: MiddlewareAuthConfig
|
|
44
|
+
authHelperContext?: AuthHelperContext
|
|
45
|
+
fileAuthImports?: Map<string, FileAuthImports>
|
|
46
|
+
/** Whether to exclude test files from scanning (default: true) */
|
|
47
|
+
excludeTestFiles?: boolean
|
|
48
|
+
/** Whether to exclude seed files from scanning (default: true) */
|
|
49
|
+
excludeSeedFiles?: boolean
|
|
50
|
+
/** Custom exclusion patterns (glob format) */
|
|
51
|
+
customExclusions?: string[]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface Layer2Stats {
|
|
55
|
+
/** Raw finding counts per detector (before dedupe) */
|
|
56
|
+
raw: Record<string, number>
|
|
57
|
+
/** Deduped finding counts per category */
|
|
58
|
+
deduped: Record<string, number>
|
|
59
|
+
/** Severity distribution of deduped findings */
|
|
60
|
+
bySeverity: Record<string, number>
|
|
61
|
+
/** Tier breakdown of deduped findings */
|
|
62
|
+
tiers: TierStats
|
|
63
|
+
/** Findings suppressed by path exclusions */
|
|
64
|
+
suppressedByPath: number
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface Layer2Result {
|
|
68
|
+
vulnerabilities: Vulnerability[]
|
|
69
|
+
filesScanned: number
|
|
70
|
+
duration: number
|
|
71
|
+
/** Heuristic breakdown stats for noise analysis */
|
|
72
|
+
stats: Layer2Stats
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function runLayer2Scan(
|
|
76
|
+
files: ScanFile[],
|
|
77
|
+
options: Layer2Options = {}
|
|
78
|
+
): Promise<Layer2Result> {
|
|
79
|
+
const startTime = Date.now()
|
|
80
|
+
const vulnerabilities: Vulnerability[] = []
|
|
81
|
+
const stats = {
|
|
82
|
+
variables: 0,
|
|
83
|
+
logicGates: 0,
|
|
84
|
+
dangerousFunctions: 0,
|
|
85
|
+
riskyImports: 0,
|
|
86
|
+
authAntipatterns: 0,
|
|
87
|
+
frameworkIssues: 0,
|
|
88
|
+
aiFingerprints: 0,
|
|
89
|
+
dataExposure: 0,
|
|
90
|
+
byokPatterns: 0,
|
|
91
|
+
promptHygiene: 0,
|
|
92
|
+
executionSinks: 0,
|
|
93
|
+
agentTools: 0,
|
|
94
|
+
// M5: New AI-era detectors
|
|
95
|
+
ragSafety: 0,
|
|
96
|
+
endpointProtection: 0,
|
|
97
|
+
schemaValidation: 0,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Detect auth helpers once for all files (if not already provided)
|
|
101
|
+
const authHelperContext = options.authHelperContext || detectAuthHelpers(files)
|
|
102
|
+
|
|
103
|
+
for (const file of files) {
|
|
104
|
+
// Only scan code files for Layer 2 (skip configs, etc.)
|
|
105
|
+
if (isCodeFile(file.path)) {
|
|
106
|
+
// Existing scanners
|
|
107
|
+
const variableFindings = detectSensitiveVariables(file.content, file.path)
|
|
108
|
+
const logicFindings = detectLogicGates(file.content, file.path)
|
|
109
|
+
|
|
110
|
+
// New Layer 2 scanners
|
|
111
|
+
const dangerousFuncFindings = detectDangerousFunctions(file.content, file.path)
|
|
112
|
+
const riskyImportFindings = detectRiskyImports(file.content, file.path)
|
|
113
|
+
const authFindings = detectAuthAntipatterns(file.content, file.path, {
|
|
114
|
+
middlewareConfig: options.middlewareConfig,
|
|
115
|
+
authHelpers: authHelperContext,
|
|
116
|
+
fileAuthImports: options.fileAuthImports,
|
|
117
|
+
})
|
|
118
|
+
const frameworkFindings = detectFrameworkIssues(file.content, file.path)
|
|
119
|
+
const aiFindings = detectAIFingerprints(file.content, file.path)
|
|
120
|
+
const dataExposureFindings = detectDataExposure(file.content, file.path)
|
|
121
|
+
const byokFindings = detectBYOKPatterns(file.content, file.path, options.middlewareConfig)
|
|
122
|
+
|
|
123
|
+
// Story B: AI-specific detection (prompt hygiene, execution sinks, agent tools)
|
|
124
|
+
const promptHygieneFindings = detectAIPromptHygiene(file.content, file.path)
|
|
125
|
+
const executionSinkFindings = detectAIExecutionSinks(file.content, file.path)
|
|
126
|
+
const agentToolFindings = detectAIAgentTools(file.content, file.path)
|
|
127
|
+
|
|
128
|
+
// M5: New AI-era detectors
|
|
129
|
+
const ragSafetyFindings = detectRAGSafetyIssues(file.content, file.path)
|
|
130
|
+
const endpointProtectionFindings = detectAIEndpointProtection(file.content, file.path, {
|
|
131
|
+
middlewareConfig: options.middlewareConfig,
|
|
132
|
+
})
|
|
133
|
+
const schemaValidationFindings = detectAISchemaValidation(file.content, file.path)
|
|
134
|
+
|
|
135
|
+
stats.variables += variableFindings.length
|
|
136
|
+
stats.logicGates += logicFindings.length
|
|
137
|
+
stats.dangerousFunctions += dangerousFuncFindings.length
|
|
138
|
+
stats.riskyImports += riskyImportFindings.length
|
|
139
|
+
stats.authAntipatterns += authFindings.length
|
|
140
|
+
stats.frameworkIssues += frameworkFindings.length
|
|
141
|
+
stats.aiFingerprints += aiFindings.length
|
|
142
|
+
stats.dataExposure += dataExposureFindings.length
|
|
143
|
+
stats.byokPatterns += byokFindings.length
|
|
144
|
+
stats.promptHygiene += promptHygieneFindings.length
|
|
145
|
+
stats.executionSinks += executionSinkFindings.length
|
|
146
|
+
stats.agentTools += agentToolFindings.length
|
|
147
|
+
stats.ragSafety += ragSafetyFindings.length
|
|
148
|
+
stats.endpointProtection += endpointProtectionFindings.length
|
|
149
|
+
stats.schemaValidation += schemaValidationFindings.length
|
|
150
|
+
|
|
151
|
+
vulnerabilities.push(
|
|
152
|
+
...variableFindings,
|
|
153
|
+
...logicFindings,
|
|
154
|
+
...dangerousFuncFindings,
|
|
155
|
+
...riskyImportFindings,
|
|
156
|
+
...authFindings,
|
|
157
|
+
...frameworkFindings,
|
|
158
|
+
...aiFindings,
|
|
159
|
+
...dataExposureFindings,
|
|
160
|
+
...byokFindings,
|
|
161
|
+
...promptHygieneFindings,
|
|
162
|
+
...executionSinkFindings,
|
|
163
|
+
...agentToolFindings,
|
|
164
|
+
...ragSafetyFindings,
|
|
165
|
+
...endpointProtectionFindings,
|
|
166
|
+
...schemaValidationFindings
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Deduplicate findings
|
|
172
|
+
const dedupedVulnerabilities = deduplicateFindings(vulnerabilities)
|
|
173
|
+
|
|
174
|
+
// Apply path exclusions (test files, seed files, etc.)
|
|
175
|
+
// By default, exclude test and seed files unless explicitly disabled
|
|
176
|
+
const excludeTestFiles = options.excludeTestFiles !== false
|
|
177
|
+
const excludeSeedFiles = options.excludeSeedFiles !== false
|
|
178
|
+
|
|
179
|
+
// Build exclusion config based on options
|
|
180
|
+
const exclusionConfig: Partial<ExclusionConfig> = {}
|
|
181
|
+
if (!excludeTestFiles) {
|
|
182
|
+
exclusionConfig.testPatterns = []
|
|
183
|
+
}
|
|
184
|
+
if (!excludeSeedFiles) {
|
|
185
|
+
exclusionConfig.seedPatterns = []
|
|
186
|
+
}
|
|
187
|
+
if (options.customExclusions) {
|
|
188
|
+
// Add custom exclusions to all pattern types
|
|
189
|
+
exclusionConfig.testPatterns = [
|
|
190
|
+
...(exclusionConfig.testPatterns || []),
|
|
191
|
+
...options.customExclusions,
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const { kept: uniqueVulnerabilities, suppressed } = filterFindingsByPath(
|
|
196
|
+
dedupedVulnerabilities,
|
|
197
|
+
Object.keys(exclusionConfig).length > 0 ? exclusionConfig : undefined
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
// Log suppressed findings
|
|
201
|
+
if (suppressed.length > 0) {
|
|
202
|
+
console.log(`[Layer 2] Suppressed ${suppressed.length} findings in test/seed/example files:`)
|
|
203
|
+
const byReason = new Map<string, number>()
|
|
204
|
+
for (const { reason } of suppressed) {
|
|
205
|
+
byReason.set(reason || 'unknown', (byReason.get(reason || 'unknown') || 0) + 1)
|
|
206
|
+
}
|
|
207
|
+
for (const [reason, count] of byReason) {
|
|
208
|
+
console.log(` - ${reason}: ${count}`)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Build raw stats map for logging
|
|
213
|
+
const rawStats: Record<string, number> = {
|
|
214
|
+
sensitive_variables: stats.variables,
|
|
215
|
+
logic_gates: stats.logicGates,
|
|
216
|
+
dangerous_functions: stats.dangerousFunctions,
|
|
217
|
+
risky_imports: stats.riskyImports,
|
|
218
|
+
auth_antipatterns: stats.authAntipatterns,
|
|
219
|
+
framework_issues: stats.frameworkIssues,
|
|
220
|
+
ai_fingerprints: stats.aiFingerprints,
|
|
221
|
+
data_exposure: stats.dataExposure,
|
|
222
|
+
byok_patterns: stats.byokPatterns,
|
|
223
|
+
ai_prompt_hygiene: stats.promptHygiene,
|
|
224
|
+
ai_execution_sinks: stats.executionSinks,
|
|
225
|
+
ai_agent_tools: stats.agentTools,
|
|
226
|
+
// M5: New AI-era detectors
|
|
227
|
+
ai_rag_safety: stats.ragSafety,
|
|
228
|
+
ai_endpoint_protection: stats.endpointProtection,
|
|
229
|
+
ai_schema_validation: stats.schemaValidation,
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Compute deduped counts per category
|
|
233
|
+
const dedupedStats: Record<string, number> = {}
|
|
234
|
+
for (const vuln of uniqueVulnerabilities) {
|
|
235
|
+
const cat = vuln.category
|
|
236
|
+
dedupedStats[cat] = (dedupedStats[cat] || 0) + 1
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Compute severity distribution
|
|
240
|
+
const severityStats: Record<string, number> = { critical: 0, high: 0, medium: 0, low: 0, info: 0 }
|
|
241
|
+
for (const vuln of uniqueVulnerabilities) {
|
|
242
|
+
severityStats[vuln.severity] = (severityStats[vuln.severity] || 0) + 1
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Compute tier breakdown (all Layer 2 findings have layer: 2)
|
|
246
|
+
const tierStats = computeTierStats(
|
|
247
|
+
uniqueVulnerabilities.map(v => ({ category: v.category, layer: 2 as const }))
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
// Map raw stats keys to detector names for tier lookup
|
|
251
|
+
const detectorNameMap: Record<string, Layer2DetectorName> = {
|
|
252
|
+
sensitive_variables: 'variables',
|
|
253
|
+
logic_gates: 'logic_gates',
|
|
254
|
+
dangerous_functions: 'dangerous_functions',
|
|
255
|
+
risky_imports: 'risky_imports',
|
|
256
|
+
auth_antipatterns: 'auth_antipatterns',
|
|
257
|
+
framework_issues: 'framework_checks',
|
|
258
|
+
ai_fingerprints: 'ai_fingerprinting',
|
|
259
|
+
data_exposure: 'data_exposure',
|
|
260
|
+
byok_patterns: 'byok_patterns',
|
|
261
|
+
ai_prompt_hygiene: 'ai_prompt_hygiene',
|
|
262
|
+
ai_execution_sinks: 'ai_execution_sinks',
|
|
263
|
+
ai_agent_tools: 'ai_agent_tools',
|
|
264
|
+
// M5: New AI-era detectors
|
|
265
|
+
ai_rag_safety: 'ai_rag_safety',
|
|
266
|
+
ai_endpoint_protection: 'ai_endpoint_protection',
|
|
267
|
+
ai_schema_validation: 'ai_schema_validation',
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Log heuristic breakdown (raw findings before dedupe) with tier info
|
|
271
|
+
console.log('[Layer 2] Heuristic breakdown (raw findings before dedupe):')
|
|
272
|
+
for (const [name, count] of Object.entries(rawStats)) {
|
|
273
|
+
if (count > 0) {
|
|
274
|
+
const detectorName = detectorNameMap[name]
|
|
275
|
+
const tier = detectorName ? getLayer2DetectorTier(detectorName) : 'unknown'
|
|
276
|
+
console.log(` - ${name}: ${count} (${tier})`)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
console.log(`[Layer 2] Tier breakdown (after dedupe): ${formatTierStats(tierStats)}`)
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
vulnerabilities: uniqueVulnerabilities,
|
|
283
|
+
filesScanned: files.filter(f => isCodeFile(f.path)).length,
|
|
284
|
+
duration: Date.now() - startTime,
|
|
285
|
+
stats: {
|
|
286
|
+
raw: rawStats,
|
|
287
|
+
deduped: dedupedStats,
|
|
288
|
+
bySeverity: severityStats,
|
|
289
|
+
tiers: tierStats,
|
|
290
|
+
suppressedByPath: suppressed.length,
|
|
291
|
+
},
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Check if file is a code file (not config/data)
|
|
296
|
+
function isCodeFile(filePath: string): boolean {
|
|
297
|
+
const codeExtensions = [
|
|
298
|
+
'.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
|
|
299
|
+
'.py', '.rb', '.php', '.go', '.java', '.cs',
|
|
300
|
+
'.rs', '.swift', '.kt', '.scala',
|
|
301
|
+
]
|
|
302
|
+
|
|
303
|
+
return codeExtensions.some(ext => filePath.endsWith(ext))
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Remove duplicate findings on the same line and merge related findings
|
|
307
|
+
function deduplicateFindings(vulnerabilities: Vulnerability[]): Vulnerability[] {
|
|
308
|
+
// First pass: exact deduplication by file:line:category
|
|
309
|
+
const seen = new Map<string, Vulnerability>()
|
|
310
|
+
|
|
311
|
+
for (const vuln of vulnerabilities) {
|
|
312
|
+
const key = `${vuln.filePath}:${vuln.lineNumber}:${vuln.category}`
|
|
313
|
+
const existing = seen.get(key)
|
|
314
|
+
|
|
315
|
+
// Keep the higher severity finding
|
|
316
|
+
if (!existing || severityRank(vuln.severity) > severityRank(existing.severity)) {
|
|
317
|
+
seen.set(key, vuln)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Second pass: merge findings on adjacent lines (within 3 lines) with same category
|
|
322
|
+
const dedupedList = Array.from(seen.values())
|
|
323
|
+
const merged = mergeAdjacentFindings(dedupedList)
|
|
324
|
+
|
|
325
|
+
// Third pass: subsume related categories (e.g., specific finding subsumes generic)
|
|
326
|
+
return subsumeRelatedFindings(merged)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Merge findings on adjacent lines with the same category
|
|
330
|
+
function mergeAdjacentFindings(vulnerabilities: Vulnerability[]): Vulnerability[] {
|
|
331
|
+
if (vulnerabilities.length <= 1) return vulnerabilities
|
|
332
|
+
|
|
333
|
+
// Group by file and category
|
|
334
|
+
const groups = new Map<string, Vulnerability[]>()
|
|
335
|
+
for (const vuln of vulnerabilities) {
|
|
336
|
+
const key = `${vuln.filePath}:${vuln.category}`
|
|
337
|
+
const group = groups.get(key) || []
|
|
338
|
+
group.push(vuln)
|
|
339
|
+
groups.set(key, group)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const result: Vulnerability[] = []
|
|
343
|
+
|
|
344
|
+
for (const [, group] of groups) {
|
|
345
|
+
if (group.length === 1) {
|
|
346
|
+
result.push(group[0])
|
|
347
|
+
continue
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Sort by line number
|
|
351
|
+
group.sort((a, b) => a.lineNumber - b.lineNumber)
|
|
352
|
+
|
|
353
|
+
// Merge adjacent findings (within 3 lines)
|
|
354
|
+
let current = group[0]
|
|
355
|
+
let mergedCount = 1
|
|
356
|
+
|
|
357
|
+
for (let i = 1; i < group.length; i++) {
|
|
358
|
+
const next = group[i]
|
|
359
|
+
if (next.lineNumber - current.lineNumber <= 3) {
|
|
360
|
+
// Merge: keep higher severity, note the merge
|
|
361
|
+
mergedCount++
|
|
362
|
+
if (severityRank(next.severity) > severityRank(current.severity)) {
|
|
363
|
+
current = {
|
|
364
|
+
...next,
|
|
365
|
+
title: current.title,
|
|
366
|
+
description: current.description,
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
// Not adjacent - emit current and start new
|
|
371
|
+
if (mergedCount > 1) {
|
|
372
|
+
current = {
|
|
373
|
+
...current,
|
|
374
|
+
title: `${current.title} (${mergedCount} occurrences)`,
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
result.push(current)
|
|
378
|
+
current = next
|
|
379
|
+
mergedCount = 1
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Emit last
|
|
384
|
+
if (mergedCount > 1) {
|
|
385
|
+
current = {
|
|
386
|
+
...current,
|
|
387
|
+
title: `${current.title} (${mergedCount} occurrences)`,
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
result.push(current)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return result
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Subsume related findings where a more specific finding covers a generic one
|
|
397
|
+
function subsumeRelatedFindings(vulnerabilities: Vulnerability[]): Vulnerability[] {
|
|
398
|
+
// Define subsumption rules: specific category subsumes generic
|
|
399
|
+
const subsumptionRules: Record<string, string[]> = {
|
|
400
|
+
// SQL injection subsumes generic dangerous function
|
|
401
|
+
sql_injection: ['dangerous_function'],
|
|
402
|
+
// Command injection subsumes generic dangerous function
|
|
403
|
+
command_injection: ['dangerous_function'],
|
|
404
|
+
// XSS subsumes generic dangerous function
|
|
405
|
+
xss: ['dangerous_function'],
|
|
406
|
+
// Specific auth issues subsume generic missing auth
|
|
407
|
+
missing_auth: ['auth_antipattern'],
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Group by file and line
|
|
411
|
+
const byLocation = new Map<string, Vulnerability[]>()
|
|
412
|
+
for (const vuln of vulnerabilities) {
|
|
413
|
+
const key = `${vuln.filePath}:${vuln.lineNumber}`
|
|
414
|
+
const group = byLocation.get(key) || []
|
|
415
|
+
group.push(vuln)
|
|
416
|
+
byLocation.set(key, group)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const result: Vulnerability[] = []
|
|
420
|
+
|
|
421
|
+
for (const [, group] of byLocation) {
|
|
422
|
+
if (group.length === 1) {
|
|
423
|
+
result.push(group[0])
|
|
424
|
+
continue
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Check for subsumption
|
|
428
|
+
const toKeep = new Set(group)
|
|
429
|
+
for (const vuln of group) {
|
|
430
|
+
const subsumes = subsumptionRules[vuln.category]
|
|
431
|
+
if (subsumes) {
|
|
432
|
+
for (const other of group) {
|
|
433
|
+
if (subsumes.includes(other.category) && other !== vuln) {
|
|
434
|
+
toKeep.delete(other)
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
result.push(...toKeep)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return result
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function severityRank(severity: string): number {
|
|
447
|
+
const ranks: Record<string, number> = {
|
|
448
|
+
critical: 4,
|
|
449
|
+
high: 3,
|
|
450
|
+
medium: 2,
|
|
451
|
+
low: 1,
|
|
452
|
+
info: 0,
|
|
453
|
+
}
|
|
454
|
+
return ranks[severity] || 0
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export { detectSensitiveVariables } from './variables'
|
|
458
|
+
export { detectLogicGates } from './logic-gates'
|
|
459
|
+
export { detectDangerousFunctions } from './dangerous-functions'
|
|
460
|
+
export { detectRiskyImports } from './risky-imports'
|
|
461
|
+
export { detectAuthAntipatterns } from './auth-antipatterns'
|
|
462
|
+
export { detectFrameworkIssues } from './framework-checks'
|
|
463
|
+
export { detectAIFingerprints } from './ai-fingerprinting'
|
|
464
|
+
export { detectDataExposure } from './data-exposure'
|
|
465
|
+
export { detectBYOKPatterns } from './byok-patterns'
|
|
466
|
+
// Story B: AI-specific detectors
|
|
467
|
+
export { detectAIPromptHygiene } from './ai-prompt-hygiene'
|
|
468
|
+
export { detectAIExecutionSinks } from './ai-execution-sinks'
|
|
469
|
+
export { detectAIAgentTools } from './ai-agent-tools'
|
|
470
|
+
// M5: New AI-era detectors
|
|
471
|
+
export { detectRAGSafetyIssues } from './ai-rag-safety'
|
|
472
|
+
export { detectAIEndpointProtection } from './ai-endpoint-protection'
|
|
473
|
+
export { detectAISchemaValidation } from './ai-schema-validation'
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 2: Logic Gates Detection
|
|
3
|
+
* Identifies security bypass patterns and dangerous logic flows
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Vulnerability } from '../types'
|
|
7
|
+
|
|
8
|
+
interface LogicPattern {
|
|
9
|
+
name: string
|
|
10
|
+
pattern: RegExp
|
|
11
|
+
severity: 'critical' | 'high' | 'medium' | 'low'
|
|
12
|
+
description: string
|
|
13
|
+
suggestedFix: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Patterns for security bypass logic
|
|
17
|
+
const LOGIC_PATTERNS: LogicPattern[] = [
|
|
18
|
+
// Development mode bypasses
|
|
19
|
+
{
|
|
20
|
+
name: 'Development mode security bypass',
|
|
21
|
+
pattern: /if\s*\(\s*process\.env\.NODE_ENV\s*[!=]==?\s*['"]production['"]\s*\)\s*(return\s+true|return\s*;|continue|break)/gi,
|
|
22
|
+
severity: 'high',
|
|
23
|
+
description: 'Security check bypassed in non-production environments',
|
|
24
|
+
suggestedFix: 'Remove development-only bypasses or ensure they cannot be triggered in production',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'Development mode auth skip',
|
|
28
|
+
pattern: /if\s*\(\s*process\.env\.NODE_ENV\s*[!=]==?\s*['"]development['"]\s*\)/gi,
|
|
29
|
+
severity: 'medium',
|
|
30
|
+
description: 'Code path that only runs in development - verify no security implications',
|
|
31
|
+
suggestedFix: 'Ensure this development-only code does not bypass security controls',
|
|
32
|
+
},
|
|
33
|
+
// Auth bypasses
|
|
34
|
+
{
|
|
35
|
+
name: 'Authentication bypass pattern',
|
|
36
|
+
pattern: /if\s*\(\s*(true|1|!false)\s*\)\s*{\s*(return|next|resolve)/gi,
|
|
37
|
+
severity: 'critical',
|
|
38
|
+
description: 'Hardcoded truthy condition may bypass authentication',
|
|
39
|
+
suggestedFix: 'Remove hardcoded bypass and implement proper authentication check',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'Commented auth check',
|
|
43
|
+
pattern: /\/\/\s*(if|await|return).*auth|\/\/.*verify.*token|\/\/.*check.*permission/gi,
|
|
44
|
+
severity: 'high',
|
|
45
|
+
description: 'Commented out authentication/authorization code detected',
|
|
46
|
+
suggestedFix: 'Remove commented code or restore the security check',
|
|
47
|
+
},
|
|
48
|
+
// Skip validation patterns
|
|
49
|
+
{
|
|
50
|
+
name: 'Validation skip',
|
|
51
|
+
pattern: /skipValidation\s*[=:]\s*true|validate\s*[=:]\s*false|noValidate\s*[=:]\s*true/gi,
|
|
52
|
+
severity: 'high',
|
|
53
|
+
description: 'Input validation explicitly disabled',
|
|
54
|
+
suggestedFix: 'Enable validation or ensure this is intentional and documented',
|
|
55
|
+
},
|
|
56
|
+
// Debug/test bypasses
|
|
57
|
+
{
|
|
58
|
+
name: 'Debug bypass',
|
|
59
|
+
pattern: /if\s*\(\s*(DEBUG|TEST|SKIP_AUTH|BYPASS|DISABLE_AUTH)\s*\)/gi,
|
|
60
|
+
severity: 'high',
|
|
61
|
+
description: 'Debug/test flag may bypass security controls',
|
|
62
|
+
suggestedFix: 'Remove debug bypasses before deploying to production',
|
|
63
|
+
},
|
|
64
|
+
// Unsafe defaults
|
|
65
|
+
{
|
|
66
|
+
name: 'Unsafe default allow',
|
|
67
|
+
pattern: /default\s*:\s*(return\s+true|allow|permit|grant)/gi,
|
|
68
|
+
severity: 'medium',
|
|
69
|
+
description: 'Default case allows access - should default to deny',
|
|
70
|
+
suggestedFix: 'Change default behavior to deny access (fail-safe defaults)',
|
|
71
|
+
},
|
|
72
|
+
// Empty catch blocks
|
|
73
|
+
{
|
|
74
|
+
name: 'Empty error handler',
|
|
75
|
+
pattern: /catch\s*\([^)]*\)\s*{\s*(\/\/.*)?}/gi,
|
|
76
|
+
severity: 'medium',
|
|
77
|
+
description: 'Empty catch block may hide security errors',
|
|
78
|
+
suggestedFix: 'Log the error or handle it appropriately',
|
|
79
|
+
},
|
|
80
|
+
// Disabled security features
|
|
81
|
+
{
|
|
82
|
+
name: 'Disabled CSRF protection',
|
|
83
|
+
pattern: /csrf\s*[=:]\s*false|disableCsrf|csrfProtection\s*[=:]\s*false/gi,
|
|
84
|
+
severity: 'high',
|
|
85
|
+
description: 'CSRF protection explicitly disabled',
|
|
86
|
+
suggestedFix: 'Enable CSRF protection for state-changing requests',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'Disabled SSL verification',
|
|
90
|
+
pattern: /rejectUnauthorized\s*[=:]\s*false|verify\s*[=:]\s*false|ssl\s*[=:]\s*false|NODE_TLS_REJECT_UNAUTHORIZED/gi,
|
|
91
|
+
severity: 'critical',
|
|
92
|
+
description: 'SSL/TLS certificate verification disabled',
|
|
93
|
+
suggestedFix: 'Enable SSL verification to prevent man-in-the-middle attacks',
|
|
94
|
+
},
|
|
95
|
+
// Insecure comparisons
|
|
96
|
+
{
|
|
97
|
+
name: 'Timing attack vulnerable comparison',
|
|
98
|
+
pattern: /===?\s*['"][^'"]{20,}['"]|password\s*===?\s*|token\s*===?\s*|secret\s*===?\s*/gi,
|
|
99
|
+
severity: 'medium',
|
|
100
|
+
description: 'Direct string comparison may be vulnerable to timing attacks',
|
|
101
|
+
suggestedFix: 'Use constant-time comparison for secrets (e.g., crypto.timingSafeEqual)',
|
|
102
|
+
},
|
|
103
|
+
// Unsafe redirects
|
|
104
|
+
{
|
|
105
|
+
name: 'Open redirect vulnerability',
|
|
106
|
+
pattern: /redirect\s*\(\s*(req\.(query|params|body)\.|request\.|url\.)/gi,
|
|
107
|
+
severity: 'high',
|
|
108
|
+
description: 'Redirect URL from user input may allow open redirect attacks',
|
|
109
|
+
suggestedFix: 'Validate redirect URLs against an allowlist of trusted domains',
|
|
110
|
+
},
|
|
111
|
+
// Admin/superuser bypasses
|
|
112
|
+
{
|
|
113
|
+
name: 'Admin bypass pattern',
|
|
114
|
+
pattern: /if\s*\(\s*(isAdmin|isSuperUser|isRoot|role\s*===?\s*['"]admin['"])\s*\)\s*(return|continue|break)/gi,
|
|
115
|
+
severity: 'medium',
|
|
116
|
+
description: 'Admin role bypasses normal security checks',
|
|
117
|
+
suggestedFix: 'Ensure admin bypass is intentional and properly audited',
|
|
118
|
+
},
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
// Check if line is a comment
|
|
122
|
+
function isComment(line: string): boolean {
|
|
123
|
+
const trimmed = line.trim()
|
|
124
|
+
return (
|
|
125
|
+
trimmed.startsWith('//') ||
|
|
126
|
+
trimmed.startsWith('#') ||
|
|
127
|
+
trimmed.startsWith('*') ||
|
|
128
|
+
trimmed.startsWith('/*')
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function detectLogicGates(
|
|
133
|
+
content: string,
|
|
134
|
+
filePath: string
|
|
135
|
+
): Vulnerability[] {
|
|
136
|
+
const vulnerabilities: Vulnerability[] = []
|
|
137
|
+
const lines = content.split('\n')
|
|
138
|
+
|
|
139
|
+
// Check each line against patterns
|
|
140
|
+
lines.forEach((line, index) => {
|
|
141
|
+
// Don't skip comments for the "commented auth check" pattern
|
|
142
|
+
const shouldSkipComments = !line.trim().startsWith('//')
|
|
143
|
+
|
|
144
|
+
for (const logicPattern of LOGIC_PATTERNS) {
|
|
145
|
+
// Skip comment lines for most patterns
|
|
146
|
+
if (shouldSkipComments && isComment(line) &&
|
|
147
|
+
logicPattern.name !== 'Commented auth check') {
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const regex = new RegExp(logicPattern.pattern.source, logicPattern.pattern.flags)
|
|
152
|
+
|
|
153
|
+
if (regex.test(line)) {
|
|
154
|
+
vulnerabilities.push({
|
|
155
|
+
id: `logic-${filePath}-${index + 1}-${logicPattern.name}`,
|
|
156
|
+
filePath,
|
|
157
|
+
lineNumber: index + 1,
|
|
158
|
+
lineContent: line.trim(),
|
|
159
|
+
severity: logicPattern.severity,
|
|
160
|
+
category: 'security_bypass',
|
|
161
|
+
title: logicPattern.name,
|
|
162
|
+
description: logicPattern.description,
|
|
163
|
+
suggestedFix: logicPattern.suggestedFix,
|
|
164
|
+
confidence: 'medium',
|
|
165
|
+
layer: 2,
|
|
166
|
+
})
|
|
167
|
+
break // Only report once per line
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// Multi-line pattern detection (for more complex patterns)
|
|
173
|
+
const multiLineFindings = detectMultiLinePatterns(content, filePath)
|
|
174
|
+
vulnerabilities.push(...multiLineFindings)
|
|
175
|
+
|
|
176
|
+
return vulnerabilities
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Detect patterns that span multiple lines
|
|
180
|
+
function detectMultiLinePatterns(content: string, filePath: string): Vulnerability[] {
|
|
181
|
+
const vulnerabilities: Vulnerability[] = []
|
|
182
|
+
const lines = content.split('\n')
|
|
183
|
+
|
|
184
|
+
// Detect try-catch with empty or minimal error handling
|
|
185
|
+
const tryCatchPattern = /try\s*{[\s\S]*?}\s*catch\s*\([^)]*\)\s*{\s*(\n\s*)*(\/\/[^\n]*)?\s*}/g
|
|
186
|
+
let match
|
|
187
|
+
|
|
188
|
+
while ((match = tryCatchPattern.exec(content)) !== null) {
|
|
189
|
+
const lineNumber = content.substring(0, match.index).split('\n').length
|
|
190
|
+
vulnerabilities.push({
|
|
191
|
+
id: `logic-multiline-${filePath}-${lineNumber}`,
|
|
192
|
+
filePath,
|
|
193
|
+
lineNumber,
|
|
194
|
+
lineContent: lines[lineNumber - 1]?.trim() || 'try {',
|
|
195
|
+
severity: 'medium',
|
|
196
|
+
category: 'security_bypass',
|
|
197
|
+
title: 'Silent error handling',
|
|
198
|
+
description: 'Try-catch block with minimal error handling may hide security issues',
|
|
199
|
+
suggestedFix: 'Log errors appropriately and handle them based on type',
|
|
200
|
+
confidence: 'low',
|
|
201
|
+
layer: 2,
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return vulnerabilities
|
|
206
|
+
}
|