@rigour-labs/core 5.0.1 → 5.1.1
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/README.md +9 -1
- package/dist/gates/agent-team.d.ts +0 -1
- package/dist/gates/agent-team.js +0 -1
- package/dist/gates/checkpoint.d.ts +0 -2
- package/dist/gates/checkpoint.js +0 -2
- package/dist/gates/context-window-artifacts.d.ts +6 -2
- package/dist/gates/context-window-artifacts.js +107 -31
- package/dist/gates/deep-analysis.d.ts +2 -0
- package/dist/gates/deep-analysis.js +41 -11
- package/dist/gates/dependency.d.ts +0 -2
- package/dist/gates/dependency.js +23 -5
- package/dist/gates/deprecated-apis.d.ts +0 -2
- package/dist/gates/deprecated-apis.js +33 -20
- package/dist/gates/duplication-drift/index.d.ts +61 -0
- package/dist/gates/duplication-drift/index.js +240 -0
- package/dist/gates/duplication-drift/similarity.d.ts +68 -0
- package/dist/gates/duplication-drift/similarity.js +177 -0
- package/dist/gates/duplication-drift/tokenizer.d.ts +55 -0
- package/dist/gates/duplication-drift/tokenizer.js +195 -0
- package/dist/gates/frontend-secret-exposure.d.ts +0 -3
- package/dist/gates/frontend-secret-exposure.js +1 -114
- package/dist/gates/frontend-secret-patterns.d.ts +33 -0
- package/dist/gates/frontend-secret-patterns.js +119 -0
- package/dist/gates/{hallucinated-imports.d.ts → hallucinated-imports/index.d.ts} +2 -29
- package/dist/gates/hallucinated-imports/index.js +174 -0
- package/dist/gates/hallucinated-imports/js-resolver.d.ts +45 -0
- package/dist/gates/hallucinated-imports/js-resolver.js +320 -0
- package/dist/gates/hallucinated-imports/manifest-discovery.d.ts +28 -0
- package/dist/gates/hallucinated-imports/manifest-discovery.js +114 -0
- package/dist/gates/hallucinated-imports/python-resolver.d.ts +24 -0
- package/dist/gates/hallucinated-imports/python-resolver.js +306 -0
- package/dist/gates/hallucinated-imports-lang.d.ts +2 -2
- package/dist/gates/hallucinated-imports-lang.js +269 -34
- package/dist/gates/hallucinated-imports.test.js +1 -2
- package/dist/gates/inconsistent-error-handling.d.ts +0 -5
- package/dist/gates/inconsistent-error-handling.js +15 -144
- package/dist/gates/language-adapters/csharp-adapter.d.ts +16 -0
- package/dist/gates/language-adapters/csharp-adapter.js +211 -0
- package/dist/gates/language-adapters/go-adapter.d.ts +26 -0
- package/dist/gates/language-adapters/go-adapter.js +195 -0
- package/dist/gates/language-adapters/index.d.ts +15 -0
- package/dist/gates/language-adapters/index.js +16 -0
- package/dist/gates/language-adapters/java-adapter.d.ts +16 -0
- package/dist/gates/language-adapters/java-adapter.js +237 -0
- package/dist/gates/language-adapters/js-adapter.d.ts +26 -0
- package/dist/gates/language-adapters/js-adapter.js +279 -0
- package/dist/gates/language-adapters/python-adapter.d.ts +25 -0
- package/dist/gates/language-adapters/python-adapter.js +183 -0
- package/dist/gates/language-adapters/registry.d.ts +26 -0
- package/dist/gates/language-adapters/registry.js +65 -0
- package/dist/gates/language-adapters/ruby-adapter.d.ts +25 -0
- package/dist/gates/language-adapters/ruby-adapter.js +217 -0
- package/dist/gates/language-adapters/rust-adapter.d.ts +27 -0
- package/dist/gates/language-adapters/rust-adapter.js +235 -0
- package/dist/gates/language-adapters/types.d.ts +60 -0
- package/dist/gates/language-adapters/types.js +22 -0
- package/dist/gates/logic-drift-extractors.d.ts +15 -0
- package/dist/gates/logic-drift-extractors.js +34 -0
- package/dist/gates/logic-drift.d.ts +0 -30
- package/dist/gates/logic-drift.js +39 -129
- package/dist/gates/phantom-apis.d.ts +0 -2
- package/dist/gates/phantom-apis.js +49 -20
- package/dist/gates/promise-safety.d.ts +0 -1
- package/dist/gates/promise-safety.js +14 -2
- package/dist/gates/runner.js +52 -23
- package/dist/gates/runner.test.js +1 -1
- package/dist/gates/security-patterns-data.d.ts +14 -0
- package/dist/gates/security-patterns-data.js +235 -0
- package/dist/gates/security-patterns.d.ts +17 -3
- package/dist/gates/security-patterns.js +80 -211
- package/dist/gates/side-effect-analysis/categorizer.d.ts +32 -0
- package/dist/gates/side-effect-analysis/categorizer.js +83 -0
- package/dist/gates/{side-effect-analysis.d.ts → side-effect-analysis/index.d.ts} +3 -5
- package/dist/gates/{side-effect-analysis.js → side-effect-analysis/index.js} +33 -45
- package/dist/gates/side-effect-analysis/scope-tracker.d.ts +37 -0
- package/dist/gates/side-effect-analysis/scope-tracker.js +40 -0
- package/dist/gates/side-effect-helpers/index.d.ts +4 -0
- package/dist/gates/side-effect-helpers/index.js +4 -0
- package/dist/gates/side-effect-helpers/pattern-detection.d.ts +123 -0
- package/dist/gates/{side-effect-helpers.js → side-effect-helpers/pattern-detection.js} +22 -468
- package/dist/gates/side-effect-helpers/resource-tracking.d.ts +80 -0
- package/dist/gates/side-effect-helpers/resource-tracking.js +281 -0
- package/dist/gates/side-effect-helpers/scope-analysis.d.ts +21 -0
- package/dist/gates/side-effect-helpers/scope-analysis.js +146 -0
- package/dist/gates/side-effect-helpers/types.d.ts +38 -0
- package/dist/gates/side-effect-helpers/types.js +41 -0
- package/dist/gates/side-effect-rules.d.ts +0 -1
- package/dist/gates/side-effect-rules.js +0 -1
- package/dist/gates/style-drift-rules.d.ts +86 -0
- package/dist/gates/style-drift-rules.js +103 -0
- package/dist/gates/style-drift.d.ts +7 -16
- package/dist/gates/style-drift.js +101 -119
- package/dist/gates/test-quality-matchers.d.ts +53 -0
- package/dist/gates/test-quality-matchers.js +86 -0
- package/dist/gates/test-quality.d.ts +0 -3
- package/dist/gates/test-quality.js +47 -44
- package/dist/hooks/checker.d.ts +0 -1
- package/dist/hooks/checker.js +0 -2
- package/dist/hooks/dlp-templates.d.ts +0 -1
- package/dist/hooks/dlp-templates.js +0 -4
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/index.js +0 -2
- package/dist/hooks/input-validator.d.ts +0 -1
- package/dist/hooks/input-validator.js +0 -1
- package/dist/hooks/input-validator.test.js +0 -1
- package/dist/hooks/standalone-checker.d.ts +0 -1
- package/dist/hooks/standalone-checker.js +0 -1
- package/dist/hooks/standalone-dlp-checker.d.ts +0 -1
- package/dist/hooks/standalone-dlp-checker.js +0 -1
- package/dist/hooks/templates.d.ts +0 -1
- package/dist/hooks/templates.js +0 -1
- package/dist/hooks/types.d.ts +0 -1
- package/dist/hooks/types.js +0 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/inference/index.js +1 -1
- package/dist/services/adaptive-thresholds.d.ts +0 -2
- package/dist/services/adaptive-thresholds.js +0 -2
- package/dist/services/filesystem-cache.d.ts +0 -1
- package/dist/services/filesystem-cache.js +0 -1
- package/dist/services/score-history.d.ts +0 -1
- package/dist/services/score-history.js +0 -1
- package/dist/services/temporal-drift.d.ts +1 -2
- package/dist/services/temporal-drift.js +7 -8
- package/dist/storage/db.d.ts +23 -7
- package/dist/storage/db.js +116 -55
- package/dist/storage/findings.d.ts +4 -3
- package/dist/storage/findings.js +13 -20
- package/dist/storage/local-memory.d.ts +4 -4
- package/dist/storage/local-memory.js +20 -22
- package/dist/storage/patterns.d.ts +5 -5
- package/dist/storage/patterns.js +20 -26
- package/dist/storage/scans.d.ts +6 -6
- package/dist/storage/scans.js +12 -21
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/scanner.js +1 -1
- package/package.json +7 -8
- package/dist/gates/duplication-drift.d.ts +0 -128
- package/dist/gates/duplication-drift.js +0 -585
- package/dist/gates/hallucinated-imports.js +0 -641
- package/dist/gates/side-effect-helpers.d.ts +0 -260
|
@@ -11,219 +11,13 @@
|
|
|
11
11
|
* - Hardcoded Secrets
|
|
12
12
|
* - Insecure Randomness
|
|
13
13
|
* - Command Injection
|
|
14
|
-
*
|
|
15
|
-
* @since v2.14.0
|
|
16
14
|
*/
|
|
17
15
|
import { Gate } from './base.js';
|
|
18
16
|
import { FileScanner } from '../utils/scanner.js';
|
|
19
17
|
import { Logger } from '../utils/logger.js';
|
|
18
|
+
import { VULNERABILITY_PATTERNS } from './security-patterns-data.js';
|
|
20
19
|
import fs from 'fs-extra';
|
|
21
20
|
import path from 'path';
|
|
22
|
-
// Pattern definitions with regex and metadata
|
|
23
|
-
const VULNERABILITY_PATTERNS = [
|
|
24
|
-
// SQL Injection
|
|
25
|
-
{
|
|
26
|
-
type: 'sql_injection',
|
|
27
|
-
regex: /(?:execute|query|raw|exec)\s*\(\s*`[^`]*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|WITH)[^`]*\$\{[^}]+\}[^`]*`/gi,
|
|
28
|
-
severity: 'critical',
|
|
29
|
-
description: 'Potential SQL injection: User input concatenated into SQL query',
|
|
30
|
-
cwe: 'CWE-89',
|
|
31
|
-
languages: ['ts', 'js', 'py']
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
type: 'sql_injection',
|
|
35
|
-
regex: /(?:execute|query|raw|exec)\s*\(\s*['"`][^'"`]*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|WITH)[^'"`]*['"`]\s*\+\s*[^)]+\)/gi,
|
|
36
|
-
severity: 'critical',
|
|
37
|
-
description: 'SQL query built with string concatenation',
|
|
38
|
-
cwe: 'CWE-89',
|
|
39
|
-
languages: ['ts', 'js']
|
|
40
|
-
},
|
|
41
|
-
// XSS
|
|
42
|
-
{
|
|
43
|
-
type: 'xss',
|
|
44
|
-
regex: /innerHTML\s*=\s*(?!\s*['"`]\s*['"`])[^;]+/g,
|
|
45
|
-
severity: 'high',
|
|
46
|
-
description: 'Potential XSS: innerHTML assignment with dynamic content',
|
|
47
|
-
cwe: 'CWE-79',
|
|
48
|
-
languages: ['ts', 'js', 'tsx', 'jsx']
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
type: 'xss',
|
|
52
|
-
regex: /dangerouslySetInnerHTML\s*=\s*\{/g,
|
|
53
|
-
severity: 'high',
|
|
54
|
-
description: 'dangerouslySetInnerHTML usage (ensure content is sanitized)',
|
|
55
|
-
cwe: 'CWE-79',
|
|
56
|
-
languages: ['tsx', 'jsx']
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
type: 'xss',
|
|
60
|
-
regex: /document\.write\s*\(/g,
|
|
61
|
-
severity: 'high',
|
|
62
|
-
description: 'document.write is dangerous for XSS',
|
|
63
|
-
cwe: 'CWE-79',
|
|
64
|
-
languages: ['ts', 'js']
|
|
65
|
-
},
|
|
66
|
-
// Path Traversal
|
|
67
|
-
{
|
|
68
|
-
type: 'path_traversal',
|
|
69
|
-
regex: /(?:readFile|writeFile|readdir|unlink|rmdir)\s*\([^)]*(?:req\.(?:params|query|body)|\.\.\/)/g,
|
|
70
|
-
severity: 'high',
|
|
71
|
-
description: 'Potential path traversal: File operation with user input',
|
|
72
|
-
cwe: 'CWE-22',
|
|
73
|
-
languages: ['ts', 'js']
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
type: 'path_traversal',
|
|
77
|
-
regex: /path\.join\s*\([^)]*req\./g,
|
|
78
|
-
severity: 'medium',
|
|
79
|
-
description: 'path.join with request data (verify input sanitization)',
|
|
80
|
-
cwe: 'CWE-22',
|
|
81
|
-
languages: ['ts', 'js']
|
|
82
|
-
},
|
|
83
|
-
// Hardcoded Secrets
|
|
84
|
-
{
|
|
85
|
-
type: 'hardcoded_secrets',
|
|
86
|
-
regex: /(?:password|secret|api_key|apikey|auth_token|access_token|private_key)\s*[:=]\s*['"][^'"]{8,}['"]/gi,
|
|
87
|
-
severity: 'critical',
|
|
88
|
-
description: 'Hardcoded secret detected in code',
|
|
89
|
-
cwe: 'CWE-798',
|
|
90
|
-
languages: ['ts', 'js', 'py', 'java', 'go']
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
type: 'hardcoded_secrets',
|
|
94
|
-
regex: /(?:sk-|pk-|rk-|ghp_|gho_|ghu_|ghs_|ghr_)[a-zA-Z0-9]{20,}/g,
|
|
95
|
-
severity: 'critical',
|
|
96
|
-
description: 'API key pattern detected (OpenAI, GitHub, etc.)',
|
|
97
|
-
cwe: 'CWE-798',
|
|
98
|
-
languages: ['*']
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
type: 'hardcoded_secrets',
|
|
102
|
-
regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
|
|
103
|
-
severity: 'critical',
|
|
104
|
-
description: 'Private key embedded in source code',
|
|
105
|
-
cwe: 'CWE-798',
|
|
106
|
-
languages: ['*']
|
|
107
|
-
},
|
|
108
|
-
// Insecure Randomness
|
|
109
|
-
{
|
|
110
|
-
type: 'insecure_randomness',
|
|
111
|
-
regex: /Math\.random\s*\(\s*\)/g,
|
|
112
|
-
severity: 'medium',
|
|
113
|
-
description: 'Math.random() is not cryptographically secure',
|
|
114
|
-
cwe: 'CWE-338',
|
|
115
|
-
languages: ['ts', 'js', 'tsx', 'jsx']
|
|
116
|
-
},
|
|
117
|
-
// Command Injection
|
|
118
|
-
{
|
|
119
|
-
type: 'command_injection',
|
|
120
|
-
regex: /(?:exec|execSync|spawn|spawnSync)\s*\(\s*(?:`[^`]*\$\{[^}]*(?:req\.|query|params|body|input|user|argv|process\.env)[^}]*\}[^`]*`|[^)]*(?:req\.|query|params|body|input|user|argv|process\.env)[^)]*)\)/g,
|
|
121
|
-
severity: 'critical',
|
|
122
|
-
description: 'Potential command injection: shell execution with user input',
|
|
123
|
-
cwe: 'CWE-78',
|
|
124
|
-
languages: ['ts', 'js']
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
type: 'command_injection',
|
|
128
|
-
regex: /child_process.*\s*\.\s*(?:exec|spawn)\s*\([^)]*(?:req\.|query|params|body|input|user|argv|process\.env)/g,
|
|
129
|
-
severity: 'high',
|
|
130
|
-
description: 'child_process usage detected (verify input sanitization)',
|
|
131
|
-
cwe: 'CWE-78',
|
|
132
|
-
languages: ['ts', 'js']
|
|
133
|
-
},
|
|
134
|
-
// ReDoS — Denial of Service via regex (OWASP #7)
|
|
135
|
-
{
|
|
136
|
-
type: 'redos',
|
|
137
|
-
regex: /new RegExp\s*\([^)]*(?:req\.|params|query|body|input|user)/g,
|
|
138
|
-
severity: 'high',
|
|
139
|
-
description: 'Dynamic regex from user input — potential ReDoS',
|
|
140
|
-
cwe: 'CWE-1333',
|
|
141
|
-
languages: ['ts', 'js']
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
type: 'redos',
|
|
145
|
-
regex: /\(\?:[^)]*\+[^)]*\)\+|\([^)]*\*[^)]*\)\+|\(\.\*\)\{/g,
|
|
146
|
-
severity: 'medium',
|
|
147
|
-
description: 'Regex with nested quantifiers — potential ReDoS',
|
|
148
|
-
cwe: 'CWE-1333',
|
|
149
|
-
languages: ['ts', 'js', 'py']
|
|
150
|
-
},
|
|
151
|
-
// Overly Permissive Code (OWASP #9)
|
|
152
|
-
{
|
|
153
|
-
type: 'overly_permissive',
|
|
154
|
-
regex: /cors\s*\(\s*\{[^}]*origin\s*:\s*(?:true|['"`]\*['"`])/g,
|
|
155
|
-
severity: 'high',
|
|
156
|
-
description: 'CORS wildcard origin — allows any domain',
|
|
157
|
-
cwe: 'CWE-942',
|
|
158
|
-
languages: ['ts', 'js']
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
type: 'overly_permissive',
|
|
162
|
-
regex: /(?:listen|bind)\s*\(\s*(?:\d+\s*,\s*)?['"`]0\.0\.0\.0['"`]/g,
|
|
163
|
-
severity: 'medium',
|
|
164
|
-
description: 'Binding to 0.0.0.0 exposes service to all interfaces',
|
|
165
|
-
cwe: 'CWE-668',
|
|
166
|
-
languages: ['ts', 'js', 'py', 'go']
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
type: 'overly_permissive',
|
|
170
|
-
regex: /chmod\s*\(\s*[^,]*,\s*['"`]?(?:0o?)?777['"`]?\s*\)/g,
|
|
171
|
-
severity: 'high',
|
|
172
|
-
description: 'chmod 777 — world-readable/writable permissions',
|
|
173
|
-
cwe: 'CWE-732',
|
|
174
|
-
languages: ['ts', 'js', 'py']
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
type: 'overly_permissive',
|
|
178
|
-
regex: /(?:Access-Control-Allow-Origin|x-powered-by)['"`,\s:]+\*/gi,
|
|
179
|
-
severity: 'high',
|
|
180
|
-
description: 'Wildcard Access-Control-Allow-Origin header',
|
|
181
|
-
cwe: 'CWE-942',
|
|
182
|
-
languages: ['ts', 'js', 'py']
|
|
183
|
-
},
|
|
184
|
-
// Unsafe Output Handling (OWASP #6)
|
|
185
|
-
{
|
|
186
|
-
type: 'unsafe_output',
|
|
187
|
-
regex: /res\.(?:send|write|end)\s*\(\s*(?:req\.|params|query|body|input|user)/g,
|
|
188
|
-
severity: 'high',
|
|
189
|
-
description: 'Reflecting user input in response without sanitization',
|
|
190
|
-
cwe: 'CWE-79',
|
|
191
|
-
languages: ['ts', 'js']
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
type: 'unsafe_output',
|
|
195
|
-
regex: /\$\{[^}]*(?:req\.|params|query|body|input|user)[^}]*\}.*(?:html|template|render)/gi,
|
|
196
|
-
severity: 'high',
|
|
197
|
-
description: 'User input interpolated into template/HTML output',
|
|
198
|
-
cwe: 'CWE-79',
|
|
199
|
-
languages: ['ts', 'js', 'py']
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
type: 'unsafe_output',
|
|
203
|
-
regex: /eval\s*\(\s*(?:req\.|params|query|body|input|user)/g,
|
|
204
|
-
severity: 'critical',
|
|
205
|
-
description: 'eval() with user input — code injection',
|
|
206
|
-
cwe: 'CWE-94',
|
|
207
|
-
languages: ['ts', 'js', 'py']
|
|
208
|
-
},
|
|
209
|
-
// Missing Input Validation (OWASP #8)
|
|
210
|
-
{
|
|
211
|
-
type: 'missing_input_validation',
|
|
212
|
-
regex: /JSON\.parse\s*\(\s*(?:req\.body|request\.body|body|data|input)\s*\)/g,
|
|
213
|
-
severity: 'medium',
|
|
214
|
-
description: 'JSON.parse on raw input without schema validation',
|
|
215
|
-
cwe: 'CWE-20',
|
|
216
|
-
languages: ['ts', 'js']
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
type: 'missing_input_validation',
|
|
220
|
-
regex: /(?:as\s+any|:\s*any)\s*(?:[;,)\]}])/g,
|
|
221
|
-
severity: 'medium',
|
|
222
|
-
description: 'Type assertion to "any" bypasses type safety',
|
|
223
|
-
cwe: 'CWE-20',
|
|
224
|
-
languages: ['ts']
|
|
225
|
-
},
|
|
226
|
-
];
|
|
227
21
|
export class SecurityPatternsGate extends Gate {
|
|
228
22
|
config;
|
|
229
23
|
severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
@@ -347,6 +141,14 @@ export class SecurityPatternsGate extends Gate {
|
|
|
347
141
|
if (pattern.type === 'hardcoded_secrets' && this.isDummySecretValue(match[0])) {
|
|
348
142
|
continue;
|
|
349
143
|
}
|
|
144
|
+
// For XSS: check if innerHTML/dangerouslySetInnerHTML is wrapped in a sanitizer
|
|
145
|
+
if (pattern.type === 'xss' && this.isSanitizedAssignment(match[0])) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// For command_injection: check if spawn/exec uses { shell: false } (safe)
|
|
149
|
+
if (pattern.type === 'command_injection' && this.isSafeShellCall(match[0], content, match.index)) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
350
152
|
// Find line number
|
|
351
153
|
const beforeMatch = content.slice(0, match.index);
|
|
352
154
|
const lineNumber = beforeMatch.split('\n').length;
|
|
@@ -364,7 +166,8 @@ export class SecurityPatternsGate extends Gate {
|
|
|
364
166
|
}
|
|
365
167
|
/**
|
|
366
168
|
* Check if a hardcoded secret match is actually a dummy/placeholder value.
|
|
367
|
-
* Filters out test values, placeholder defaults,
|
|
169
|
+
* Filters out test values, placeholder defaults, env-var-name assignments,
|
|
170
|
+
* store action types, and low-entropy constants.
|
|
368
171
|
*/
|
|
369
172
|
isDummySecretValue(matchText) {
|
|
370
173
|
// Extract the quoted value from the match (e.g., api_key="test-api-key" → test-api-key)
|
|
@@ -380,13 +183,79 @@ export class SecurityPatternsGate extends Gate {
|
|
|
380
183
|
return true;
|
|
381
184
|
if (/^testpass(?:word)?$/i.test(value))
|
|
382
185
|
return true;
|
|
383
|
-
// All-caps with underscores = env var names, not actual secrets
|
|
384
|
-
// e.g., API_KEY = "OPEN_SANDBOX_API_KEY"
|
|
385
|
-
if (/^[A-Z][A-Z0-9_]{7,}$/.test(value))
|
|
186
|
+
// All-caps with underscores/dollars = env var names or constants, not actual secrets
|
|
187
|
+
// e.g., API_KEY = "OPEN_SANDBOX_API_KEY", SECRETS$ADD_SECRET (Redux action types)
|
|
188
|
+
if (/^[A-Z][A-Z0-9_$]{7,}$/.test(value))
|
|
189
|
+
return true;
|
|
190
|
+
// Store action type patterns: NAMESPACE$ACTION or namespace/ACTION (Redux, Zustand, Flux)
|
|
191
|
+
if (/^\w+\$\w+$/.test(value))
|
|
192
|
+
return true;
|
|
193
|
+
if (/^[a-z][\w-]*\/[A-Z_]+$/.test(value))
|
|
386
194
|
return true;
|
|
387
195
|
// Common documentation/tutorial dummy values
|
|
388
196
|
if (/^(?:sk_test_|pk_test_|sk_live_xxx|password123|secret123|abcdef|abc123)/i.test(value))
|
|
389
197
|
return true;
|
|
198
|
+
// Shannon entropy check: low-entropy values are likely constants, not real secrets
|
|
199
|
+
// Real secrets have high entropy (>4.0 bits/char); constants and names have low entropy
|
|
200
|
+
if (this.shannonEntropy(value) < 3.0)
|
|
201
|
+
return true;
|
|
202
|
+
// ALL_CAPS_SNAKE_CASE without any lowercase/special chars = likely enum or constant
|
|
203
|
+
if (/^[A-Z][A-Z0-9_$]*$/.test(value) && value.length >= 6)
|
|
204
|
+
return true;
|
|
205
|
+
// URL-like values that are just config (not secrets): localhost, 127.0.0.1, etc.
|
|
206
|
+
if (/^(?:https?:\/\/)?(?:localhost|127\.0\.0\.1|0\.0\.0\.0)/.test(value))
|
|
207
|
+
return true;
|
|
208
|
+
// Common non-secret patterns: UUIDs, semver, file paths
|
|
209
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value))
|
|
210
|
+
return true;
|
|
211
|
+
if (/^\d+\.\d+\.\d+/.test(value))
|
|
212
|
+
return true; // semver
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Calculate Shannon entropy (bits per character) of a string.
|
|
217
|
+
* Real secrets have high entropy (>4.5); constants/names have low entropy (<3.0).
|
|
218
|
+
*/
|
|
219
|
+
shannonEntropy(str) {
|
|
220
|
+
if (!str || str.length === 0)
|
|
221
|
+
return 0;
|
|
222
|
+
const freq = new Map();
|
|
223
|
+
for (const ch of str) {
|
|
224
|
+
freq.set(ch, (freq.get(ch) || 0) + 1);
|
|
225
|
+
}
|
|
226
|
+
let entropy = 0;
|
|
227
|
+
for (const count of freq.values()) {
|
|
228
|
+
const p = count / str.length;
|
|
229
|
+
if (p > 0)
|
|
230
|
+
entropy -= p * Math.log2(p);
|
|
231
|
+
}
|
|
232
|
+
return entropy;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Check if an innerHTML or dangerouslySetInnerHTML assignment is wrapped in a sanitizer.
|
|
236
|
+
* Known sanitizers: DOMPurify.sanitize(), sanitize(), xss(), escapeHtml(), htmlEncode().
|
|
237
|
+
*/
|
|
238
|
+
isSanitizedAssignment(matchText) {
|
|
239
|
+
const sanitizers = [
|
|
240
|
+
'sanitize(', 'DOMPurify.sanitize(', 'dompurify.sanitize(',
|
|
241
|
+
'xss(', 'escape(', 'escapeHtml(', 'htmlEncode(',
|
|
242
|
+
'sanitizeHtml(', 'clean(', 'purify(',
|
|
243
|
+
];
|
|
244
|
+
const rhs = matchText.includes('=') ? matchText.split('=').slice(1).join('=') : matchText;
|
|
245
|
+
return sanitizers.some(s => rhs.toLowerCase().includes(s.toLowerCase()));
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Check if a shell execution call is using { shell: false } option (safe pattern).
|
|
249
|
+
* Also, spawn() without { shell: true } is safe by default — don't flag it.
|
|
250
|
+
*/
|
|
251
|
+
isSafeShellCall(matchText, fullContent, matchIndex) {
|
|
252
|
+
// Check surrounding context (100 chars after match) for shell: false
|
|
253
|
+
const context = fullContent.slice(matchIndex, matchIndex + matchText.length + 100);
|
|
254
|
+
if (/shell\s*:\s*false/.test(context))
|
|
255
|
+
return true;
|
|
256
|
+
// spawn() without shell: true is safe by default
|
|
257
|
+
if (/\bspawn(?:Sync)?\s*\(/.test(matchText) && !/shell\s*:\s*true/.test(context))
|
|
258
|
+
return true;
|
|
390
259
|
return false;
|
|
391
260
|
}
|
|
392
261
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Side Effect Categorization and Severity Assignment
|
|
3
|
+
*
|
|
4
|
+
* Functions that classify side effects by type/severity and assign
|
|
5
|
+
* appropriate titles and descriptions.
|
|
6
|
+
*/
|
|
7
|
+
import { SideEffectViolation } from '../side-effect-helpers/index.js';
|
|
8
|
+
import { Failure } from '../../types/index.js';
|
|
9
|
+
/**
|
|
10
|
+
* Gets the human-readable title for a rule
|
|
11
|
+
*/
|
|
12
|
+
export declare function getRuleTitle(rule: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Gets the severity level for a rule
|
|
15
|
+
*/
|
|
16
|
+
export declare function getRuleSeverity(rule: string): 'low' | 'medium' | 'high' | 'critical';
|
|
17
|
+
/**
|
|
18
|
+
* Categorizes a violation and assigns it proper severity and title
|
|
19
|
+
*/
|
|
20
|
+
export declare function categorizeViolation(violation: SideEffectViolation): SideEffectViolation;
|
|
21
|
+
/**
|
|
22
|
+
* Converts a side effect violation to a Failure object
|
|
23
|
+
*/
|
|
24
|
+
export declare function violationToFailure(violation: SideEffectViolation, createFailure: (message: string, files: string[], hint?: string, title?: string, startLine?: number, endLine?: number, severity?: string) => Failure): Failure;
|
|
25
|
+
/**
|
|
26
|
+
* Groups violations by severity level
|
|
27
|
+
*/
|
|
28
|
+
export declare function groupBySeverity(violations: SideEffectViolation[]): Record<string, SideEffectViolation[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Filters violations by severity threshold
|
|
31
|
+
*/
|
|
32
|
+
export declare function filterBySeverity(violations: SideEffectViolation[], minSeverity: 'critical' | 'high' | 'medium' | 'low'): SideEffectViolation[];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Side Effect Categorization and Severity Assignment
|
|
3
|
+
*
|
|
4
|
+
* Functions that classify side effects by type/severity and assign
|
|
5
|
+
* appropriate titles and descriptions.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Categorization information for each rule type
|
|
9
|
+
*/
|
|
10
|
+
const RULE_TITLES = {
|
|
11
|
+
'unbounded-timer': 'Unbounded Timer',
|
|
12
|
+
'orphan-process': 'Orphan Process',
|
|
13
|
+
'unbounded-io-loop': 'Unbounded I/O Loop',
|
|
14
|
+
'retry-without-limit': 'Retry Without Limit',
|
|
15
|
+
'circular-trigger': 'Circular File Trigger',
|
|
16
|
+
'resource-leak': 'Resource Leak',
|
|
17
|
+
'unbounded-recursion': 'Unbounded Recursion',
|
|
18
|
+
'auto-restart-bomb': 'Auto-Restart Bomb',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Maps rule IDs to their severity categories
|
|
22
|
+
*/
|
|
23
|
+
const RULE_SEVERITIES = {
|
|
24
|
+
'unbounded-timer': 'high',
|
|
25
|
+
'orphan-process': 'high',
|
|
26
|
+
'unbounded-io-loop': 'critical',
|
|
27
|
+
'retry-without-limit': 'high',
|
|
28
|
+
'circular-trigger': 'critical',
|
|
29
|
+
'resource-leak': 'medium',
|
|
30
|
+
'unbounded-recursion': 'high',
|
|
31
|
+
'auto-restart-bomb': 'critical',
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Gets the human-readable title for a rule
|
|
35
|
+
*/
|
|
36
|
+
export function getRuleTitle(rule) {
|
|
37
|
+
return RULE_TITLES[rule] || rule;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Gets the severity level for a rule
|
|
41
|
+
*/
|
|
42
|
+
export function getRuleSeverity(rule) {
|
|
43
|
+
return RULE_SEVERITIES[rule] || 'medium';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Categorizes a violation and assigns it proper severity and title
|
|
47
|
+
*/
|
|
48
|
+
export function categorizeViolation(violation) {
|
|
49
|
+
// Override severity if necessary (already set in violation)
|
|
50
|
+
return violation;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Converts a side effect violation to a Failure object
|
|
54
|
+
*/
|
|
55
|
+
export function violationToFailure(violation, createFailure) {
|
|
56
|
+
return createFailure(violation.description, [violation.file], violation.hint, `Side-Effect: ${getRuleTitle(violation.rule)}`, violation.line, violation.line, violation.severity);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Groups violations by severity level
|
|
60
|
+
*/
|
|
61
|
+
export function groupBySeverity(violations) {
|
|
62
|
+
const grouped = {
|
|
63
|
+
critical: [],
|
|
64
|
+
high: [],
|
|
65
|
+
medium: [],
|
|
66
|
+
low: [],
|
|
67
|
+
};
|
|
68
|
+
for (const violation of violations) {
|
|
69
|
+
const severity = violation.severity;
|
|
70
|
+
if (severity in grouped) {
|
|
71
|
+
grouped[severity].push(violation);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return grouped;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Filters violations by severity threshold
|
|
78
|
+
*/
|
|
79
|
+
export function filterBySeverity(violations, minSeverity) {
|
|
80
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
81
|
+
const minLevel = severityOrder[minSeverity];
|
|
82
|
+
return violations.filter(v => severityOrder[v.severity] <= minLevel);
|
|
83
|
+
}
|
|
@@ -32,11 +32,9 @@
|
|
|
32
32
|
*
|
|
33
33
|
* This is a CORE gate (enabled by default, provenance: ai-drift).
|
|
34
34
|
* Supports: JS/TS, Python, Go, Rust, C#, Java, Ruby
|
|
35
|
-
*
|
|
36
|
-
* @since v4.3.0
|
|
37
35
|
*/
|
|
38
|
-
import { Gate, GateContext } from '
|
|
39
|
-
import { Failure, Provenance } from '
|
|
36
|
+
import { Gate, GateContext } from '../base.js';
|
|
37
|
+
import { Failure, Provenance } from '../../types/index.js';
|
|
40
38
|
export interface SideEffectAnalysisConfig {
|
|
41
39
|
enabled?: boolean;
|
|
42
40
|
check_unbounded_timers?: boolean;
|
|
@@ -63,5 +61,5 @@ export declare class SideEffectAnalysisGate extends Gate {
|
|
|
63
61
|
private checkResourceLifecycle;
|
|
64
62
|
private checkRecursiveDepth;
|
|
65
63
|
private checkAutoRestart;
|
|
66
|
-
private buildFailures;
|
|
67
64
|
}
|
|
65
|
+
export { SideEffectViolation, SideEffectLang } from '../side-effect-helpers/index.js';
|
|
@@ -32,25 +32,33 @@
|
|
|
32
32
|
*
|
|
33
33
|
* This is a CORE gate (enabled by default, provenance: ai-drift).
|
|
34
34
|
* Supports: JS/TS, Python, Go, Rust, C#, Java, Ruby
|
|
35
|
-
*
|
|
36
|
-
* @since v4.3.0
|
|
37
35
|
*/
|
|
38
|
-
import { Gate } from '
|
|
39
|
-
import { FileScanner } from '
|
|
40
|
-
import { Logger } from '
|
|
36
|
+
import { Gate } from '../base.js';
|
|
37
|
+
import { FileScanner } from '../../utils/scanner.js';
|
|
38
|
+
import { Logger } from '../../utils/logger.js';
|
|
41
39
|
import fs from 'fs-extra';
|
|
42
40
|
import path from 'path';
|
|
43
41
|
import { LANG_MAP, FILE_GLOBS, stripStrings,
|
|
44
|
-
// Scope analysis
|
|
45
|
-
findEnclosingFunction, extractLoopBody, extractFunctionDefs,
|
|
46
|
-
// Variable binding
|
|
47
|
-
extractVariableBinding, hasCleanupForVariable,
|
|
48
|
-
// Framework awareness
|
|
49
|
-
isInUseEffectWithCleanup, hasGoDefer, isPythonWithStatement, isJavaTryWithResources, isCSharpUsing, isRubyBlockForm, isRustAutoDropped, isInsideCleanupContext,
|
|
50
42
|
// Detectors
|
|
51
43
|
isTimerCreation, getTimerCleanupPatterns, isProcessSpawn, getProcessCleanupPatterns, isUnboundedLoop, containsIO, isFileWatcher, extractWatchedPath, extractWritePath, pathsOverlap, findWriteInBody, hasDebounceProtection, isResourceOpen, getResourceClosePatterns, isExitHandler,
|
|
52
44
|
// Loop/recursion
|
|
53
|
-
hasRetryLimit, hasCatchWithContinue, hasBaseCase, hasDepthParameter,
|
|
45
|
+
hasRetryLimit, hasCatchWithContinue, hasBaseCase, hasDepthParameter,
|
|
46
|
+
// Variable binding
|
|
47
|
+
extractVariableBinding, hasCleanupForVariable,
|
|
48
|
+
// Go defer
|
|
49
|
+
hasGoDefer,
|
|
50
|
+
// Python with
|
|
51
|
+
isPythonWithStatement,
|
|
52
|
+
// Java try-with
|
|
53
|
+
isJavaTryWithResources,
|
|
54
|
+
// C# using
|
|
55
|
+
isCSharpUsing,
|
|
56
|
+
// Ruby block
|
|
57
|
+
isRubyBlockForm,
|
|
58
|
+
// Rust
|
|
59
|
+
isRustAutoDropped, } from '../side-effect-helpers/index.js';
|
|
60
|
+
import { findFunctionScope, getBlockBody, isInFrameworkCleanup, getAllFunctions, } from './scope-tracker.js';
|
|
61
|
+
import { violationToFailure, } from './categorizer.js';
|
|
54
62
|
export class SideEffectAnalysisGate extends Gate {
|
|
55
63
|
cfg;
|
|
56
64
|
constructor(config = {}) {
|
|
@@ -102,7 +110,7 @@ export class SideEffectAnalysisGate extends Gate {
|
|
|
102
110
|
}
|
|
103
111
|
catch { /* skip unreadable files */ }
|
|
104
112
|
}
|
|
105
|
-
return this.
|
|
113
|
+
return violations.map(v => violationToFailure(v, (msg, files, hint, title, sl, el, sev) => this.createFailure(msg, files, hint, title, sl, el, sev)));
|
|
106
114
|
}
|
|
107
115
|
scanFile(lang, lines, content, file, violations) {
|
|
108
116
|
if (this.cfg.check_unbounded_timers) {
|
|
@@ -148,14 +156,10 @@ export class SideEffectAnalysisGate extends Gate {
|
|
|
148
156
|
// Extract variable binding: `const timer = setInterval(...)`
|
|
149
157
|
const varName = extractVariableBinding(lines[i], lang);
|
|
150
158
|
// Find enclosing function scope
|
|
151
|
-
const scope =
|
|
152
|
-
// FRAMEWORK CHECK: React useEffect with cleanup return
|
|
153
|
-
if ((
|
|
154
|
-
continue; //
|
|
155
|
-
}
|
|
156
|
-
// FRAMEWORK CHECK: Inside a cleanup/teardown context already
|
|
157
|
-
if (isInsideCleanupContext(lines, i, lang)) {
|
|
158
|
-
continue; // Timer in cleanup = intentional short-lived timer
|
|
159
|
+
const scope = findFunctionScope(lines, i, lang);
|
|
160
|
+
// FRAMEWORK CHECK: React useEffect with cleanup return or cleanup context
|
|
161
|
+
if (isInFrameworkCleanup(lines, i, lang)) {
|
|
162
|
+
continue; // Framework cleanup handles it
|
|
159
163
|
}
|
|
160
164
|
if (varName) {
|
|
161
165
|
// Variable is stored — check for cleanup using that specific variable
|
|
@@ -202,7 +206,7 @@ export class SideEffectAnalysisGate extends Gate {
|
|
|
202
206
|
if (!spawnMatch)
|
|
203
207
|
continue;
|
|
204
208
|
const varName = extractVariableBinding(lines[i], lang);
|
|
205
|
-
const scope =
|
|
209
|
+
const scope = findFunctionScope(lines, i, lang);
|
|
206
210
|
// Check if the process result is awaited on the same line
|
|
207
211
|
if (/\bawait\b/.test(lines[i]))
|
|
208
212
|
continue;
|
|
@@ -263,7 +267,7 @@ export class SideEffectAnalysisGate extends Gate {
|
|
|
263
267
|
if (!isUnboundedLoop(lines[i], lang))
|
|
264
268
|
continue;
|
|
265
269
|
// Extract actual loop body using scope tracking
|
|
266
|
-
const { body, end } =
|
|
270
|
+
const { body, end } = getBlockBody(lines, i, lang);
|
|
267
271
|
// Check for I/O inside the extracted body
|
|
268
272
|
if (!containsIO(body, lang))
|
|
269
273
|
continue;
|
|
@@ -308,7 +312,7 @@ export class SideEffectAnalysisGate extends Gate {
|
|
|
308
312
|
const isLoop = isUnboundedLoop(lines[i], lang) || /\bwhile\s*\(/.test(stripped);
|
|
309
313
|
if (!isLoop)
|
|
310
314
|
continue;
|
|
311
|
-
const { body, end } =
|
|
315
|
+
const { body, end } = getBlockBody(lines, i, lang);
|
|
312
316
|
// Check if this is actually a retry pattern (catch + continue)
|
|
313
317
|
if (!hasCatchWithContinue(body, lang))
|
|
314
318
|
continue;
|
|
@@ -341,7 +345,7 @@ export class SideEffectAnalysisGate extends Gate {
|
|
|
341
345
|
// Extract the watched path
|
|
342
346
|
const watchedPath = extractWatchedPath(lines[i]);
|
|
343
347
|
// Extract the watcher callback body
|
|
344
|
-
const { body, end } =
|
|
348
|
+
const { body, end } = getBlockBody(lines, i, lang);
|
|
345
349
|
// Check for file write operations in the callback body
|
|
346
350
|
const writeCall = findWriteInBody(body, lang);
|
|
347
351
|
if (!writeCall)
|
|
@@ -426,10 +430,10 @@ export class SideEffectAnalysisGate extends Gate {
|
|
|
426
430
|
if (lang === 'rs' && isRustAutoDropped(lines, i))
|
|
427
431
|
continue;
|
|
428
432
|
// Inside a cleanup context (finally, __exit__, Dispose, etc.)
|
|
429
|
-
if (
|
|
433
|
+
if (isInFrameworkCleanup(lines, i, lang))
|
|
430
434
|
continue;
|
|
431
435
|
// ── VARIABLE-BOUND CLEANUP CHECK ──
|
|
432
|
-
const scope =
|
|
436
|
+
const scope = findFunctionScope(lines, i, lang);
|
|
433
437
|
if (varName) {
|
|
434
438
|
// Check for cleanup using the specific variable
|
|
435
439
|
const hasPairedCleanup = hasCleanupForVariable(lines, varName, scope.start, scope.end, closePats, lang);
|
|
@@ -472,7 +476,7 @@ export class SideEffectAnalysisGate extends Gate {
|
|
|
472
476
|
// it's just a stack overflow — bad but not a side effect).
|
|
473
477
|
// ═══════════════════════════════════════════════════════════════
|
|
474
478
|
checkRecursiveDepth(lang, lines, file, violations) {
|
|
475
|
-
const funcDefs =
|
|
479
|
+
const funcDefs = getAllFunctions(lines, lang);
|
|
476
480
|
for (const func of funcDefs) {
|
|
477
481
|
const bodyLines = lines.slice(func.start + 1, func.end);
|
|
478
482
|
const body = bodyLines.join('\n');
|
|
@@ -517,7 +521,7 @@ export class SideEffectAnalysisGate extends Gate {
|
|
|
517
521
|
if (!isExitHandler(lines[i], lang))
|
|
518
522
|
continue;
|
|
519
523
|
// Extract the handler body
|
|
520
|
-
const { body, end } =
|
|
524
|
+
const { body, end } = getBlockBody(lines, i, lang);
|
|
521
525
|
// Check if the handler spawns a process
|
|
522
526
|
const hasSpawn = body.split('\n').some(l => isProcessSpawn(l, lang) !== null);
|
|
523
527
|
if (!hasSpawn)
|
|
@@ -541,20 +545,4 @@ export class SideEffectAnalysisGate extends Gate {
|
|
|
541
545
|
});
|
|
542
546
|
}
|
|
543
547
|
}
|
|
544
|
-
// ═══════════════════════════════════════════════════════════════
|
|
545
|
-
// OUTPUT
|
|
546
|
-
// ═══════════════════════════════════════════════════════════════
|
|
547
|
-
buildFailures(violations) {
|
|
548
|
-
return violations.map(v => this.createFailure(v.description, [v.file], v.hint, `Side-Effect: ${RULE_TITLES[v.rule] || v.rule}`, v.line, v.line, v.severity));
|
|
549
|
-
}
|
|
550
548
|
}
|
|
551
|
-
const RULE_TITLES = {
|
|
552
|
-
'unbounded-timer': 'Unbounded Timer',
|
|
553
|
-
'orphan-process': 'Orphan Process',
|
|
554
|
-
'unbounded-io-loop': 'Unbounded I/O Loop',
|
|
555
|
-
'retry-without-limit': 'Retry Without Limit',
|
|
556
|
-
'circular-trigger': 'Circular File Trigger',
|
|
557
|
-
'resource-leak': 'Resource Leak',
|
|
558
|
-
'unbounded-recursion': 'Unbounded Recursion',
|
|
559
|
-
'auto-restart-bomb': 'Auto-Restart Bomb',
|
|
560
|
-
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope and Context Tracking for Side-Effect Analysis
|
|
3
|
+
*
|
|
4
|
+
* Tracks scope boundaries, context tracking, and state during analysis.
|
|
5
|
+
*/
|
|
6
|
+
import { SideEffectLang } from '../side-effect-helpers/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Finds the enclosing function scope for a given line index.
|
|
9
|
+
* Returns start and end line numbers of the scope.
|
|
10
|
+
*/
|
|
11
|
+
export declare function findFunctionScope(lines: string[], lineIndex: number, lang: SideEffectLang): {
|
|
12
|
+
start: number;
|
|
13
|
+
end: number;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Extracts the body of a loop or block starting at the given line.
|
|
17
|
+
* Handles scope-aware block detection (brace/indent tracking).
|
|
18
|
+
*/
|
|
19
|
+
export declare function getBlockBody(lines: string[], lineIndex: number, lang: SideEffectLang): {
|
|
20
|
+
body: string;
|
|
21
|
+
end: number;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Checks if a timer creation is in a React useEffect with cleanup return.
|
|
25
|
+
* Framework-aware pattern detection for safe side effects.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isInFrameworkCleanup(lines: string[], lineIndex: number, lang: SideEffectLang): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Extracts all function definitions from the source code.
|
|
30
|
+
* Used for recursion analysis.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getAllFunctions(lines: string[], lang: SideEffectLang): Array<{
|
|
33
|
+
name: string;
|
|
34
|
+
start: number;
|
|
35
|
+
end: number;
|
|
36
|
+
params: string;
|
|
37
|
+
}>;
|