getdoorman 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/LICENSE +21 -0
- package/README.md +181 -0
- package/bin/doorman.js +444 -0
- package/package.json +74 -0
- package/src/ai-fixer.js +559 -0
- package/src/ast-scanner.js +434 -0
- package/src/auth.js +149 -0
- package/src/baseline.js +48 -0
- package/src/compliance.js +539 -0
- package/src/config.js +466 -0
- package/src/custom-rules.js +32 -0
- package/src/dashboard.js +202 -0
- package/src/detector.js +142 -0
- package/src/fix-engine.js +48 -0
- package/src/fix-registry-extra.js +95 -0
- package/src/fix-registry-go-rust.js +77 -0
- package/src/fix-registry-java-csharp.js +77 -0
- package/src/fix-registry-js.js +99 -0
- package/src/fix-registry-mcp-ai.js +57 -0
- package/src/fix-registry-python.js +87 -0
- package/src/fixer-ruby-php.js +608 -0
- package/src/fixer.js +2113 -0
- package/src/hooks.js +115 -0
- package/src/ignore.js +176 -0
- package/src/index.js +384 -0
- package/src/metrics.js +126 -0
- package/src/monorepo.js +65 -0
- package/src/presets.js +54 -0
- package/src/reporter.js +975 -0
- package/src/rule-worker.js +36 -0
- package/src/rules/ast-rules.js +756 -0
- package/src/rules/bugs/accessibility.js +235 -0
- package/src/rules/bugs/ai-codegen-fixable.js +172 -0
- package/src/rules/bugs/ai-codegen.js +365 -0
- package/src/rules/bugs/code-smell-bugs.js +247 -0
- package/src/rules/bugs/crypto-bugs.js +195 -0
- package/src/rules/bugs/docker-bugs.js +158 -0
- package/src/rules/bugs/general.js +361 -0
- package/src/rules/bugs/go-bugs.js +279 -0
- package/src/rules/bugs/index.js +73 -0
- package/src/rules/bugs/js-api.js +257 -0
- package/src/rules/bugs/js-array-object.js +210 -0
- package/src/rules/bugs/js-async-fixable.js +223 -0
- package/src/rules/bugs/js-async.js +211 -0
- package/src/rules/bugs/js-closure-scope.js +182 -0
- package/src/rules/bugs/js-database.js +203 -0
- package/src/rules/bugs/js-error-handling.js +148 -0
- package/src/rules/bugs/js-logic.js +261 -0
- package/src/rules/bugs/js-memory.js +214 -0
- package/src/rules/bugs/js-node.js +361 -0
- package/src/rules/bugs/js-react.js +373 -0
- package/src/rules/bugs/js-regex.js +200 -0
- package/src/rules/bugs/js-state.js +272 -0
- package/src/rules/bugs/js-type-coercion.js +318 -0
- package/src/rules/bugs/nextjs-bugs.js +242 -0
- package/src/rules/bugs/nextjs-fixable.js +120 -0
- package/src/rules/bugs/node-fixable.js +178 -0
- package/src/rules/bugs/python-advanced.js +245 -0
- package/src/rules/bugs/python-fixable.js +98 -0
- package/src/rules/bugs/python.js +284 -0
- package/src/rules/bugs/react-fixable.js +207 -0
- package/src/rules/bugs/ruby-bugs.js +182 -0
- package/src/rules/bugs/shell-bugs.js +181 -0
- package/src/rules/bugs/silent-failures.js +261 -0
- package/src/rules/bugs/ts-bugs.js +235 -0
- package/src/rules/bugs/unused-vars.js +65 -0
- package/src/rules/compliance/accessibility-ext.js +468 -0
- package/src/rules/compliance/education.js +322 -0
- package/src/rules/compliance/financial.js +421 -0
- package/src/rules/compliance/frameworks.js +507 -0
- package/src/rules/compliance/healthcare.js +520 -0
- package/src/rules/compliance/index.js +2714 -0
- package/src/rules/compliance/regional-eu.js +480 -0
- package/src/rules/compliance/regional-international.js +903 -0
- package/src/rules/cost/index.js +1993 -0
- package/src/rules/data/index.js +2503 -0
- package/src/rules/dependencies/index.js +1684 -0
- package/src/rules/deployment/index.js +2050 -0
- package/src/rules/index.js +71 -0
- package/src/rules/infrastructure/index.js +3048 -0
- package/src/rules/performance/index.js +3455 -0
- package/src/rules/quality/index.js +3175 -0
- package/src/rules/reliability/index.js +3040 -0
- package/src/rules/scope-rules.js +815 -0
- package/src/rules/security/ai-api.js +1177 -0
- package/src/rules/security/auth.js +1328 -0
- package/src/rules/security/cors.js +127 -0
- package/src/rules/security/crypto.js +527 -0
- package/src/rules/security/csharp.js +862 -0
- package/src/rules/security/csrf.js +193 -0
- package/src/rules/security/dart.js +835 -0
- package/src/rules/security/deserialization.js +291 -0
- package/src/rules/security/file-upload.js +187 -0
- package/src/rules/security/go.js +850 -0
- package/src/rules/security/headers.js +235 -0
- package/src/rules/security/index.js +65 -0
- package/src/rules/security/injection.js +1639 -0
- package/src/rules/security/mcp-server.js +71 -0
- package/src/rules/security/misconfiguration.js +660 -0
- package/src/rules/security/oauth-jwt.js +329 -0
- package/src/rules/security/path-traversal.js +295 -0
- package/src/rules/security/php.js +1054 -0
- package/src/rules/security/prototype-pollution.js +283 -0
- package/src/rules/security/rate-limiting.js +208 -0
- package/src/rules/security/ruby.js +1061 -0
- package/src/rules/security/rust.js +693 -0
- package/src/rules/security/secrets.js +747 -0
- package/src/rules/security/shell.js +647 -0
- package/src/rules/security/ssrf.js +298 -0
- package/src/rules/security/supply-chain-advanced.js +393 -0
- package/src/rules/security/supply-chain.js +734 -0
- package/src/rules/security/swift.js +835 -0
- package/src/rules/security/taint.js +27 -0
- package/src/rules/security/xss.js +520 -0
- package/src/scan-cache.js +71 -0
- package/src/scanner.js +710 -0
- package/src/scope-analyzer.js +685 -0
- package/src/share.js +88 -0
- package/src/taint.js +300 -0
- package/src/telemetry.js +183 -0
- package/src/tracer.js +190 -0
- package/src/upload.js +35 -0
- package/src/worker.js +31 -0
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dart / Flutter Security Rules (SEC-DART-001 through SEC-DART-040)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const isDart = (f) => f.endsWith('.dart');
|
|
6
|
+
|
|
7
|
+
const SKIP_PATH = /[/\\](test|tests|__tests__|__mocks__|mocks|fixtures|__fixtures__|spec|__snapshots__|node_modules|vendor|dist|build|\.dart_tool)[/\\]/i;
|
|
8
|
+
const COMMENT_LINE = /^\s*(\/\/|\/\*|\*)/;
|
|
9
|
+
|
|
10
|
+
function scanLines(content, regex, file, rule) {
|
|
11
|
+
const findings = [];
|
|
12
|
+
const lines = content.split('\n');
|
|
13
|
+
for (let i = 0; i < lines.length; i++) {
|
|
14
|
+
const line = lines[i];
|
|
15
|
+
if (COMMENT_LINE.test(line)) continue;
|
|
16
|
+
if (regex.test(line)) {
|
|
17
|
+
findings.push({
|
|
18
|
+
ruleId: rule.id,
|
|
19
|
+
category: rule.category,
|
|
20
|
+
severity: rule.severity,
|
|
21
|
+
title: rule.title,
|
|
22
|
+
description: rule.description,
|
|
23
|
+
confidence: rule.confidence,
|
|
24
|
+
file,
|
|
25
|
+
line: i + 1,
|
|
26
|
+
fix: rule.fix || null,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return findings;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const rules = [
|
|
34
|
+
// SEC-DART-001: Insecure HTTP connections
|
|
35
|
+
{
|
|
36
|
+
id: 'SEC-DART-001',
|
|
37
|
+
category: 'security',
|
|
38
|
+
severity: 'high',
|
|
39
|
+
confidence: 'likely',
|
|
40
|
+
title: 'Insecure HTTP Connection',
|
|
41
|
+
description: 'Using http:// instead of https:// transmits data in plaintext, vulnerable to man-in-the-middle attacks.',
|
|
42
|
+
fix: { suggestion: 'Use https:// for all network connections.' },
|
|
43
|
+
check({ files }) {
|
|
44
|
+
const findings = [];
|
|
45
|
+
const pattern = /['"]http:\/\/(?!localhost|127\.0\.0\.1|10\.|192\.168\.)/;
|
|
46
|
+
for (const [path, content] of files) {
|
|
47
|
+
if (SKIP_PATH.test(path)) continue;
|
|
48
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
49
|
+
}
|
|
50
|
+
return findings;
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// SEC-DART-002: Hardcoded API keys
|
|
55
|
+
{
|
|
56
|
+
id: 'SEC-DART-002',
|
|
57
|
+
category: 'security',
|
|
58
|
+
severity: 'critical',
|
|
59
|
+
confidence: 'likely',
|
|
60
|
+
title: 'Hardcoded API Key or Secret',
|
|
61
|
+
description: 'API keys or secrets hardcoded in Dart code can be extracted from the compiled app.',
|
|
62
|
+
fix: { suggestion: 'Use --dart-define, environment variables, or a secrets manager instead of hardcoding keys.' },
|
|
63
|
+
check({ files }) {
|
|
64
|
+
const findings = [];
|
|
65
|
+
const pattern = /(?:const|final|var)\s+\w*(?:apiKey|api_key|secret|password|token|apiSecret|api_secret)\w*\s*=\s*['"]/i;
|
|
66
|
+
for (const [path, content] of files) {
|
|
67
|
+
if (SKIP_PATH.test(path)) continue;
|
|
68
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
69
|
+
}
|
|
70
|
+
return findings;
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// SEC-DART-003: SharedPreferences for sensitive data
|
|
75
|
+
{
|
|
76
|
+
id: 'SEC-DART-003',
|
|
77
|
+
category: 'security',
|
|
78
|
+
severity: 'high',
|
|
79
|
+
confidence: 'likely',
|
|
80
|
+
title: 'Sensitive Data in SharedPreferences',
|
|
81
|
+
description: 'SharedPreferences stores data in plaintext XML/plist files accessible on rooted devices.',
|
|
82
|
+
fix: { suggestion: 'Use flutter_secure_storage which uses Keychain (iOS) and EncryptedSharedPreferences (Android).' },
|
|
83
|
+
check({ files }) {
|
|
84
|
+
const findings = [];
|
|
85
|
+
const pattern = /SharedPreferences.*(?:password|token|secret|apiKey|api_key|credential|auth)/i;
|
|
86
|
+
for (const [path, content] of files) {
|
|
87
|
+
if (SKIP_PATH.test(path)) continue;
|
|
88
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
89
|
+
}
|
|
90
|
+
return findings;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// SEC-DART-004: Missing certificate pinning
|
|
95
|
+
{
|
|
96
|
+
id: 'SEC-DART-004',
|
|
97
|
+
category: 'security',
|
|
98
|
+
severity: 'medium',
|
|
99
|
+
confidence: 'suggestion',
|
|
100
|
+
title: 'Disabled Certificate Verification',
|
|
101
|
+
description: 'Setting badCertificateCallback to always return true disables all certificate validation.',
|
|
102
|
+
fix: { suggestion: 'Implement proper certificate pinning with trusted certificates or use http_certificate_pinning package.' },
|
|
103
|
+
check({ files }) {
|
|
104
|
+
const findings = [];
|
|
105
|
+
const pattern = /badCertificateCallback\s*=.*(?:true|=>.*true)/;
|
|
106
|
+
for (const [path, content] of files) {
|
|
107
|
+
if (SKIP_PATH.test(path)) continue;
|
|
108
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
109
|
+
}
|
|
110
|
+
return findings;
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// SEC-DART-005: Insecure WebView
|
|
115
|
+
{
|
|
116
|
+
id: 'SEC-DART-005',
|
|
117
|
+
category: 'security',
|
|
118
|
+
severity: 'medium',
|
|
119
|
+
confidence: 'likely',
|
|
120
|
+
title: 'Insecure WebView Configuration',
|
|
121
|
+
description: 'JavascriptMode.unrestricted enables JavaScript in WebView without content restrictions.',
|
|
122
|
+
fix: { suggestion: 'Use JavascriptMode.disabled when JavaScript is not needed, or implement NavigationDelegate to restrict URLs.' },
|
|
123
|
+
check({ files }) {
|
|
124
|
+
const findings = [];
|
|
125
|
+
const pattern = /javascriptMode\s*:\s*JavascriptMode\.unrestricted|JavaScriptMode\.unrestricted/;
|
|
126
|
+
for (const [path, content] of files) {
|
|
127
|
+
if (SKIP_PATH.test(path)) continue;
|
|
128
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
129
|
+
}
|
|
130
|
+
return findings;
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
// SEC-DART-006: Platform channel injection
|
|
135
|
+
{
|
|
136
|
+
id: 'SEC-DART-006',
|
|
137
|
+
category: 'security',
|
|
138
|
+
severity: 'medium',
|
|
139
|
+
confidence: 'suggestion',
|
|
140
|
+
title: 'Platform Channel Without Input Validation',
|
|
141
|
+
description: 'MethodChannel calls passing user input to native code without validation can lead to injection.',
|
|
142
|
+
fix: { suggestion: 'Validate and sanitize all data passed through platform channels before processing on the native side.' },
|
|
143
|
+
check({ files }) {
|
|
144
|
+
const findings = [];
|
|
145
|
+
const pattern = /MethodChannel\s*\(|\.invokeMethod\s*\(/;
|
|
146
|
+
for (const [path, content] of files) {
|
|
147
|
+
if (SKIP_PATH.test(path)) continue;
|
|
148
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
149
|
+
}
|
|
150
|
+
return findings;
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// SEC-DART-007: Debug mode check
|
|
155
|
+
{
|
|
156
|
+
id: 'SEC-DART-007',
|
|
157
|
+
category: 'security',
|
|
158
|
+
severity: 'medium',
|
|
159
|
+
confidence: 'likely',
|
|
160
|
+
title: 'Debug Mode Check Missing or Misused',
|
|
161
|
+
description: 'kDebugMode or assert-based debug checks may leak sensitive behavior in non-standard builds.',
|
|
162
|
+
fix: { suggestion: 'Use kReleaseMode for production-only code paths and ensure debug features are properly gated.' },
|
|
163
|
+
check({ files }) {
|
|
164
|
+
const findings = [];
|
|
165
|
+
const pattern = /kDebugMode|assert\s*\(.*debug/i;
|
|
166
|
+
for (const [path, content] of files) {
|
|
167
|
+
if (SKIP_PATH.test(path)) continue;
|
|
168
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
169
|
+
}
|
|
170
|
+
return findings;
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
// SEC-DART-008: SQL injection in sqflite
|
|
175
|
+
{
|
|
176
|
+
id: 'SEC-DART-008',
|
|
177
|
+
category: 'security',
|
|
178
|
+
severity: 'critical',
|
|
179
|
+
confidence: 'likely',
|
|
180
|
+
title: 'SQL Injection in sqflite',
|
|
181
|
+
description: 'String interpolation in rawQuery/rawInsert/rawUpdate/rawDelete enables SQL injection.',
|
|
182
|
+
fix: { suggestion: 'Use parameterized queries with ? placeholders and pass values as the second argument.' },
|
|
183
|
+
check({ files }) {
|
|
184
|
+
const findings = [];
|
|
185
|
+
const pattern = /raw(?:Query|Insert|Update|Delete)\s*\(\s*['"].*\$/;
|
|
186
|
+
for (const [path, content] of files) {
|
|
187
|
+
if (SKIP_PATH.test(path)) continue;
|
|
188
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
189
|
+
}
|
|
190
|
+
return findings;
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
// SEC-DART-009: Insecure random
|
|
195
|
+
{
|
|
196
|
+
id: 'SEC-DART-009',
|
|
197
|
+
category: 'security',
|
|
198
|
+
severity: 'medium',
|
|
199
|
+
confidence: 'likely',
|
|
200
|
+
title: 'Insecure Random Number Generation',
|
|
201
|
+
description: 'dart:math Random() is not cryptographically secure and should not be used for security purposes.',
|
|
202
|
+
fix: { suggestion: 'Use Random.secure() for cryptographically secure random numbers.' },
|
|
203
|
+
check({ files }) {
|
|
204
|
+
const findings = [];
|
|
205
|
+
const pattern = /Random\s*\(\s*\)(?!.*secure)/i;
|
|
206
|
+
for (const [path, content] of files) {
|
|
207
|
+
if (SKIP_PATH.test(path)) continue;
|
|
208
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
209
|
+
}
|
|
210
|
+
return findings;
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
// SEC-DART-010: print() with sensitive data
|
|
215
|
+
{
|
|
216
|
+
id: 'SEC-DART-010',
|
|
217
|
+
category: 'security',
|
|
218
|
+
severity: 'medium',
|
|
219
|
+
confidence: 'likely',
|
|
220
|
+
title: 'print() with Sensitive Data in Production',
|
|
221
|
+
description: 'print() statements remain in release builds and can leak sensitive information in logs.',
|
|
222
|
+
fix: { suggestion: 'Wrap print() in kDebugMode checks or use a logging framework with log levels.' },
|
|
223
|
+
check({ files }) {
|
|
224
|
+
const findings = [];
|
|
225
|
+
const pattern = /print\s*\(.*(?:password|token|secret|key|credential|auth)/i;
|
|
226
|
+
for (const [path, content] of files) {
|
|
227
|
+
if (SKIP_PATH.test(path)) continue;
|
|
228
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
229
|
+
}
|
|
230
|
+
return findings;
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
// SEC-DART-011: Unsafe HTML rendering
|
|
235
|
+
{
|
|
236
|
+
id: 'SEC-DART-011',
|
|
237
|
+
category: 'security',
|
|
238
|
+
severity: 'high',
|
|
239
|
+
confidence: 'likely',
|
|
240
|
+
title: 'Unsafe HTML Rendering',
|
|
241
|
+
description: 'Rendering unsanitized HTML content can lead to XSS in WebView or flutter_html widgets.',
|
|
242
|
+
fix: { suggestion: 'Sanitize HTML content before rendering. Use allowlists for permitted HTML tags and attributes.' },
|
|
243
|
+
check({ files }) {
|
|
244
|
+
const findings = [];
|
|
245
|
+
const pattern = /Html\s*\(\s*data\s*:|\.loadUrl.*javascript:|evaluateJavascript\s*\(/;
|
|
246
|
+
for (const [path, content] of files) {
|
|
247
|
+
if (SKIP_PATH.test(path)) continue;
|
|
248
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
249
|
+
}
|
|
250
|
+
return findings;
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
// SEC-DART-012: Insecure deep link handling
|
|
255
|
+
{
|
|
256
|
+
id: 'SEC-DART-012',
|
|
257
|
+
category: 'security',
|
|
258
|
+
severity: 'medium',
|
|
259
|
+
confidence: 'suggestion',
|
|
260
|
+
title: 'Insecure Deep Link Handling',
|
|
261
|
+
description: 'Deep links without proper validation can be exploited to navigate to unintended screens or trigger actions.',
|
|
262
|
+
fix: { suggestion: 'Validate deep link URIs, require authentication for sensitive routes, and use App Links / Universal Links.' },
|
|
263
|
+
check({ files }) {
|
|
264
|
+
const findings = [];
|
|
265
|
+
const pattern = /uni_links|getInitialUri|uriLinkStream|getInitialLink|onGenerateRoute.*Uri\.parse/;
|
|
266
|
+
for (const [path, content] of files) {
|
|
267
|
+
if (SKIP_PATH.test(path)) continue;
|
|
268
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
269
|
+
}
|
|
270
|
+
return findings;
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
// SEC-DART-013: Missing encryption for local storage
|
|
275
|
+
{
|
|
276
|
+
id: 'SEC-DART-013',
|
|
277
|
+
category: 'security',
|
|
278
|
+
severity: 'medium',
|
|
279
|
+
confidence: 'suggestion',
|
|
280
|
+
title: 'Unencrypted Local File Storage',
|
|
281
|
+
description: 'Writing sensitive data to files without encryption exposes it on rooted/jailbroken devices.',
|
|
282
|
+
fix: { suggestion: 'Encrypt files using encrypt package or use flutter_secure_storage for sensitive key-value data.' },
|
|
283
|
+
check({ files }) {
|
|
284
|
+
const findings = [];
|
|
285
|
+
const pattern = /File\s*\(.*\)\.writeAs(?:String|Bytes)|writeAsString.*(?:password|token|secret|key)/i;
|
|
286
|
+
for (const [path, content] of files) {
|
|
287
|
+
if (SKIP_PATH.test(path)) continue;
|
|
288
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
289
|
+
}
|
|
290
|
+
return findings;
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
// SEC-DART-014: Missing authentication checks
|
|
295
|
+
{
|
|
296
|
+
id: 'SEC-DART-014',
|
|
297
|
+
category: 'security',
|
|
298
|
+
severity: 'high',
|
|
299
|
+
confidence: 'suggestion',
|
|
300
|
+
title: 'Missing Authentication Check in Route',
|
|
301
|
+
description: 'Routes or screens accessing sensitive data without authentication guards.',
|
|
302
|
+
fix: { suggestion: 'Implement route guards or middleware to verify authentication before accessing protected screens.' },
|
|
303
|
+
check({ files }) {
|
|
304
|
+
const findings = [];
|
|
305
|
+
const pattern = /MaterialPageRoute|CupertinoPageRoute|GoRoute.*(?:admin|settings|profile|account|payment)/i;
|
|
306
|
+
for (const [path, content] of files) {
|
|
307
|
+
if (SKIP_PATH.test(path)) continue;
|
|
308
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
309
|
+
}
|
|
310
|
+
return findings;
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
// SEC-DART-015: Root/jailbreak detection missing
|
|
315
|
+
{
|
|
316
|
+
id: 'SEC-DART-015',
|
|
317
|
+
category: 'security',
|
|
318
|
+
severity: 'low',
|
|
319
|
+
confidence: 'suggestion',
|
|
320
|
+
title: 'Missing Root/Jailbreak Detection',
|
|
321
|
+
description: 'Apps handling sensitive data should detect rooted/jailbroken devices and adjust behavior.',
|
|
322
|
+
fix: { suggestion: 'Use flutter_jailbreak_detection or root_checker packages to detect compromised devices.' },
|
|
323
|
+
check({ files }) {
|
|
324
|
+
const findings = [];
|
|
325
|
+
const pattern = /flutter_jailbreak_detection|root_checker|SafeDevice/;
|
|
326
|
+
for (const [path, content] of files) {
|
|
327
|
+
if (SKIP_PATH.test(path)) continue;
|
|
328
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
329
|
+
}
|
|
330
|
+
return findings;
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
// SEC-DART-016: Hardcoded Firebase config
|
|
335
|
+
{
|
|
336
|
+
id: 'SEC-DART-016',
|
|
337
|
+
category: 'security',
|
|
338
|
+
severity: 'high',
|
|
339
|
+
confidence: 'likely',
|
|
340
|
+
title: 'Hardcoded Firebase Configuration',
|
|
341
|
+
description: 'Firebase API keys and project IDs hardcoded in Dart code instead of using configuration files.',
|
|
342
|
+
fix: { suggestion: 'Use firebase_options.dart generated by FlutterFire CLI and keep it out of version control.' },
|
|
343
|
+
check({ files }) {
|
|
344
|
+
const findings = [];
|
|
345
|
+
const pattern = /(?:const|final)\s+\w*(?:firebaseApiKey|firebase_api_key|messagingSenderId|appId)\w*\s*=\s*['"]/i;
|
|
346
|
+
for (const [path, content] of files) {
|
|
347
|
+
if (SKIP_PATH.test(path)) continue;
|
|
348
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
349
|
+
}
|
|
350
|
+
return findings;
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
// SEC-DART-017: Insecure WebSocket
|
|
355
|
+
{
|
|
356
|
+
id: 'SEC-DART-017',
|
|
357
|
+
category: 'security',
|
|
358
|
+
severity: 'high',
|
|
359
|
+
confidence: 'likely',
|
|
360
|
+
title: 'Insecure WebSocket Connection',
|
|
361
|
+
description: 'Using ws:// instead of wss:// transmits WebSocket data without encryption.',
|
|
362
|
+
fix: { suggestion: 'Use wss:// for all WebSocket connections.' },
|
|
363
|
+
check({ files }) {
|
|
364
|
+
const findings = [];
|
|
365
|
+
const pattern = /WebSocketChannel\.connect\s*\(\s*['"]ws:\/\//;
|
|
366
|
+
for (const [path, content] of files) {
|
|
367
|
+
if (SKIP_PATH.test(path)) continue;
|
|
368
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
369
|
+
}
|
|
370
|
+
return findings;
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
// SEC-DART-018: Disabled certificate check in HttpClient
|
|
375
|
+
{
|
|
376
|
+
id: 'SEC-DART-018',
|
|
377
|
+
category: 'security',
|
|
378
|
+
severity: 'critical',
|
|
379
|
+
confidence: 'likely',
|
|
380
|
+
title: 'Disabled Certificate Verification in HttpClient',
|
|
381
|
+
description: 'Setting SecurityContext with allowLegacyUnsafeRenegotiation disables TLS protections.',
|
|
382
|
+
fix: { suggestion: 'Remove allowLegacyUnsafeRenegotiation and use proper certificate pinning.' },
|
|
383
|
+
check({ files }) {
|
|
384
|
+
const findings = [];
|
|
385
|
+
const pattern = /allowLegacyUnsafeRenegotiation\s*=\s*true/;
|
|
386
|
+
for (const [path, content] of files) {
|
|
387
|
+
if (SKIP_PATH.test(path)) continue;
|
|
388
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
389
|
+
}
|
|
390
|
+
return findings;
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
// SEC-DART-019: Eval-like code execution
|
|
395
|
+
{
|
|
396
|
+
id: 'SEC-DART-019',
|
|
397
|
+
category: 'security',
|
|
398
|
+
severity: 'critical',
|
|
399
|
+
confidence: 'likely',
|
|
400
|
+
title: 'Dynamic Code Execution',
|
|
401
|
+
description: 'Using dart:mirrors or evaluateJavascript with user input can lead to code injection.',
|
|
402
|
+
fix: { suggestion: 'Avoid dynamic code execution. Use static analysis and predefined operations instead.' },
|
|
403
|
+
check({ files }) {
|
|
404
|
+
const findings = [];
|
|
405
|
+
const pattern = /dart:mirrors|evaluateJavascript\s*\(|runJavascript\s*\(/;
|
|
406
|
+
for (const [path, content] of files) {
|
|
407
|
+
if (SKIP_PATH.test(path)) continue;
|
|
408
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
409
|
+
}
|
|
410
|
+
return findings;
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
// SEC-DART-020: Insecure clipboard usage
|
|
415
|
+
{
|
|
416
|
+
id: 'SEC-DART-020',
|
|
417
|
+
category: 'security',
|
|
418
|
+
severity: 'medium',
|
|
419
|
+
confidence: 'suggestion',
|
|
420
|
+
title: 'Sensitive Data Copied to Clipboard',
|
|
421
|
+
description: 'Clipboard data is accessible to all apps and may persist across device restarts.',
|
|
422
|
+
fix: { suggestion: 'Avoid copying sensitive data. If needed, clear clipboard after a timeout using Clipboard.setData.' },
|
|
423
|
+
check({ files }) {
|
|
424
|
+
const findings = [];
|
|
425
|
+
const pattern = /Clipboard\.setData\s*\(.*(?:password|token|secret|key)/i;
|
|
426
|
+
for (const [path, content] of files) {
|
|
427
|
+
if (SKIP_PATH.test(path)) continue;
|
|
428
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
429
|
+
}
|
|
430
|
+
return findings;
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
// SEC-DART-021: Unvalidated JSON parsing
|
|
435
|
+
{
|
|
436
|
+
id: 'SEC-DART-021',
|
|
437
|
+
category: 'security',
|
|
438
|
+
severity: 'medium',
|
|
439
|
+
confidence: 'suggestion',
|
|
440
|
+
title: 'Unvalidated JSON Parsing',
|
|
441
|
+
description: 'jsonDecode without try-catch can crash the app with malformed server responses.',
|
|
442
|
+
fix: { suggestion: 'Wrap jsonDecode in try-catch and validate the structure of decoded data before use.' },
|
|
443
|
+
check({ files }) {
|
|
444
|
+
const findings = [];
|
|
445
|
+
const pattern = /jsonDecode\s*\((?!.*try)/;
|
|
446
|
+
for (const [path, content] of files) {
|
|
447
|
+
if (SKIP_PATH.test(path)) continue;
|
|
448
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
449
|
+
}
|
|
450
|
+
return findings;
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
// SEC-DART-022: Hardcoded OAuth credentials
|
|
455
|
+
{
|
|
456
|
+
id: 'SEC-DART-022',
|
|
457
|
+
category: 'security',
|
|
458
|
+
severity: 'critical',
|
|
459
|
+
confidence: 'likely',
|
|
460
|
+
title: 'Hardcoded OAuth Client Secret',
|
|
461
|
+
description: 'OAuth client secrets embedded in mobile apps can be extracted and used to impersonate the app.',
|
|
462
|
+
fix: { suggestion: 'Use PKCE flow for mobile apps which does not require a client secret, or use a backend proxy.' },
|
|
463
|
+
check({ files }) {
|
|
464
|
+
const findings = [];
|
|
465
|
+
const pattern = /(?:clientSecret|client_secret)\s*[:=]\s*['"]/;
|
|
466
|
+
for (const [path, content] of files) {
|
|
467
|
+
if (SKIP_PATH.test(path)) continue;
|
|
468
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
469
|
+
}
|
|
470
|
+
return findings;
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
// SEC-DART-023: Insecure biometric auth
|
|
475
|
+
{
|
|
476
|
+
id: 'SEC-DART-023',
|
|
477
|
+
category: 'security',
|
|
478
|
+
severity: 'high',
|
|
479
|
+
confidence: 'suggestion',
|
|
480
|
+
title: 'Insecure Biometric Authentication',
|
|
481
|
+
description: 'local_auth without additional server-side verification can be bypassed on rooted devices.',
|
|
482
|
+
fix: { suggestion: 'Combine biometric auth with server-side token verification. Do not rely solely on local authentication.' },
|
|
483
|
+
check({ files }) {
|
|
484
|
+
const findings = [];
|
|
485
|
+
const pattern = /LocalAuthentication\s*\(\)|authenticate\s*\(\s*localizedReason/;
|
|
486
|
+
for (const [path, content] of files) {
|
|
487
|
+
if (SKIP_PATH.test(path)) continue;
|
|
488
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
489
|
+
}
|
|
490
|
+
return findings;
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
// SEC-DART-024: Screenshot/screen recording not prevented
|
|
495
|
+
{
|
|
496
|
+
id: 'SEC-DART-024',
|
|
497
|
+
category: 'security',
|
|
498
|
+
severity: 'low',
|
|
499
|
+
confidence: 'suggestion',
|
|
500
|
+
title: 'Missing Screenshot Prevention',
|
|
501
|
+
description: 'Sensitive screens without screenshot prevention can be captured by malicious screen recording.',
|
|
502
|
+
fix: { suggestion: 'Use FLAG_SECURE on Android and UITextField trick on iOS to prevent screenshots of sensitive content.' },
|
|
503
|
+
check({ files }) {
|
|
504
|
+
const findings = [];
|
|
505
|
+
const pattern = /FlutterWindowManager|flutter_windowmanager|FLAG_SECURE/;
|
|
506
|
+
for (const [path, content] of files) {
|
|
507
|
+
if (SKIP_PATH.test(path)) continue;
|
|
508
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
509
|
+
}
|
|
510
|
+
return findings;
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
|
|
514
|
+
// SEC-DART-025: Weak hashing
|
|
515
|
+
{
|
|
516
|
+
id: 'SEC-DART-025',
|
|
517
|
+
category: 'security',
|
|
518
|
+
severity: 'high',
|
|
519
|
+
confidence: 'likely',
|
|
520
|
+
title: 'Weak Hashing Algorithm (MD5/SHA1)',
|
|
521
|
+
description: 'MD5 and SHA1 are cryptographically broken and should not be used for security.',
|
|
522
|
+
fix: { suggestion: 'Use SHA-256 or higher from the crypto package (sha256.convert).' },
|
|
523
|
+
check({ files }) {
|
|
524
|
+
const findings = [];
|
|
525
|
+
const pattern = /md5\.convert|sha1\.convert|Hmac\s*\(\s*md5|Hmac\s*\(\s*sha1/;
|
|
526
|
+
for (const [path, content] of files) {
|
|
527
|
+
if (SKIP_PATH.test(path)) continue;
|
|
528
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
529
|
+
}
|
|
530
|
+
return findings;
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
|
|
534
|
+
// SEC-DART-026: Obfuscation missing
|
|
535
|
+
{
|
|
536
|
+
id: 'SEC-DART-026',
|
|
537
|
+
category: 'security',
|
|
538
|
+
severity: 'low',
|
|
539
|
+
confidence: 'suggestion',
|
|
540
|
+
title: 'Missing Code Obfuscation',
|
|
541
|
+
description: 'Flutter apps without --obfuscate flag can be easily reverse-engineered.',
|
|
542
|
+
fix: { suggestion: 'Build with --obfuscate --split-debug-info flags for release builds.' },
|
|
543
|
+
check({ files }) {
|
|
544
|
+
const findings = [];
|
|
545
|
+
const pattern = /flutter\s+build.*(?:apk|appbundle|ios|ipa)(?!.*obfuscate)/;
|
|
546
|
+
for (const [path, content] of files) {
|
|
547
|
+
if (SKIP_PATH.test(path)) continue;
|
|
548
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
549
|
+
}
|
|
550
|
+
return findings;
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
// SEC-DART-027: Insecure image caching
|
|
555
|
+
{
|
|
556
|
+
id: 'SEC-DART-027',
|
|
557
|
+
category: 'security',
|
|
558
|
+
severity: 'low',
|
|
559
|
+
confidence: 'suggestion',
|
|
560
|
+
title: 'Insecure Image Cache',
|
|
561
|
+
description: 'Cached images are stored unencrypted and may contain sensitive information.',
|
|
562
|
+
fix: { suggestion: 'Use custom cache managers with encryption or clear caches when the app goes to background.' },
|
|
563
|
+
check({ files }) {
|
|
564
|
+
const findings = [];
|
|
565
|
+
const pattern = /CachedNetworkImage|cached_network_image/;
|
|
566
|
+
for (const [path, content] of files) {
|
|
567
|
+
if (SKIP_PATH.test(path)) continue;
|
|
568
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
569
|
+
}
|
|
570
|
+
return findings;
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
|
|
574
|
+
// SEC-DART-028: Insecure process execution
|
|
575
|
+
{
|
|
576
|
+
id: 'SEC-DART-028',
|
|
577
|
+
category: 'security',
|
|
578
|
+
severity: 'critical',
|
|
579
|
+
confidence: 'likely',
|
|
580
|
+
title: 'Process Execution with User Input',
|
|
581
|
+
description: 'Process.run or Process.start with unsanitized input can lead to command injection.',
|
|
582
|
+
fix: { suggestion: 'Avoid passing user input to Process.run. If necessary, use allowlists and strict validation.' },
|
|
583
|
+
check({ files }) {
|
|
584
|
+
const findings = [];
|
|
585
|
+
const pattern = /Process\.(?:run|start)\s*\(/;
|
|
586
|
+
for (const [path, content] of files) {
|
|
587
|
+
if (SKIP_PATH.test(path)) continue;
|
|
588
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
589
|
+
}
|
|
590
|
+
return findings;
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
|
|
594
|
+
// SEC-DART-029: Insecure Hive storage
|
|
595
|
+
{
|
|
596
|
+
id: 'SEC-DART-029',
|
|
597
|
+
category: 'security',
|
|
598
|
+
severity: 'medium',
|
|
599
|
+
confidence: 'suggestion',
|
|
600
|
+
title: 'Unencrypted Hive Box',
|
|
601
|
+
description: 'Hive boxes without encryption store data in plaintext on the device.',
|
|
602
|
+
fix: { suggestion: 'Use Hive.openBox with encryptionCipher parameter using HiveAesCipher.' },
|
|
603
|
+
check({ files }) {
|
|
604
|
+
const findings = [];
|
|
605
|
+
const pattern = /Hive\.openBox\s*[<(](?!.*encryptionCipher)/;
|
|
606
|
+
for (const [path, content] of files) {
|
|
607
|
+
if (SKIP_PATH.test(path)) continue;
|
|
608
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
609
|
+
}
|
|
610
|
+
return findings;
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
|
|
614
|
+
// SEC-DART-030: Dio without interceptors
|
|
615
|
+
{
|
|
616
|
+
id: 'SEC-DART-030',
|
|
617
|
+
category: 'security',
|
|
618
|
+
severity: 'low',
|
|
619
|
+
confidence: 'suggestion',
|
|
620
|
+
title: 'Dio HTTP Client Without Security Interceptors',
|
|
621
|
+
description: 'Dio without interceptors for auth token refresh and error handling may leak credentials.',
|
|
622
|
+
fix: { suggestion: 'Add interceptors for authentication, logging sanitization, and error handling.' },
|
|
623
|
+
check({ files }) {
|
|
624
|
+
const findings = [];
|
|
625
|
+
const pattern = /Dio\s*\(\s*\)|Dio\s*\(\s*BaseOptions/;
|
|
626
|
+
for (const [path, content] of files) {
|
|
627
|
+
if (SKIP_PATH.test(path)) continue;
|
|
628
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
629
|
+
}
|
|
630
|
+
return findings;
|
|
631
|
+
},
|
|
632
|
+
},
|
|
633
|
+
|
|
634
|
+
// SEC-DART-031: Insecure deserialization
|
|
635
|
+
{
|
|
636
|
+
id: 'SEC-DART-031',
|
|
637
|
+
category: 'security',
|
|
638
|
+
severity: 'high',
|
|
639
|
+
confidence: 'suggestion',
|
|
640
|
+
title: 'Insecure Deserialization',
|
|
641
|
+
description: 'Deserializing untrusted data without schema validation can lead to unexpected behavior.',
|
|
642
|
+
fix: { suggestion: 'Validate JSON structure against a schema before deserialization. Use json_serializable for type safety.' },
|
|
643
|
+
check({ files }) {
|
|
644
|
+
const findings = [];
|
|
645
|
+
const pattern = /fromJson\s*\(\s*(?:jsonDecode|json\.decode)\s*\(\s*(?:response|body|data)/;
|
|
646
|
+
for (const [path, content] of files) {
|
|
647
|
+
if (SKIP_PATH.test(path)) continue;
|
|
648
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
649
|
+
}
|
|
650
|
+
return findings;
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
|
|
654
|
+
// SEC-DART-032: Logging interceptor in production
|
|
655
|
+
{
|
|
656
|
+
id: 'SEC-DART-032',
|
|
657
|
+
category: 'security',
|
|
658
|
+
severity: 'medium',
|
|
659
|
+
confidence: 'suggestion',
|
|
660
|
+
title: 'Logging Interceptor in Production',
|
|
661
|
+
description: 'HTTP logging interceptors can log sensitive headers (Authorization) and request bodies.',
|
|
662
|
+
fix: { suggestion: 'Disable or sanitize LogInterceptor in release builds. Guard with kDebugMode.' },
|
|
663
|
+
check({ files }) {
|
|
664
|
+
const findings = [];
|
|
665
|
+
const pattern = /LogInterceptor\s*\(|\.interceptors\.add\s*\(\s*LogInterceptor/;
|
|
666
|
+
for (const [path, content] of files) {
|
|
667
|
+
if (SKIP_PATH.test(path)) continue;
|
|
668
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
669
|
+
}
|
|
670
|
+
return findings;
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
|
|
674
|
+
// SEC-DART-033: Exported content provider
|
|
675
|
+
{
|
|
676
|
+
id: 'SEC-DART-033',
|
|
677
|
+
category: 'security',
|
|
678
|
+
severity: 'medium',
|
|
679
|
+
confidence: 'suggestion',
|
|
680
|
+
title: 'Exposed Android Content Provider',
|
|
681
|
+
description: 'Exported content providers or broadcast receivers can be accessed by other apps.',
|
|
682
|
+
fix: { suggestion: 'Set android:exported=false or add permission requirements to restrict access.' },
|
|
683
|
+
check({ files }) {
|
|
684
|
+
const findings = [];
|
|
685
|
+
const pattern = /android:exported\s*=\s*"true"/;
|
|
686
|
+
for (const [path, content] of files) {
|
|
687
|
+
if (SKIP_PATH.test(path)) continue;
|
|
688
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
689
|
+
}
|
|
690
|
+
return findings;
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
|
|
694
|
+
// SEC-DART-034: Missing network security config
|
|
695
|
+
{
|
|
696
|
+
id: 'SEC-DART-034',
|
|
697
|
+
category: 'security',
|
|
698
|
+
severity: 'medium',
|
|
699
|
+
confidence: 'suggestion',
|
|
700
|
+
title: 'Missing Network Security Configuration',
|
|
701
|
+
description: 'Without a network_security_config.xml, Android 9+ blocks cleartext traffic by default but older versions do not.',
|
|
702
|
+
fix: { suggestion: 'Create res/xml/network_security_config.xml with cleartextTrafficPermitted=false.' },
|
|
703
|
+
check({ files }) {
|
|
704
|
+
const findings = [];
|
|
705
|
+
const pattern = /usesCleartextTraffic\s*=\s*"true"|cleartextTrafficPermitted\s*=\s*"true"/;
|
|
706
|
+
for (const [path, content] of files) {
|
|
707
|
+
if (SKIP_PATH.test(path)) continue;
|
|
708
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
709
|
+
}
|
|
710
|
+
return findings;
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
|
|
714
|
+
// SEC-DART-035: Regex DoS
|
|
715
|
+
{
|
|
716
|
+
id: 'SEC-DART-035',
|
|
717
|
+
category: 'security',
|
|
718
|
+
severity: 'medium',
|
|
719
|
+
confidence: 'suggestion',
|
|
720
|
+
title: 'Potential Regex Denial of Service',
|
|
721
|
+
description: 'Complex regex patterns with nested quantifiers can cause catastrophic backtracking.',
|
|
722
|
+
fix: { suggestion: 'Simplify regex patterns, add length limits on input, or use timeout for regex operations.' },
|
|
723
|
+
check({ files }) {
|
|
724
|
+
const findings = [];
|
|
725
|
+
const pattern = /RegExp\s*\(\s*['"].*(?:\+\+|\*\*|\{\d+,\}.*\{\d+,\})/;
|
|
726
|
+
for (const [path, content] of files) {
|
|
727
|
+
if (SKIP_PATH.test(path)) continue;
|
|
728
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
729
|
+
}
|
|
730
|
+
return findings;
|
|
731
|
+
},
|
|
732
|
+
},
|
|
733
|
+
|
|
734
|
+
// SEC-DART-036: Path traversal
|
|
735
|
+
{
|
|
736
|
+
id: 'SEC-DART-036',
|
|
737
|
+
category: 'security',
|
|
738
|
+
severity: 'high',
|
|
739
|
+
confidence: 'likely',
|
|
740
|
+
title: 'Path Traversal Vulnerability',
|
|
741
|
+
description: 'Using user input in file paths without sanitization allows reading arbitrary files.',
|
|
742
|
+
fix: { suggestion: 'Canonicalize paths and verify they are within the expected directory. Reject paths containing "..".' },
|
|
743
|
+
check({ files }) {
|
|
744
|
+
const findings = [];
|
|
745
|
+
const pattern = /File\s*\(\s*['"]?\$|Directory\s*\(\s*['"]?\$/;
|
|
746
|
+
for (const [path, content] of files) {
|
|
747
|
+
if (SKIP_PATH.test(path)) continue;
|
|
748
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
749
|
+
}
|
|
750
|
+
return findings;
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
|
|
754
|
+
// SEC-DART-037: Insecure token storage
|
|
755
|
+
{
|
|
756
|
+
id: 'SEC-DART-037',
|
|
757
|
+
category: 'security',
|
|
758
|
+
severity: 'high',
|
|
759
|
+
confidence: 'likely',
|
|
760
|
+
title: 'Token Stored in Plain Variable',
|
|
761
|
+
description: 'Storing auth tokens in plain String variables makes them accessible via memory inspection.',
|
|
762
|
+
fix: { suggestion: 'Store tokens in flutter_secure_storage and clear from memory when not needed.' },
|
|
763
|
+
check({ files }) {
|
|
764
|
+
const findings = [];
|
|
765
|
+
const pattern = /(?:static|const|final)\s+String\s+\w*(?:token|jwt|accessToken|refreshToken)\w*\s*=\s*['"]/i;
|
|
766
|
+
for (const [path, content] of files) {
|
|
767
|
+
if (SKIP_PATH.test(path)) continue;
|
|
768
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
769
|
+
}
|
|
770
|
+
return findings;
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
|
|
774
|
+
// SEC-DART-038: Insecure intent handling
|
|
775
|
+
{
|
|
776
|
+
id: 'SEC-DART-038',
|
|
777
|
+
category: 'security',
|
|
778
|
+
severity: 'medium',
|
|
779
|
+
confidence: 'suggestion',
|
|
780
|
+
title: 'Insecure Intent/Scheme Handling',
|
|
781
|
+
description: 'Processing intents or URL schemes without validation can lead to unauthorized actions.',
|
|
782
|
+
fix: { suggestion: 'Validate and sanitize all data received through intents. Verify the calling package.' },
|
|
783
|
+
check({ files }) {
|
|
784
|
+
const findings = [];
|
|
785
|
+
const pattern = /getStringExtra|intent\.data|launch\s*\(\s*Uri\.parse/;
|
|
786
|
+
for (const [path, content] of files) {
|
|
787
|
+
if (SKIP_PATH.test(path)) continue;
|
|
788
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
789
|
+
}
|
|
790
|
+
return findings;
|
|
791
|
+
},
|
|
792
|
+
},
|
|
793
|
+
|
|
794
|
+
// SEC-DART-039: Missing timeout on HTTP requests
|
|
795
|
+
{
|
|
796
|
+
id: 'SEC-DART-039',
|
|
797
|
+
category: 'security',
|
|
798
|
+
severity: 'low',
|
|
799
|
+
confidence: 'suggestion',
|
|
800
|
+
title: 'Missing HTTP Request Timeout',
|
|
801
|
+
description: 'HTTP requests without timeouts can hang indefinitely, leading to resource exhaustion.',
|
|
802
|
+
fix: { suggestion: 'Set connectTimeout and receiveTimeout on Dio, or use http package with timeout parameter.' },
|
|
803
|
+
check({ files }) {
|
|
804
|
+
const findings = [];
|
|
805
|
+
const pattern = /http\.(?:get|post|put|delete|patch)\s*\(\s*Uri\.parse(?!.*timeout)/;
|
|
806
|
+
for (const [path, content] of files) {
|
|
807
|
+
if (SKIP_PATH.test(path)) continue;
|
|
808
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
809
|
+
}
|
|
810
|
+
return findings;
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
|
|
814
|
+
// SEC-DART-040: Debugging enabled in manifest
|
|
815
|
+
{
|
|
816
|
+
id: 'SEC-DART-040',
|
|
817
|
+
category: 'security',
|
|
818
|
+
severity: 'high',
|
|
819
|
+
confidence: 'likely',
|
|
820
|
+
title: 'Debugging Enabled in Android Manifest',
|
|
821
|
+
description: 'android:debuggable=true in release builds allows debugging and memory inspection.',
|
|
822
|
+
fix: { suggestion: 'Ensure android:debuggable is set to false or removed (defaults to false in release).' },
|
|
823
|
+
check({ files }) {
|
|
824
|
+
const findings = [];
|
|
825
|
+
const pattern = /android:debuggable\s*=\s*"true"/;
|
|
826
|
+
for (const [path, content] of files) {
|
|
827
|
+
if (SKIP_PATH.test(path)) continue;
|
|
828
|
+
if (isDart(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
829
|
+
}
|
|
830
|
+
return findings;
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
];
|
|
834
|
+
|
|
835
|
+
export default rules;
|