Audit iOS and Android mobile applications against OWASP MASVS / MASTG — insecure storage, weak crypto, certificate pinning, deeplinks, IPC, jailbreak/root detection, reverse-engineering resistance. Use when the user mentions 'mobile security,' 'iOS security,' 'Android security,' 'mobile audit,' 'mobile pentest,' 'MASVS,' 'MASTG,' 'certificate pinning,' 'jailbreak detection,' 'root detection,' 'deeplink,' 'URL scheme,' 'app transport security,' 'keychain,' 'keystore,' 'mobile reverse engineering,' or has a mobile app to review.
96%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Audit mobile apps against the OWASP Mobile Application Security Verification Standard (MASVS) and Mobile Application Security Testing Guide (MASTG). Covers source code review, static analysis of compiled binaries, and runtime testing.
Scope: this skill covers the app and its interaction with the device, the backend, and other apps. For backend API security, pair with api-audit. For dependency CVEs (CocoaPods, SPM, Gradle), pair with dependency-audit.
Before reverse-engineering or runtime-testing a binary, confirm:
If unclear, ask before proceeding.
kSecAttrAccessible class — kSecAttrAccessibleWhenUnlockedThisDeviceOnly or kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. Avoid Always and ThisDeviceOnly-less variantsNSUserDefaults, plist, or app bundle — strings <app>.ipa should not reveal API keys or secretsandroid:allowBackup="false" in the manifest (or backup rules carefully scoped) — otherwise adb backup extracts everythingpasteboard.expirationDate, Android ClipDescription.EXTRA_IS_SENSITIVE)applicationDidEnterBackground blur, Android FLAG_SECURE on the activitystrings, class-dump, apktool reveal embedded constantsSecRandomCopyBytes (iOS) / SecureRandom (Android) — not arc4random() for crypto, never Math.random()iv = "0000000000000000", that's worse than no encryption (reveals plaintext patterns)NSAllowsArbitraryLoads = true. If exceptions exist, they're specific domains, justified, and documentednetwork_security_config.xml exists and enforces cleartext-traffic refusal — <base-config cleartextTrafficPermitted="false">URLSessionDelegate + URLAuthenticationChallenge; Android: NetworkSecurityConfig <pin-set> or OkHttp CertificatePinnerWKWebView only (iOS, not UIWebView); JavaScript bridge audited; setJavaScriptEnabled(false) if the WebView doesn't need JSloadUrl with user-controlled URL — open redirect, intent-spoofing, phishing surfaceLAContext.evaluatePolicy (iOS) / BiometricPrompt (Android) — not the deprecated FingerprintManagerSecAccessControl.biometryAny, Android KeyGenParameterSpec.setUserAuthenticationRequired(true))android:exported="true") reviewed for parameter handlingmyapp://) which any app can registermyapp:// from a WebView to trigger an in-app action without user consent is an XSS-to-action chainandroid:exported="false" unless explicitly intended for cross-app access; if exported, every URI path validatedandroid:permission is callable by any appLocalBroadcastManager for in-app broadcasts; system broadcasts validatedUIApplication.openURL options include UIApplicationOpenURLOptionsSourceApplicationKey)otool -hv on iOS, readelf -h on Android .so). For memory-safety review of the native and JNI source itself → native-code-reviewstrip, ProGuard/R8)DEBUG flag, isDebuggable in manifest)This category is rated optional in MASVS — only required for high-risk apps (banking, DRM, government). For most apps, don't waste effort here; ship secure crypto and a proper backend.
If required:
ptrace self-attach (iOS / Linux), Debug.isDebuggerConnected (Android)Note: every resilience control will be bypassed by a determined attacker with physical device access. They buy time, they don't prevent.
| Tool | Platform | Use |
|---|---|---|
| MobSF | iOS + Android | Automated static + dynamic scanner; first-pass triage |
| nuclei + mobile templates | Both | Pattern-based scanner |
| semgrep + mobile rules | Both | AST-based rules |
| jadx | Android | Decompile APK to Java |
| apktool | Android | Disassemble APK |
| Hopper / Ghidra / IDA | iOS | Disassemble Mach-O |
| class-dump / nm / otool | iOS | Symbol and structure inspection |
strings | Both | First check — secrets, URLs, debug strings |
| Frida + objection | Both | Runtime instrumentation, SSL-pinning bypass, method tracing |
For grey/black-box assessment, use a non-personal device:
objection if you need to see encrypted traffic during testingapi-audit)Every candidate goes through the refutation pass in references/verification.md before it reaches findings.json: a separate read that starts from the cited evidence and tries to disprove the claim. Close each candidate with exactly one verdict, per references/findings-format.md. Gaps with no traced impact are hardening notes, not records.
Citing a decompiled binary. Mobile findings usually come from a compiled app, not source you can point a repo line at. Save the read-only tool output — strings, a jadx / apktool decompilation, class-dump, otool -hv, readelf -h, a manifest or plist dump, a proxied request/response — redacted, to <audit dir>/evidence/<name>.txt (or .json), and point location and each evidence[] entry at that file and the line within it. Never write a secret value to an evidence file: a hardcoded key's evidence is the decompiled call site that uses it, redacted to type + last 4 — not the key.
confirmed — a complete trace from attacker-reachable input (or an extracted artifact) to impact, or a runtime proof. Examples: an AES key read out of the decompiled APK with strings, traced to the decrypt call that uses it (evidence file saved; static-only, so confidence: medium); cleartext login captured over the test proxy because NSAllowsArbitraryLoads=true ships in Info.plist — a permissive shipped default, so confirmed at confidence: medium with a system_configuration condition for builds that don't override it, and reproduced: true once the request is captured.needs_validation — the binary supports the claim but one decisive fact lives off-device. Examples: an android:exported="true" activity whose real risk depends on backend authorization you can't see; a network_security_config cleartext exception whose domain you have not yet confirmed the running app contacts. A live check you describe but don't run (a proxy capture, a Frida hook) is needs_validation with that capture as the plan — not confirmed.rejected — a named control at a named location rules it out. Examples: a Log.d of a token gated behind if (BuildConfig.DEBUG) with android:debuggable="false" in the release manifest (ruled_out_by points at the manifest line in the evidence dump); a candidate "hardcoded key" that is a publishable Firebase/Maps identifier with its restrictions in place (see references/severity-calibration.md §6).Severity goes on confirmed records only, calibrated with references/severity-calibration.md: overall ≤ demonstrated impact; a static-only extraction caps confidence at medium. MASVS-RESILIENCE gaps (root/jailbreak detection, obfuscation, anti-debugging) with no traced impact are hardening notes, not findings — they raise an attacker's cost, they don't cross a boundary.
Record domain fields under ext (optional object on any record):
ext key | Meaning | Example |
|---|---|---|
masvs | MASVS control id | MASVS-STORAGE-1 |
platform | platform the finding is on | ios | android | both |
source | where the evidence came from | source | decompiled | runtime |
Fingerprint: mobile-audit/<masvs-or-class>/<file or resource>/<symbol or name>, slash-separated, no line numbers (the validator rejects :<digits> segments). Examples: mobile-audit/MASVS-NETWORK-1/Info.plist/NSAllowsArbitraryLoads, mobile-audit/MASVS-STORAGE-1/AndroidManifest.xml/allowBackup, mobile-audit/MASVS-CRYPTO-2/com/acme/app/crypto/Vault/decrypt. For a decompiled app, use the class or symbol the code lives in, not the evidence file name, so the fingerprint stays the same when the evidence is re-captured.
Write findings.json sorted by fingerprint, then validate and fix until it passes:
node references/validate-findings.mjs findings.jsonOptionally export SARIF: node references/to-sarif.mjs findings.json -o findings.sarif. Dispositions (Fixed / Deferred / Accepted Risk / Mitigated (platform/config) / Requires major migration) are set later, usually by finding-triage; a false positive is a rejected record, not a disposition.
Write findings.json first and validate it; the markdown report is rendered from the JSON and never changes a verdict or severity the JSON doesn't already say.
# Mobile Application Security Audit
## App: [name + version] ## Platform: iOS / Android / both ## MASVS profile: L1 / L2 / R
## findings.json: [path] — validated: [yes] ## Date: [date]
### Summary
- Confirmed: [N] (Critical [n] | High [n] | Medium [n] | Low [n]) Needs validation: [N] Rejected: [N]
### Confirmed findings
#### [SEVERITY] [Title] `fingerprint`
**Location:** `evidence/<file>:line` (or repo `path:line`) **MASVS:** [MASVS-X-n] **CWE:** [CWE-n] **Platform:** [ios | android | both] **Confidence:** [medium | high (reproduced)]
**What and why it matters:** [the trace, in a few lines]
**Fix:** [concrete change] **Verify the fix:** [the check that proves it]
### Needs validation
| Finding | Blockers | Check for the owner to run |
|---|---|---|
### Hardening notes (not findings)
- [gap] — [why it isn't a finding] — [effort] (RESILIENCE controls usually land here)
### Rejected (do not re-raise)
| Fingerprint | Ruled out by | Location |
|---|---|---|
### MASVS category coverage
| Category | Confirmed | Needs validation | Severity high-water mark |
|---|---|---|---|
| STORAGE / CRYPTO / NETWORK / AUTH / PLATFORM / CODE / RESILIENCE | | | |
### Backend API findings
[Cross-link to api-audit / owasp-audit output]
### Prioritized remediation plan
1. [highest first — 30 / 60 / 90 day fixes]references/ at build time): findings-format.md, severity-calibration.md, verification.mdbriiirussell/cybersecurity-skills
c79bb8b
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.