cyberaudit-skill 3.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/README.md +77 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +241 -0
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +126 -0
- package/package.json +58 -0
- package/skills/cyberaudit/AGENT-BOOT.md +122 -0
- package/skills/cyberaudit/COMMANDS.md +1125 -0
- package/skills/cyberaudit/INSTALL.md +311 -0
- package/skills/cyberaudit/MASTER.md +194 -0
- package/skills/cyberaudit/README.md +154 -0
- package/skills/cyberaudit/USAGE-GUIDE.md +107 -0
- package/skills/cyberaudit/mobile/MOBILE-CHECKLIST.md +245 -0
- package/skills/cyberaudit/mobile/MOBILE-PHILOSOPHY.md +352 -0
- package/skills/cyberaudit/mobile/MOBILE-REMEDIATION-LIBRARY.md +468 -0
- package/skills/cyberaudit/mobile/MOBILE-THREAT-MODELS.md +279 -0
- package/skills/cyberaudit/mobile/frameworks/EXPO.md +247 -0
- package/skills/cyberaudit/mobile/frameworks/FLUTTER.md +338 -0
- package/skills/cyberaudit/mobile/frameworks/IONIC.md +248 -0
- package/skills/cyberaudit/mobile/frameworks/REACT-NATIVE.md +479 -0
- package/skills/cyberaudit/mobile/vulnerabilities/AUTH-MOBILE.md +160 -0
- package/skills/cyberaudit/mobile/vulnerabilities/BINARY-ANALYSIS.md +193 -0
- package/skills/cyberaudit/mobile/vulnerabilities/CRYPTO-MOBILE.md +192 -0
- package/skills/cyberaudit/mobile/vulnerabilities/IPC-DEEPLINKS.md +231 -0
- package/skills/cyberaudit/mobile/vulnerabilities/NETWORK-MOBILE.md +165 -0
- package/skills/cyberaudit/mobile/vulnerabilities/PERMISSIONS.md +180 -0
- package/skills/cyberaudit/mobile/vulnerabilities/RUNTIME-MOBILE.md +214 -0
- package/skills/cyberaudit/mobile/vulnerabilities/STORAGE.md +197 -0
- package/skills/cyberaudit/reports/EXECUTIVE-SUMMARY-TEMPLATE.md +188 -0
- package/skills/cyberaudit/reports/REPORT-TEMPLATE-MOBILE.md +410 -0
- package/skills/cyberaudit/reports/REPORT-TEMPLATE-WEB.md +222 -0
- package/skills/cyberaudit/shared/COMPLIANCE.md +307 -0
- package/skills/cyberaudit/shared/CVSS-GUIDE.md +175 -0
- package/skills/cyberaudit/shared/OWASP-MAPPER.md +130 -0
- package/skills/cyberaudit/shared/SEVERITY-SCORING.md +102 -0
- package/skills/cyberaudit/shared/THREAT-MODELING.md +112 -0
- package/skills/cyberaudit/web/WEB-CHECKLIST.md +222 -0
- package/skills/cyberaudit/web/WEB-PHILOSOPHY.md +308 -0
- package/skills/cyberaudit/web/WEB-REMEDIATION-LIBRARY.md +665 -0
- package/skills/cyberaudit/web/WEB-THREAT-MODELS.md +460 -0
- package/skills/cyberaudit/web/frameworks/ANGULAR.md +169 -0
- package/skills/cyberaudit/web/frameworks/EXPRESS.md +185 -0
- package/skills/cyberaudit/web/frameworks/LARAVEL.md +371 -0
- package/skills/cyberaudit/web/frameworks/NESTJS.md +337 -0
- package/skills/cyberaudit/web/frameworks/NEXTJS.md +352 -0
- package/skills/cyberaudit/web/frameworks/REACT.md +346 -0
- package/skills/cyberaudit/web/frameworks/VUE.md +205 -0
- package/skills/cyberaudit/web/vulnerabilities/AUTH-AUTHZ.md +117 -0
- package/skills/cyberaudit/web/vulnerabilities/BUSINESS-LOGIC.md +603 -0
- package/skills/cyberaudit/web/vulnerabilities/CORS.md +117 -0
- package/skills/cyberaudit/web/vulnerabilities/CSRF.md +131 -0
- package/skills/cyberaudit/web/vulnerabilities/DESERIALIZATION.md +96 -0
- package/skills/cyberaudit/web/vulnerabilities/HEADERS.md +105 -0
- package/skills/cyberaudit/web/vulnerabilities/IDOR-BOLA.md +153 -0
- package/skills/cyberaudit/web/vulnerabilities/INJECTION.md +165 -0
- package/skills/cyberaudit/web/vulnerabilities/SECRETS.md +119 -0
- package/skills/cyberaudit/web/vulnerabilities/SSRF.md +124 -0
- package/skills/cyberaudit/web/vulnerabilities/SUPPLY-CHAIN.md +142 -0
- package/skills/cyberaudit/web/vulnerabilities/XSS.md +133 -0
- package/skills/cyberaudit/web/vulnerabilities/XXE.md +94 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# 🔧 MOBILE REMEDIATION LIBRARY — CYBERAUDIT SKILL
|
|
2
|
+
# Complete fix library for mobile vulnerabilities
|
|
3
|
+
|
|
4
|
+
═══════════════════════════════════════════════════════════════
|
|
5
|
+
REM-MOB-001 — SECURED ASYNCSTORAGE
|
|
6
|
+
═══════════════════════════════════════════════════════════════
|
|
7
|
+
|
|
8
|
+
ISSUE: Sensitive data in AsyncStorage (unencrypted)
|
|
9
|
+
SEVERITY: HIGH
|
|
10
|
+
MASVS : MASVS-STORAGE-1
|
|
11
|
+
|
|
12
|
+
BEFORE (Vulnerable):
|
|
13
|
+
await AsyncStorage.setItem('auth_token', token)
|
|
14
|
+
await AsyncStorage.setItem('user_password', password)
|
|
15
|
+
await AsyncStorage.setItem('card_number', cardNumber)
|
|
16
|
+
|
|
17
|
+
AFTER (Secured):
|
|
18
|
+
// yarn add react-native-keychain
|
|
19
|
+
import * as Keychain from 'react-native-keychain'
|
|
20
|
+
|
|
21
|
+
// Store
|
|
22
|
+
await Keychain.setGenericPassword(
|
|
23
|
+
'auth',
|
|
24
|
+
token,
|
|
25
|
+
{
|
|
26
|
+
service: 'com.yourapp.auth-token',
|
|
27
|
+
accessControl: Keychain.ACCESS_CONTROL
|
|
28
|
+
.BIOMETRY_ANY_OR_DEVICE_PASSCODE,
|
|
29
|
+
accessible: Keychain.ACCESSIBLE
|
|
30
|
+
.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
// Retrieve
|
|
35
|
+
const creds = await Keychain.getGenericPassword({
|
|
36
|
+
service: 'com.yourapp.auth-token'
|
|
37
|
+
})
|
|
38
|
+
const token = creds ? creds.password : null
|
|
39
|
+
|
|
40
|
+
// Delete (logout)
|
|
41
|
+
await Keychain.resetGenericPassword({
|
|
42
|
+
service: 'com.yourapp.auth-token'
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// For less critical data (user preferences)
|
|
46
|
+
// EncryptedStorage is acceptable
|
|
47
|
+
import EncryptedStorage from 'react-native-encrypted-storage'
|
|
48
|
+
|
|
49
|
+
await EncryptedStorage.setItem(
|
|
50
|
+
'user_preferences',
|
|
51
|
+
JSON.stringify({ theme: 'dark', language: 'fr' })
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
═══════════════════════════════════════════════════════════════
|
|
55
|
+
REM-MOB-002 — CERTIFICATE PINNING RN
|
|
56
|
+
═══════════════════════════════════════════════════════════════
|
|
57
|
+
|
|
58
|
+
ISSUE: No certificate pinning
|
|
59
|
+
SEVERITY: HIGH
|
|
60
|
+
MASVS : MASVS-NETWORK-2
|
|
61
|
+
|
|
62
|
+
AFTER (Secured):
|
|
63
|
+
// yarn add react-native-ssl-pinning
|
|
64
|
+
import { fetch as pinnedFetch } from 'react-native-ssl-pinning'
|
|
65
|
+
|
|
66
|
+
// Obtain pins:
|
|
67
|
+
// openssl s_client -connect api.yourapp.com:443 < /dev/null 2>/dev/null \
|
|
68
|
+
// | openssl x509 -pubkey -noout \
|
|
69
|
+
// | openssl pkey -pubin -outform der \
|
|
70
|
+
// | openssl dgst -sha256 -binary \
|
|
71
|
+
// | base64
|
|
72
|
+
|
|
73
|
+
const PINS = {
|
|
74
|
+
primary: 'ABC123...base64==',
|
|
75
|
+
backup: 'XYZ789...base64==', // MANDATORY
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Secure API service
|
|
79
|
+
class SecureApiService {
|
|
80
|
+
private baseUrl = 'https://api.yourapp.com'
|
|
81
|
+
|
|
82
|
+
async get<T>(endpoint: string): Promise<T> {
|
|
83
|
+
const response = await pinnedFetch(
|
|
84
|
+
`${this.baseUrl}${endpoint}`,
|
|
85
|
+
{
|
|
86
|
+
method: 'GET',
|
|
87
|
+
sslPinning: {
|
|
88
|
+
certs: [PINS.primary, PINS.backup],
|
|
89
|
+
},
|
|
90
|
+
headers: {
|
|
91
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
return response.json()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async post<T>(endpoint: string, body: object): Promise<T> {
|
|
100
|
+
const response = await pinnedFetch(
|
|
101
|
+
`${this.baseUrl}${endpoint}`,
|
|
102
|
+
{
|
|
103
|
+
method: 'POST',
|
|
104
|
+
sslPinning: {
|
|
105
|
+
certs: [PINS.primary, PINS.backup],
|
|
106
|
+
},
|
|
107
|
+
body: JSON.stringify(body),
|
|
108
|
+
headers: {
|
|
109
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
110
|
+
'Content-Type': 'application/json',
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
return response.json()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private async getToken(): Promise<string> {
|
|
118
|
+
const creds = await Keychain.getGenericPassword({
|
|
119
|
+
service: 'com.yourapp.auth-token'
|
|
120
|
+
})
|
|
121
|
+
return creds ? creds.password : ''
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
═══════════════════════════════════════════════════════════════
|
|
126
|
+
REM-MOB-003 — SCREENSHOT PROTECTION
|
|
127
|
+
═══════════════════════════════════════════════════════════════
|
|
128
|
+
|
|
129
|
+
ISSUE: Sensitive data visible in app switcher
|
|
130
|
+
SEVERITY: MEDIUM
|
|
131
|
+
MASVS : MASVS-STORAGE-4
|
|
132
|
+
|
|
133
|
+
AFTER (Secured):
|
|
134
|
+
// React Native hook
|
|
135
|
+
import { AppState, Platform, View, Image } from 'react-native'
|
|
136
|
+
import { useEffect, useState } from 'react'
|
|
137
|
+
|
|
138
|
+
export const useScreenProtection = () => {
|
|
139
|
+
const [isHidden, setIsHidden] = useState(false)
|
|
140
|
+
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
const sub = AppState.addEventListener('change', state => {
|
|
143
|
+
setIsHidden(
|
|
144
|
+
state === 'background' || state === 'inactive'
|
|
145
|
+
)
|
|
146
|
+
})
|
|
147
|
+
return () => sub.remove()
|
|
148
|
+
}, [])
|
|
149
|
+
|
|
150
|
+
return isHidden
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Wrapper component
|
|
154
|
+
export const ProtectedView = ({ children, style }) => {
|
|
155
|
+
const isHidden = useScreenProtection()
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<View style={[style, { flex: 1 }]}>
|
|
159
|
+
{isHidden ? (
|
|
160
|
+
<View style={styles.overlay}>
|
|
161
|
+
<Image
|
|
162
|
+
source={require('@/assets/splash.png')}
|
|
163
|
+
style={styles.logo}
|
|
164
|
+
/>
|
|
165
|
+
</View>
|
|
166
|
+
) : (
|
|
167
|
+
children
|
|
168
|
+
)}
|
|
169
|
+
</View>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Android: FLAG_SECURE in MainActivity.java
|
|
174
|
+
// import android.view.WindowManager;
|
|
175
|
+
// getWindow().setFlags(
|
|
176
|
+
// WindowManager.LayoutParams.FLAG_SECURE,
|
|
177
|
+
// WindowManager.LayoutParams.FLAG_SECURE
|
|
178
|
+
// );
|
|
179
|
+
|
|
180
|
+
═══════════════════════════════════════════════════════════════
|
|
181
|
+
REM-MOB-004 — SECURE LOGGER
|
|
182
|
+
═══════════════════════════════════════════════════════════════
|
|
183
|
+
|
|
184
|
+
ISSUE: console.log with sensitive data in production
|
|
185
|
+
SEVERITY: MEDIUM
|
|
186
|
+
MASVS : MASVS-STORAGE-2
|
|
187
|
+
|
|
188
|
+
AFTER (Secured):
|
|
189
|
+
// utils/logger.ts
|
|
190
|
+
|
|
191
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error'
|
|
192
|
+
|
|
193
|
+
const isProduction = !__DEV__
|
|
194
|
+
|
|
195
|
+
const sanitize = (data: unknown): unknown => {
|
|
196
|
+
if (typeof data !== 'object' || data === null) return data
|
|
197
|
+
|
|
198
|
+
const sensitiveKeys = [
|
|
199
|
+
'token', 'password', 'secret', 'apiKey',
|
|
200
|
+
'authorization', 'credit_card', 'ssn', 'pin'
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
const sanitized = { ...data as Record<string, unknown> }
|
|
204
|
+
for (const key of Object.keys(sanitized)) {
|
|
205
|
+
if (sensitiveKeys.some(sk => key.toLowerCase().includes(sk))) {
|
|
206
|
+
sanitized[key] = '[REDACTED]'
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return sanitized
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const logger = {
|
|
213
|
+
debug: (...args: unknown[]) => {
|
|
214
|
+
if (!isProduction) console.log('[DEBUG]', ...args)
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
info: (...args: unknown[]) => {
|
|
218
|
+
if (!isProduction) console.info('[INFO]', ...args)
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
warn: (...args: unknown[]) => {
|
|
222
|
+
if (!isProduction) console.warn('[WARN]', ...args)
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
// In prod: send to monitoring service
|
|
226
|
+
// without sensitive data
|
|
227
|
+
error: (message: string, error?: Error, context?: object) => {
|
|
228
|
+
if (!isProduction) {
|
|
229
|
+
console.error('[ERROR]', message, error, context)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Crashlytics / Sentry without sensitive data
|
|
233
|
+
const safeContext = context ? sanitize(context) : undefined
|
|
234
|
+
crashReporter.recordError(error ?? new Error(message), {
|
|
235
|
+
message,
|
|
236
|
+
context: safeContext,
|
|
237
|
+
})
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
// Never call with sensitive data
|
|
241
|
+
sensitive: () => { /* intentionally no-op */ },
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
═══════════════════════════════════════════════════════════════
|
|
245
|
+
REM-MOB-005 — DEEP LINK VALIDATION
|
|
246
|
+
═══════════════════════════════════════════════════════════════
|
|
247
|
+
|
|
248
|
+
ISSUE: Deep links without parameter validation
|
|
249
|
+
SEVERITY: HIGH
|
|
250
|
+
MASVS : MASVS-PLATFORM-2
|
|
251
|
+
|
|
252
|
+
AFTER (Secured):
|
|
253
|
+
// navigation/DeepLinkHandler.ts
|
|
254
|
+
import { Linking } from 'react-native'
|
|
255
|
+
import { getAuthToken } from '@/services/auth'
|
|
256
|
+
|
|
257
|
+
type DeepLinkRoute = {
|
|
258
|
+
screen: string
|
|
259
|
+
params?: Record<string, unknown>
|
|
260
|
+
requiresAuth: boolean
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const ALLOWED_ROUTES: Record<string, DeepLinkRoute> = {
|
|
264
|
+
'/product': {
|
|
265
|
+
screen: 'ProductDetail',
|
|
266
|
+
requiresAuth: false,
|
|
267
|
+
},
|
|
268
|
+
'/order': {
|
|
269
|
+
screen: 'OrderDetail',
|
|
270
|
+
requiresAuth: true,
|
|
271
|
+
},
|
|
272
|
+
'/payment': {
|
|
273
|
+
screen: 'Payment',
|
|
274
|
+
requiresAuth: true,
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export const handleDeepLink = async (
|
|
279
|
+
url: string,
|
|
280
|
+
navigation: NavigationProp<any>
|
|
281
|
+
): Promise<void> => {
|
|
282
|
+
// 1. Parse URL
|
|
283
|
+
let parsed: URL
|
|
284
|
+
try {
|
|
285
|
+
parsed = new URL(url)
|
|
286
|
+
} catch {
|
|
287
|
+
console.warn('Invalid deep link URL')
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 2. Verify scheme
|
|
292
|
+
const allowedSchemes = ['yourapp:', 'https:']
|
|
293
|
+
if (!allowedSchemes.includes(parsed.protocol)) return
|
|
294
|
+
|
|
295
|
+
// 3. If HTTPS, verify host
|
|
296
|
+
if (parsed.protocol === 'https:') {
|
|
297
|
+
const allowedHosts = ['app.yourapp.com', 'yourapp.com']
|
|
298
|
+
if (!allowedHosts.includes(parsed.hostname)) return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 4. Verify route is allowed
|
|
302
|
+
const routeConfig = ALLOWED_ROUTES[parsed.pathname]
|
|
303
|
+
if (!routeConfig) return
|
|
304
|
+
|
|
305
|
+
// 5. Verify auth if needed
|
|
306
|
+
if (routeConfig.requiresAuth) {
|
|
307
|
+
const token = await getAuthToken()
|
|
308
|
+
if (!token) {
|
|
309
|
+
navigation.navigate('Login', {
|
|
310
|
+
returnTo: url,
|
|
311
|
+
})
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 6. Validate and sanitize parameters
|
|
317
|
+
const params: Record<string, unknown> = {}
|
|
318
|
+
|
|
319
|
+
// Example: numeric ID only
|
|
320
|
+
if (parsed.searchParams.has('id')) {
|
|
321
|
+
const id = parseInt(
|
|
322
|
+
parsed.searchParams.get('id') ?? '', 10
|
|
323
|
+
)
|
|
324
|
+
if (isNaN(id) || id <= 0) return
|
|
325
|
+
params.id = id
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// 7. Navigate securely
|
|
329
|
+
navigation.navigate(routeConfig.screen, params)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
═══════════════════════════════════════════════════════════════
|
|
333
|
+
REM-MOB-006 — SECURED ANDROID MANIFEST
|
|
334
|
+
═══════════════════════════════════════════════════════════════
|
|
335
|
+
|
|
336
|
+
ISSUE: AndroidManifest.xml with poor configuration
|
|
337
|
+
SEVERITY: HIGH
|
|
338
|
+
MASVS : MASVS-PLATFORM-1, MASVS-STORAGE-3
|
|
339
|
+
|
|
340
|
+
AFTER (Secured):
|
|
341
|
+
<!-- Complete secured AndroidManifest.xml -->
|
|
342
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
343
|
+
|
|
344
|
+
<!-- Minimum permissions -->
|
|
345
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
346
|
+
<!-- Add only what is actually used -->
|
|
347
|
+
|
|
348
|
+
<application
|
|
349
|
+
android:allowBackup="false"
|
|
350
|
+
android:fullBackupContent="@xml/backup_rules"
|
|
351
|
+
android:dataExtractionRules="@xml/data_extraction_rules"
|
|
352
|
+
android:usesCleartextTraffic="false"
|
|
353
|
+
android:networkSecurityConfig="@xml/network_security_config"
|
|
354
|
+
android:debuggable="false"
|
|
355
|
+
android:extractNativeLibs="false">
|
|
356
|
+
|
|
357
|
+
<activity
|
|
358
|
+
android:name=".MainActivity"
|
|
359
|
+
android:exported="true"
|
|
360
|
+
android:launchMode="singleTask">
|
|
361
|
+
|
|
362
|
+
<!-- Deep Links via App Links (HTTPS only) -->
|
|
363
|
+
<intent-filter android:autoVerify="true">
|
|
364
|
+
<action android:name="android.intent.action.VIEW" />
|
|
365
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
366
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
367
|
+
<data
|
|
368
|
+
android:scheme="https"
|
|
369
|
+
android:host="app.yourapp.com" />
|
|
370
|
+
</intent-filter>
|
|
371
|
+
</activity>
|
|
372
|
+
|
|
373
|
+
<!-- Internal services: not exported -->
|
|
374
|
+
<service
|
|
375
|
+
android:name=".InternalService"
|
|
376
|
+
android:exported="false" />
|
|
377
|
+
|
|
378
|
+
<!-- Internal providers: not exported -->
|
|
379
|
+
<provider
|
|
380
|
+
android:name=".InternalProvider"
|
|
381
|
+
android:exported="false"
|
|
382
|
+
android:authorities="com.yourapp.internal" />
|
|
383
|
+
|
|
384
|
+
</application>
|
|
385
|
+
</manifest>
|
|
386
|
+
|
|
387
|
+
<!-- res/xml/network_security_config.xml -->
|
|
388
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
389
|
+
<network-security-config>
|
|
390
|
+
<base-config cleartextTrafficPermitted="false">
|
|
391
|
+
<trust-anchors>
|
|
392
|
+
<certificates src="system" />
|
|
393
|
+
</trust-anchors>
|
|
394
|
+
</base-config>
|
|
395
|
+
|
|
396
|
+
<domain-config>
|
|
397
|
+
<domain includeSubdomains="true">api.yourapp.com</domain>
|
|
398
|
+
<pin-set expiration="2026-12-31">
|
|
399
|
+
<pin digest="SHA-256">PRIMARY_PIN_BASE64==</pin>
|
|
400
|
+
<pin digest="SHA-256">BACKUP_PIN_BASE64==</pin>
|
|
401
|
+
</pin-set>
|
|
402
|
+
</domain-config>
|
|
403
|
+
</network-security-config>
|
|
404
|
+
|
|
405
|
+
<!-- res/xml/backup_rules.xml -->
|
|
406
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
407
|
+
<full-backup-content>
|
|
408
|
+
<!-- Exclude all sensitive data from backups -->
|
|
409
|
+
<exclude domain="sharedpref" path="." />
|
|
410
|
+
<exclude domain="database" path="." />
|
|
411
|
+
<exclude domain="file" path="." />
|
|
412
|
+
</full-backup-content>
|
|
413
|
+
|
|
414
|
+
═══════════════════════════════════════════════════════════════
|
|
415
|
+
REM-MOB-007 — SECURE BIOMETRICS
|
|
416
|
+
═══════════════════════════════════════════════════════════════
|
|
417
|
+
|
|
418
|
+
ISSUE: Biometrics implemented as a simple UI check
|
|
419
|
+
SEVERITY: HIGH
|
|
420
|
+
MASVS : MASVS-AUTH-3
|
|
421
|
+
|
|
422
|
+
AFTER (Secured):
|
|
423
|
+
// Biometrics must be linked to a cryptographic key
|
|
424
|
+
// Not just: if (biometricResult === true) → access
|
|
425
|
+
|
|
426
|
+
// React Native with react-native-keychain
|
|
427
|
+
import * as Keychain from 'react-native-keychain'
|
|
428
|
+
|
|
429
|
+
// Store with biometric protection
|
|
430
|
+
const storeSensitiveData = async (data: string) => {
|
|
431
|
+
await Keychain.setGenericPassword(
|
|
432
|
+
'protected-data',
|
|
433
|
+
data,
|
|
434
|
+
{
|
|
435
|
+
service: 'com.yourapp.biometric-protected',
|
|
436
|
+
accessControl:
|
|
437
|
+
Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET,
|
|
438
|
+
// BIOMETRY_CURRENT_SET = invalidated if new fingerprints
|
|
439
|
+
// added → enhanced security
|
|
440
|
+
accessible:
|
|
441
|
+
Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
|
|
442
|
+
authenticationType:
|
|
443
|
+
Keychain.AUTHENTICATION_TYPE.BIOMETRICS,
|
|
444
|
+
}
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Retrieve with biometric validation
|
|
449
|
+
const getProtectedData = async (): Promise<string | null> => {
|
|
450
|
+
try {
|
|
451
|
+
const creds = await Keychain.getGenericPassword({
|
|
452
|
+
service: 'com.yourapp.biometric-protected',
|
|
453
|
+
authenticationPrompt: {
|
|
454
|
+
title: 'Authentication required',
|
|
455
|
+
subtitle: 'Confirm your identity to access',
|
|
456
|
+
cancel: 'Cancel',
|
|
457
|
+
},
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
// Biometrics was verified by Keychain
|
|
461
|
+
// (not just a JS boolean)
|
|
462
|
+
return creds ? creds.password : null
|
|
463
|
+
|
|
464
|
+
} catch (error) {
|
|
465
|
+
// Biometrics denied or unavailable
|
|
466
|
+
return null
|
|
467
|
+
}
|
|
468
|
+
}
|