ship-safe 6.1.1 → 6.2.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/README.md +735 -641
- package/cli/agents/api-fuzzer.js +345 -345
- package/cli/agents/auth-bypass-agent.js +348 -348
- package/cli/agents/base-agent.js +272 -272
- package/cli/agents/cicd-scanner.js +236 -201
- package/cli/agents/config-auditor.js +521 -521
- package/cli/agents/deep-analyzer.js +6 -2
- package/cli/agents/git-history-scanner.js +170 -170
- package/cli/agents/html-reporter.js +568 -568
- package/cli/agents/index.js +84 -84
- package/cli/agents/injection-tester.js +500 -500
- package/cli/agents/llm-redteam.js +251 -251
- package/cli/agents/mobile-scanner.js +231 -231
- package/cli/agents/orchestrator.js +322 -322
- package/cli/agents/pii-compliance-agent.js +301 -301
- package/cli/agents/scoring-engine.js +248 -248
- package/cli/agents/supabase-rls-agent.js +154 -154
- package/cli/agents/supply-chain-agent.js +650 -507
- package/cli/bin/ship-safe.js +452 -426
- package/cli/commands/agent.js +608 -608
- package/cli/commands/audit.js +986 -980
- package/cli/commands/baseline.js +193 -193
- package/cli/commands/ci.js +342 -342
- package/cli/commands/deps.js +516 -516
- package/cli/commands/doctor.js +159 -159
- package/cli/commands/fix.js +218 -218
- package/cli/commands/hooks.js +268 -0
- package/cli/commands/init.js +407 -407
- package/cli/commands/mcp.js +304 -304
- package/cli/commands/red-team.js +7 -1
- package/cli/commands/remediate.js +798 -798
- package/cli/commands/rotate.js +571 -571
- package/cli/commands/scan.js +569 -569
- package/cli/commands/score.js +449 -449
- package/cli/commands/watch.js +281 -281
- package/cli/hooks/patterns.js +313 -0
- package/cli/hooks/post-tool-use.js +140 -0
- package/cli/hooks/pre-tool-use.js +186 -0
- package/cli/index.js +73 -69
- package/cli/providers/llm-provider.js +397 -287
- package/cli/utils/autofix-rules.js +74 -74
- package/cli/utils/cache-manager.js +311 -311
- package/cli/utils/output.js +230 -230
- package/cli/utils/patterns.js +1121 -1121
- package/cli/utils/pdf-generator.js +94 -94
- package/package.json +69 -69
- package/configs/supabase/rls-templates.sql +0 -242
|
@@ -1,231 +1,231 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MobileScanner Agent
|
|
3
|
-
* ====================
|
|
4
|
-
*
|
|
5
|
-
* Security scanning for React Native, Expo, Flutter,
|
|
6
|
-
* and native mobile codebases.
|
|
7
|
-
* Based on OWASP Mobile Top 10 2024.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import fs from 'fs';
|
|
11
|
-
import path from 'path';
|
|
12
|
-
import { BaseAgent, createFinding } from './base-agent.js';
|
|
13
|
-
|
|
14
|
-
const PATTERNS = [
|
|
15
|
-
// ── M1: Improper Credential Usage ──────────────────────────────────────────
|
|
16
|
-
{
|
|
17
|
-
rule: 'MOBILE_HARDCODED_KEY',
|
|
18
|
-
title: 'Mobile: Hardcoded API Key in Bundle',
|
|
19
|
-
regex: /(?:apiKey|api_key|API_KEY|secret|SECRET)\s*[:=]\s*["'][a-zA-Z0-9_\-]{20,}["']/g,
|
|
20
|
-
severity: 'critical',
|
|
21
|
-
cwe: 'CWE-798',
|
|
22
|
-
owasp: 'M1',
|
|
23
|
-
description: 'Hardcoded API key in mobile code. Mobile bundles are easily decompiled.',
|
|
24
|
-
fix: 'Store secrets server-side. Use expo-secure-store or EncryptedSharedPreferences.',
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
rule: 'MOBILE_KEY_IN_CONFIG',
|
|
28
|
-
title: 'Mobile: Secret in app.json/app.config.js',
|
|
29
|
-
regex: /(?:apiKey|apiSecret|secret|token|password|private_key)\s*["']?\s*[:=]\s*["'][^"']{8,}["']/gi,
|
|
30
|
-
severity: 'high',
|
|
31
|
-
cwe: 'CWE-798',
|
|
32
|
-
owasp: 'M1',
|
|
33
|
-
description: 'Secret in app config file. This gets bundled into the app binary.',
|
|
34
|
-
fix: 'Move to environment variables or server-side configuration',
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
// ── M3: Insecure Authentication/Authorization ──────────────────────────────
|
|
38
|
-
{
|
|
39
|
-
rule: 'MOBILE_LOCAL_AUTH_ONLY',
|
|
40
|
-
title: 'Mobile: Client-Only Authentication',
|
|
41
|
-
regex: /(?:isAuthenticated|isLoggedIn|isAdmin)\s*[:=]\s*(?:AsyncStorage|localStorage|SecureStore)/g,
|
|
42
|
-
severity: 'high',
|
|
43
|
-
cwe: 'CWE-603',
|
|
44
|
-
owasp: 'M3',
|
|
45
|
-
description: 'Authentication state stored only on client. Attacker can bypass by modifying storage.',
|
|
46
|
-
fix: 'Verify authentication server-side on every API request',
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
// ── M4: Insufficient Input/Output Validation ──────────────────────────────
|
|
50
|
-
{
|
|
51
|
-
rule: 'MOBILE_WEBVIEW_JS',
|
|
52
|
-
title: 'Mobile: WebView JavaScript Enabled',
|
|
53
|
-
regex: /(?:javaScriptEnabled|javascriptEnabled)\s*[:=]\s*(?:\{?\s*true|True)/g,
|
|
54
|
-
severity: 'medium',
|
|
55
|
-
cwe: 'CWE-79',
|
|
56
|
-
owasp: 'M4',
|
|
57
|
-
description: 'WebView with JavaScript enabled can be exploited via injected content.',
|
|
58
|
-
fix: 'Disable JavaScript in WebViews loading untrusted content, or sanitize loaded HTML',
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
rule: 'MOBILE_DEEPLINK_INJECTION',
|
|
62
|
-
title: 'Mobile: Deep Link Parameter Injection',
|
|
63
|
-
regex: /(?:Linking\.getInitialURL|useURL|addEventListener\s*\(\s*['"]url['"])/g,
|
|
64
|
-
severity: 'high',
|
|
65
|
-
cwe: 'CWE-20',
|
|
66
|
-
owasp: 'M4',
|
|
67
|
-
confidence: 'low',
|
|
68
|
-
description: 'Deep link URL handler detected. Ensure parameters from deep links are validated before use.',
|
|
69
|
-
fix: 'Validate and sanitize all parameters from deep links before use',
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
// ── M5: Insecure Communication ─────────────────────────────────────────────
|
|
73
|
-
{
|
|
74
|
-
rule: 'MOBILE_HTTP_ENDPOINT',
|
|
75
|
-
title: 'Mobile: HTTP (Non-HTTPS) Endpoint',
|
|
76
|
-
regex: /(?:baseURL|apiUrl|endpoint|url|API_URL)\s*[:=]\s*["']http:\/\//gi,
|
|
77
|
-
severity: 'high',
|
|
78
|
-
cwe: 'CWE-319',
|
|
79
|
-
owasp: 'M5',
|
|
80
|
-
description: 'HTTP endpoint in mobile app. All traffic is unencrypted and interceptable.',
|
|
81
|
-
fix: 'Use HTTPS for all endpoints. Configure ATS (iOS) and cleartextTraffic (Android).',
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
rule: 'MOBILE_NO_CERT_PINNING',
|
|
85
|
-
title: 'Mobile: Missing Certificate Pinning',
|
|
86
|
-
regex: /(?:fetch|axios|http)\s*\(\s*(?!.*pin|certificate)/g,
|
|
87
|
-
severity: 'medium',
|
|
88
|
-
cwe: 'CWE-295',
|
|
89
|
-
owasp: 'M5',
|
|
90
|
-
confidence: 'low',
|
|
91
|
-
description: 'No certificate pinning detected. MITM attacks possible on compromised networks.',
|
|
92
|
-
fix: 'Implement cert pinning with react-native-ssl-pinning or TrustKit',
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
// ── M6: Inadequate Privacy Controls ────────────────────────────────────────
|
|
96
|
-
{
|
|
97
|
-
rule: 'MOBILE_EXCESSIVE_PERMISSIONS',
|
|
98
|
-
title: 'Mobile: Excessive Permissions',
|
|
99
|
-
regex: /(?:CAMERA|CONTACTS|LOCATION|MICROPHONE|CALENDAR|READ_SMS|CALL_LOG|READ_PHONE_STATE)\s*(?:[,\]])/g,
|
|
100
|
-
severity: 'medium',
|
|
101
|
-
cwe: 'CWE-250',
|
|
102
|
-
owasp: 'M6',
|
|
103
|
-
confidence: 'low',
|
|
104
|
-
description: 'Multiple sensitive permissions requested. Only request what is needed.',
|
|
105
|
-
fix: 'Remove unnecessary permissions. Request at runtime with clear justification.',
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
// ── M8: Security Misconfiguration ──────────────────────────────────────────
|
|
109
|
-
{
|
|
110
|
-
rule: 'MOBILE_DEBUG_BUILD',
|
|
111
|
-
title: 'Mobile: Debug Mode in Release',
|
|
112
|
-
regex: /(?:__DEV__|debuggable\s*[:=]\s*true|android:debuggable="true"|DEBUG_MODE\s*[:=]\s*true)/g,
|
|
113
|
-
severity: 'high',
|
|
114
|
-
cwe: 'CWE-215',
|
|
115
|
-
owasp: 'M8',
|
|
116
|
-
description: 'Debug mode enabled. Exposes debugging interfaces and detailed error messages.',
|
|
117
|
-
fix: 'Ensure __DEV__ checks are used correctly. Set debuggable=false in release builds.',
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
rule: 'MOBILE_BACKUP_ENABLED',
|
|
121
|
-
title: 'Mobile: App Backup Enabled',
|
|
122
|
-
regex: /(?:android:allowBackup="true"|allowsBackup\s*[:=]\s*true)/g,
|
|
123
|
-
severity: 'medium',
|
|
124
|
-
cwe: 'CWE-312',
|
|
125
|
-
owasp: 'M8',
|
|
126
|
-
description: 'App backup enabled. Sensitive data can be extracted from backups.',
|
|
127
|
-
fix: 'Set android:allowBackup="false" and exclude sensitive files from backup',
|
|
128
|
-
},
|
|
129
|
-
|
|
130
|
-
// ── M9: Insecure Data Storage ──────────────────────────────────────────────
|
|
131
|
-
{
|
|
132
|
-
rule: 'MOBILE_ASYNCSTORAGE_SECRET',
|
|
133
|
-
title: 'Mobile: Secret in AsyncStorage',
|
|
134
|
-
regex: /AsyncStorage\.setItem\s*\(\s*["'](?:.*(?:token|key|secret|password|credential|session))/gi,
|
|
135
|
-
severity: 'high',
|
|
136
|
-
cwe: 'CWE-312',
|
|
137
|
-
owasp: 'M9',
|
|
138
|
-
description: 'Storing secrets in AsyncStorage (unencrypted). Use expo-secure-store or Keychain.',
|
|
139
|
-
fix: 'Use expo-secure-store (Expo) or react-native-keychain for sensitive data',
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
rule: 'MOBILE_LOCALSTORAGE_SECRET',
|
|
143
|
-
title: 'Mobile: Secret in localStorage',
|
|
144
|
-
regex: /localStorage\.setItem\s*\(\s*["'](?:.*(?:token|key|secret|password|credential|session))/gi,
|
|
145
|
-
severity: 'high',
|
|
146
|
-
cwe: 'CWE-312',
|
|
147
|
-
owasp: 'M9',
|
|
148
|
-
description: 'Storing secrets in localStorage (unencrypted). Use secure storage APIs.',
|
|
149
|
-
fix: 'Use platform-specific secure storage: Keychain (iOS), EncryptedSharedPreferences (Android)',
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
rule: 'MOBILE_LOG_SENSITIVE',
|
|
153
|
-
title: 'Mobile: Sensitive Data in Logs',
|
|
154
|
-
regex: /console\.(?:log|info|warn|debug)\s*\(\s*.*(?:token|password|secret|key|credential|session|auth)/gi,
|
|
155
|
-
severity: 'medium',
|
|
156
|
-
cwe: 'CWE-532',
|
|
157
|
-
owasp: 'M9',
|
|
158
|
-
confidence: 'medium',
|
|
159
|
-
description: 'Sensitive data logged to console. Logs are accessible on rooted/jailbroken devices.',
|
|
160
|
-
fix: 'Remove sensitive data from console.log. Use __DEV__ check for debug logging.',
|
|
161
|
-
},
|
|
162
|
-
|
|
163
|
-
// ── M10: Insufficient Cryptography ─────────────────────────────────────────
|
|
164
|
-
{
|
|
165
|
-
rule: 'MOBILE_HARDCODED_CRYPTO_KEY',
|
|
166
|
-
title: 'Mobile: Hardcoded Encryption Key',
|
|
167
|
-
regex: /(?:encrypt|cipher|aes|crypto).*(?:key|iv|salt)\s*[:=]\s*["'][a-zA-Z0-9+/=]{8,}["']/gi,
|
|
168
|
-
severity: 'critical',
|
|
169
|
-
cwe: 'CWE-321',
|
|
170
|
-
owasp: 'M10',
|
|
171
|
-
description: 'Hardcoded encryption key in mobile code. Easily extracted from decompiled binary.',
|
|
172
|
-
fix: 'Derive keys from user credentials or fetch from server at runtime',
|
|
173
|
-
},
|
|
174
|
-
|
|
175
|
-
// ── Flutter-specific ───────────────────────────────────────────────────────
|
|
176
|
-
{
|
|
177
|
-
rule: 'FLUTTER_SHARED_PREFS_SECRET',
|
|
178
|
-
title: 'Flutter: Secret in SharedPreferences',
|
|
179
|
-
regex: /SharedPreferences.*(?:setString|setInt)\s*\(\s*["'](?:.*(?:token|key|secret|password|api))/gi,
|
|
180
|
-
severity: 'high',
|
|
181
|
-
cwe: 'CWE-312',
|
|
182
|
-
owasp: 'M9',
|
|
183
|
-
description: 'Storing secrets in SharedPreferences (unencrypted). Use flutter_secure_storage.',
|
|
184
|
-
fix: 'Use flutter_secure_storage package for sensitive data',
|
|
185
|
-
},
|
|
186
|
-
];
|
|
187
|
-
|
|
188
|
-
export class MobileScanner extends BaseAgent {
|
|
189
|
-
constructor() {
|
|
190
|
-
super('MobileScanner', 'Mobile security scanning (OWASP Mobile Top 10)', 'mobile');
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
shouldRun(recon) {
|
|
194
|
-
return recon?.frameworks?.some(f =>
|
|
195
|
-
['react-native', 'flutter', 'expo'].includes(f)
|
|
196
|
-
) ?? false;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
async analyze(context) {
|
|
200
|
-
const { rootPath, files, recon } = context;
|
|
201
|
-
|
|
202
|
-
// Only run if mobile framework detected
|
|
203
|
-
const isMobile = recon?.frameworks?.some(f =>
|
|
204
|
-
['react-native', 'flutter', 'expo'].includes(f)
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
// Also check for mobile-specific files
|
|
208
|
-
const hasMobileFiles = files.some(f => {
|
|
209
|
-
const basename = path.basename(f);
|
|
210
|
-
return ['app.json', 'app.config.js', 'app.config.ts',
|
|
211
|
-
'pubspec.yaml', 'AndroidManifest.xml', 'Info.plist',
|
|
212
|
-
'expo-env.d.ts'].includes(basename);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
if (!isMobile && !hasMobileFiles) return [];
|
|
216
|
-
|
|
217
|
-
const codeFiles = files.filter(f => {
|
|
218
|
-
const ext = path.extname(f).toLowerCase();
|
|
219
|
-
return ['.js', '.jsx', '.ts', '.tsx', '.dart', '.swift', '.kt',
|
|
220
|
-
'.java', '.xml', '.plist', '.json'].includes(ext);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
let findings = [];
|
|
224
|
-
for (const file of codeFiles) {
|
|
225
|
-
findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
|
|
226
|
-
}
|
|
227
|
-
return findings;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export default MobileScanner;
|
|
1
|
+
/**
|
|
2
|
+
* MobileScanner Agent
|
|
3
|
+
* ====================
|
|
4
|
+
*
|
|
5
|
+
* Security scanning for React Native, Expo, Flutter,
|
|
6
|
+
* and native mobile codebases.
|
|
7
|
+
* Based on OWASP Mobile Top 10 2024.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { BaseAgent, createFinding } from './base-agent.js';
|
|
13
|
+
|
|
14
|
+
const PATTERNS = [
|
|
15
|
+
// ── M1: Improper Credential Usage ──────────────────────────────────────────
|
|
16
|
+
{
|
|
17
|
+
rule: 'MOBILE_HARDCODED_KEY',
|
|
18
|
+
title: 'Mobile: Hardcoded API Key in Bundle',
|
|
19
|
+
regex: /(?:apiKey|api_key|API_KEY|secret|SECRET)\s*[:=]\s*["'][a-zA-Z0-9_\-]{20,}["']/g,
|
|
20
|
+
severity: 'critical',
|
|
21
|
+
cwe: 'CWE-798',
|
|
22
|
+
owasp: 'M1',
|
|
23
|
+
description: 'Hardcoded API key in mobile code. Mobile bundles are easily decompiled.',
|
|
24
|
+
fix: 'Store secrets server-side. Use expo-secure-store or EncryptedSharedPreferences.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
rule: 'MOBILE_KEY_IN_CONFIG',
|
|
28
|
+
title: 'Mobile: Secret in app.json/app.config.js',
|
|
29
|
+
regex: /(?:apiKey|apiSecret|secret|token|password|private_key)\s*["']?\s*[:=]\s*["'][^"']{8,}["']/gi,
|
|
30
|
+
severity: 'high',
|
|
31
|
+
cwe: 'CWE-798',
|
|
32
|
+
owasp: 'M1',
|
|
33
|
+
description: 'Secret in app config file. This gets bundled into the app binary.',
|
|
34
|
+
fix: 'Move to environment variables or server-side configuration',
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// ── M3: Insecure Authentication/Authorization ──────────────────────────────
|
|
38
|
+
{
|
|
39
|
+
rule: 'MOBILE_LOCAL_AUTH_ONLY',
|
|
40
|
+
title: 'Mobile: Client-Only Authentication',
|
|
41
|
+
regex: /(?:isAuthenticated|isLoggedIn|isAdmin)\s*[:=]\s*(?:AsyncStorage|localStorage|SecureStore)/g,
|
|
42
|
+
severity: 'high',
|
|
43
|
+
cwe: 'CWE-603',
|
|
44
|
+
owasp: 'M3',
|
|
45
|
+
description: 'Authentication state stored only on client. Attacker can bypass by modifying storage.',
|
|
46
|
+
fix: 'Verify authentication server-side on every API request',
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// ── M4: Insufficient Input/Output Validation ──────────────────────────────
|
|
50
|
+
{
|
|
51
|
+
rule: 'MOBILE_WEBVIEW_JS',
|
|
52
|
+
title: 'Mobile: WebView JavaScript Enabled',
|
|
53
|
+
regex: /(?:javaScriptEnabled|javascriptEnabled)\s*[:=]\s*(?:\{?\s*true|True)/g,
|
|
54
|
+
severity: 'medium',
|
|
55
|
+
cwe: 'CWE-79',
|
|
56
|
+
owasp: 'M4',
|
|
57
|
+
description: 'WebView with JavaScript enabled can be exploited via injected content.',
|
|
58
|
+
fix: 'Disable JavaScript in WebViews loading untrusted content, or sanitize loaded HTML',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
rule: 'MOBILE_DEEPLINK_INJECTION',
|
|
62
|
+
title: 'Mobile: Deep Link Parameter Injection',
|
|
63
|
+
regex: /(?:Linking\.getInitialURL|useURL|addEventListener\s*\(\s*['"]url['"])/g,
|
|
64
|
+
severity: 'high',
|
|
65
|
+
cwe: 'CWE-20',
|
|
66
|
+
owasp: 'M4',
|
|
67
|
+
confidence: 'low',
|
|
68
|
+
description: 'Deep link URL handler detected. Ensure parameters from deep links are validated before use.',
|
|
69
|
+
fix: 'Validate and sanitize all parameters from deep links before use',
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// ── M5: Insecure Communication ─────────────────────────────────────────────
|
|
73
|
+
{
|
|
74
|
+
rule: 'MOBILE_HTTP_ENDPOINT',
|
|
75
|
+
title: 'Mobile: HTTP (Non-HTTPS) Endpoint',
|
|
76
|
+
regex: /(?:baseURL|apiUrl|endpoint|url|API_URL)\s*[:=]\s*["']http:\/\//gi,
|
|
77
|
+
severity: 'high',
|
|
78
|
+
cwe: 'CWE-319',
|
|
79
|
+
owasp: 'M5',
|
|
80
|
+
description: 'HTTP endpoint in mobile app. All traffic is unencrypted and interceptable.',
|
|
81
|
+
fix: 'Use HTTPS for all endpoints. Configure ATS (iOS) and cleartextTraffic (Android).',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
rule: 'MOBILE_NO_CERT_PINNING',
|
|
85
|
+
title: 'Mobile: Missing Certificate Pinning',
|
|
86
|
+
regex: /(?:fetch|axios|http)\s*\(\s*(?!.*pin|certificate)/g,
|
|
87
|
+
severity: 'medium',
|
|
88
|
+
cwe: 'CWE-295',
|
|
89
|
+
owasp: 'M5',
|
|
90
|
+
confidence: 'low',
|
|
91
|
+
description: 'No certificate pinning detected. MITM attacks possible on compromised networks.',
|
|
92
|
+
fix: 'Implement cert pinning with react-native-ssl-pinning or TrustKit',
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// ── M6: Inadequate Privacy Controls ────────────────────────────────────────
|
|
96
|
+
{
|
|
97
|
+
rule: 'MOBILE_EXCESSIVE_PERMISSIONS',
|
|
98
|
+
title: 'Mobile: Excessive Permissions',
|
|
99
|
+
regex: /(?:CAMERA|CONTACTS|LOCATION|MICROPHONE|CALENDAR|READ_SMS|CALL_LOG|READ_PHONE_STATE)\s*(?:[,\]])/g,
|
|
100
|
+
severity: 'medium',
|
|
101
|
+
cwe: 'CWE-250',
|
|
102
|
+
owasp: 'M6',
|
|
103
|
+
confidence: 'low',
|
|
104
|
+
description: 'Multiple sensitive permissions requested. Only request what is needed.',
|
|
105
|
+
fix: 'Remove unnecessary permissions. Request at runtime with clear justification.',
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// ── M8: Security Misconfiguration ──────────────────────────────────────────
|
|
109
|
+
{
|
|
110
|
+
rule: 'MOBILE_DEBUG_BUILD',
|
|
111
|
+
title: 'Mobile: Debug Mode in Release',
|
|
112
|
+
regex: /(?:__DEV__|debuggable\s*[:=]\s*true|android:debuggable="true"|DEBUG_MODE\s*[:=]\s*true)/g,
|
|
113
|
+
severity: 'high',
|
|
114
|
+
cwe: 'CWE-215',
|
|
115
|
+
owasp: 'M8',
|
|
116
|
+
description: 'Debug mode enabled. Exposes debugging interfaces and detailed error messages.',
|
|
117
|
+
fix: 'Ensure __DEV__ checks are used correctly. Set debuggable=false in release builds.',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
rule: 'MOBILE_BACKUP_ENABLED',
|
|
121
|
+
title: 'Mobile: App Backup Enabled',
|
|
122
|
+
regex: /(?:android:allowBackup="true"|allowsBackup\s*[:=]\s*true)/g,
|
|
123
|
+
severity: 'medium',
|
|
124
|
+
cwe: 'CWE-312',
|
|
125
|
+
owasp: 'M8',
|
|
126
|
+
description: 'App backup enabled. Sensitive data can be extracted from backups.',
|
|
127
|
+
fix: 'Set android:allowBackup="false" and exclude sensitive files from backup',
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// ── M9: Insecure Data Storage ──────────────────────────────────────────────
|
|
131
|
+
{
|
|
132
|
+
rule: 'MOBILE_ASYNCSTORAGE_SECRET',
|
|
133
|
+
title: 'Mobile: Secret in AsyncStorage',
|
|
134
|
+
regex: /AsyncStorage\.setItem\s*\(\s*["'](?:.*(?:token|key|secret|password|credential|session))/gi,
|
|
135
|
+
severity: 'high',
|
|
136
|
+
cwe: 'CWE-312',
|
|
137
|
+
owasp: 'M9',
|
|
138
|
+
description: 'Storing secrets in AsyncStorage (unencrypted). Use expo-secure-store or Keychain.',
|
|
139
|
+
fix: 'Use expo-secure-store (Expo) or react-native-keychain for sensitive data',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
rule: 'MOBILE_LOCALSTORAGE_SECRET',
|
|
143
|
+
title: 'Mobile: Secret in localStorage',
|
|
144
|
+
regex: /localStorage\.setItem\s*\(\s*["'](?:.*(?:token|key|secret|password|credential|session))/gi,
|
|
145
|
+
severity: 'high',
|
|
146
|
+
cwe: 'CWE-312',
|
|
147
|
+
owasp: 'M9',
|
|
148
|
+
description: 'Storing secrets in localStorage (unencrypted). Use secure storage APIs.',
|
|
149
|
+
fix: 'Use platform-specific secure storage: Keychain (iOS), EncryptedSharedPreferences (Android)',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
rule: 'MOBILE_LOG_SENSITIVE',
|
|
153
|
+
title: 'Mobile: Sensitive Data in Logs',
|
|
154
|
+
regex: /console\.(?:log|info|warn|debug)\s*\(\s*.*(?:token|password|secret|key|credential|session|auth)/gi,
|
|
155
|
+
severity: 'medium',
|
|
156
|
+
cwe: 'CWE-532',
|
|
157
|
+
owasp: 'M9',
|
|
158
|
+
confidence: 'medium',
|
|
159
|
+
description: 'Sensitive data logged to console. Logs are accessible on rooted/jailbroken devices.',
|
|
160
|
+
fix: 'Remove sensitive data from console.log. Use __DEV__ check for debug logging.',
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// ── M10: Insufficient Cryptography ─────────────────────────────────────────
|
|
164
|
+
{
|
|
165
|
+
rule: 'MOBILE_HARDCODED_CRYPTO_KEY',
|
|
166
|
+
title: 'Mobile: Hardcoded Encryption Key',
|
|
167
|
+
regex: /(?:encrypt|cipher|aes|crypto).*(?:key|iv|salt)\s*[:=]\s*["'][a-zA-Z0-9+/=]{8,}["']/gi,
|
|
168
|
+
severity: 'critical',
|
|
169
|
+
cwe: 'CWE-321',
|
|
170
|
+
owasp: 'M10',
|
|
171
|
+
description: 'Hardcoded encryption key in mobile code. Easily extracted from decompiled binary.',
|
|
172
|
+
fix: 'Derive keys from user credentials or fetch from server at runtime',
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// ── Flutter-specific ───────────────────────────────────────────────────────
|
|
176
|
+
{
|
|
177
|
+
rule: 'FLUTTER_SHARED_PREFS_SECRET',
|
|
178
|
+
title: 'Flutter: Secret in SharedPreferences',
|
|
179
|
+
regex: /SharedPreferences.*(?:setString|setInt)\s*\(\s*["'](?:.*(?:token|key|secret|password|api))/gi,
|
|
180
|
+
severity: 'high',
|
|
181
|
+
cwe: 'CWE-312',
|
|
182
|
+
owasp: 'M9',
|
|
183
|
+
description: 'Storing secrets in SharedPreferences (unencrypted). Use flutter_secure_storage.',
|
|
184
|
+
fix: 'Use flutter_secure_storage package for sensitive data',
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
export class MobileScanner extends BaseAgent {
|
|
189
|
+
constructor() {
|
|
190
|
+
super('MobileScanner', 'Mobile security scanning (OWASP Mobile Top 10)', 'mobile');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
shouldRun(recon) {
|
|
194
|
+
return recon?.frameworks?.some(f =>
|
|
195
|
+
['react-native', 'flutter', 'expo'].includes(f)
|
|
196
|
+
) ?? false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async analyze(context) {
|
|
200
|
+
const { rootPath, files, recon } = context;
|
|
201
|
+
|
|
202
|
+
// Only run if mobile framework detected
|
|
203
|
+
const isMobile = recon?.frameworks?.some(f =>
|
|
204
|
+
['react-native', 'flutter', 'expo'].includes(f)
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// Also check for mobile-specific files
|
|
208
|
+
const hasMobileFiles = files.some(f => {
|
|
209
|
+
const basename = path.basename(f);
|
|
210
|
+
return ['app.json', 'app.config.js', 'app.config.ts',
|
|
211
|
+
'pubspec.yaml', 'AndroidManifest.xml', 'Info.plist',
|
|
212
|
+
'expo-env.d.ts'].includes(basename);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (!isMobile && !hasMobileFiles) return [];
|
|
216
|
+
|
|
217
|
+
const codeFiles = files.filter(f => {
|
|
218
|
+
const ext = path.extname(f).toLowerCase();
|
|
219
|
+
return ['.js', '.jsx', '.ts', '.tsx', '.dart', '.swift', '.kt',
|
|
220
|
+
'.java', '.xml', '.plist', '.json'].includes(ext);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
let findings = [];
|
|
224
|
+
for (const file of codeFiles) {
|
|
225
|
+
findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
|
|
226
|
+
}
|
|
227
|
+
return findings;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export default MobileScanner;
|