agentleFS
ashrafiucseashrafiucse/security-audit-skills/skills/android-code-security/SKILL.md

android-code-security

Skill2 starsChanged 48 days agoMITView on GitHub

Audits native Android app code in Kotlin AND Java — tokens outside EncryptedSharedPreferences/Keystore, local SQLi via execSQL/rawQuery interpolation, TLS bypass in code (empty X509TrustManager, always-true HostnameVerifier, OkHttp trust-all), WebView @JavascriptInterface methods reaching sensitive ops with external URLs, deep-link intent.getStringExtra into startActivity/Class.forName, PendingIntent without FLAG_IMMUTABLE, hardcoded SecretKeySpec, Cipher.getInstance("AES") ECB default, MD5/SHA-1, seeded SecureRandom, token Log.d leakage, committed google-services.json keys. Use when the project has app/src/main Kotlin or Java code with an Android build.gradle.

The file

We split this skill into the rules it gives an agent

1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Always-true verifier (lambda or verify → return true) → Critical.

    line 72

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Android Code Security (Kotlin + Java)

App-code shapes for native Android. The manifest/platform half (exported components, allowBackup, cleartext policy, network security config) stays with ../mobile-security/SKILL.md — run both; this skill owns what lives in app/src/main/**. Sibling: ../flutter-security/SKILL.md (same classes, Dart shapes).

Kotlin and Java share every platform API — most rules fire on both with one grep; the divergent shapes (lambdas vs anonymous classes, string templates vs concatenation) get their own pattern lines below.

Step 0 — Detect

rg --files -g 'build.gradle*' -g '*.kt' -g '*.java' | head -5
rg -n "com.android.application" -g 'build.gradle*'
rg --files -g 'google-services.json'

Record: security-crypto (EncryptedSharedPreferences) present? OkHttp/Retrofit? WebView? Kotlin-only or mixed with Java legacy files?

Step 1 — Storage census (persona: device thief / rooted / adb backup)

rg -n "getSharedPreferences\(" -g '*.kt' -g '*.java' app/src/
rg -n "writeText\(|FileOutputStream\(" -g '*.kt' -g '*.java' app/src/
rg -n "SQLiteOpenHelper|execSQL\(|rawQuery\(" -g '*.kt' -g '*.java' app/src/

Census discipline: every hit dispositioned (credential-class → finding; theme/flags → verified-safe). Credential-class = tokens, keys, passwords, PII. Plaintext SharedPreferences is world-readable to anything with the device/userdata. Safe shape: EncryptedSharedPreferences.create(...) with a MasterKey — presence alone doesn't clean a hit of getSharedPreferences on a DIFFERENT name; check which store the token write targets.

Step 2 — Local SQLi (the app's own DB)

rg -n 'execSQL\(.*\$' -g '*.kt' app/src/        # Kotlin string templates
rg -n 'execSQL\(.*\+' -g '*.kt' -g '*.java' app/src/   # concatenation (both langs)
rg -n 'rawQuery\(.*(\$|\+)' -g '*.kt' -g '*.java' app/src/

Interpolated/concatenated values = injection; the local DB often mirrors server data and is readable by backups. Safe: bind args — execSQL(sql, arrayOf(v)), rawQuery(sql, selectionArgs). Note: SQL literals legitimately contain parens (VALUES (?), WHERE (a AND b)) — the vuln signal is $/+ reaching the statement, not parens. Trace the value: deeplink/intent-controlled input raises severity (other-app persona).

Step 3 — TLS bypass in code

rg -n 'checkServerTrusted[^{]*\{\}' -g '*.kt' -g '*.java' app/src/
rg -n 'HostnameVerifier\s*\{[^}]*true' -g '*.kt' app/src/
rg -n -A1 "boolean verify" -g '*.java' app/src/   # Java twin is TWO lines: signature, then `return true;`
rg -n "http://" -g '*.kt' -g '*.java' app/src/
  • Empty-body checkServerTrusted (Kotlin override fun ...() {} or Java anonymous class) → Critical when wired into a client (look for sslSocketFactory(...) nearby). The empty method IS the trust kill.
  • Always-true verifier (lambda or verify → return true) → Critical.
  • http:// constants → Medium; Critical when auth headers ride the request. localhost/test-config is the common safe near-miss — disposition it.
  • Safe: default trust + CertificatePinner for token-carrying clients.

Step 4 — WebView bridge depth

rg -n "addJavascriptInterface|@JavascriptInterface" -g '*.kt' -g '*.java' app/src/
rg -n "loadUrl\((url|target|[a-z]+Url)" -g '*.kt' -g '*.java' app/src/
rg -n "javaScriptEnabled = true|setJavaScriptEnabled\(true\)" -g '*.kt' -g '*.java' app/src/

../mobile-security/SKILL.md flags bridge existence from the manifest side; HERE you open every @JavascriptInterface method body: file reads, payment, token access, intent launching → Critical when the URL is external (deeplink/intent-sourced). loadUrl( with a constant https string is the safe near-miss; a variable is the vuln shape.

Step 5 — Intent & deep-link flow (persona: any other app on the device)

rg -n "getStringExtra\(|getData\(|getParcelableExtra\(" -g '*.kt' -g '*.java' app/src/
rg -n "Class\.forName|setComponent\(|setPackage\(" -g '*.kt' -g '*.java' app/src/
rg -n "PendingIntent\.getActivity\(" -g '*.kt' -g '*.java' app/src/
  • External string → Class.forName/setComponent → startActivity = the app instantiates any class another app names (private activities, side-effectful receivers) → High.
  • PendingIntent.getActivity(ctx, req, intent, 0) — flags=0 = MUTABLE pending intent → High (hijack/rewrite by other apps pre-12 semantics). Safe: PendingIntent.FLAG_IMMUTABLE in the flags argument — same call, flags differ; check the LAST argument, not the call itself.
  • Route allowlist maps (intent.data.lastPathSegment → const map) = safe shape.

Step 6 — Crypto

rg -n "SecretKeySpec\(" -g '*.kt' -g '*.java' app/src/
rg -n 'Cipher\.getInstance\("AES"\)' -g '*.kt' -g '*.java' app/src/
rg -n 'getInstance\("(MD5|SHA-1)"\)' -g '*.kt' -g '*.java' app/src/
rg -n "setSeed\(" -g '*.kt' -g '*.java' app/src/
  • SecretKeySpec(hardcodedBytes, "AES") → Critical: key extractable from APK, never rotatable. Safe: AndroidKeyStore KeyGenParameterSpec.
  • Cipher.getInstance("AES") → ECB default → High. "AES/GCM/NoPadding" = safe.
  • MD5/SHA-1: severity by what it protects (fingerprint/ticket = Medium; password/token = Critical).
  • setSeed(constant) on SecureRandom → High — predictable OTPs/tokens.

Step 7 — Secrets artifacts & logcat

rg -n "current_key|api_key" -g 'google-services.json'
rg -n 'Log\.[dvi]\([^)]*[Tt]oken' -g '*.kt' -g '*.java' app/src/
rg -n -i "(api[_-]?key|secret|token)\s*=\s*\"[^\"]{8,}" -g '*.kt' -g '*.java' -g 'strings.xml' -g 'build.gradle*'
  • google-services.json current_key (AIza…) committed → Medium baseline: check Firebase key restrictions + which APIs are enabled before rating higher; unrestricted keys are attacker-budget risks.
  • Token values in Log.d/v/i → Medium (logcat: adb, bug reports, vendor crash dumps). if (BuildConfig.DEBUG) gate + static message = safe.
  • Hardcoded key assignments → route via ../secrets-detection/SKILL.md.

Step 8 — Dependencies & build hygiene

build.gradle deps: dependency-vulns' osv_scan parses gradle.lockfile (maven ecosystem) — plain implementation coordinates need manual OSV lookup by group:artifact:version; note as limitation in the report. minifyEnabled false in release + no resource obfuscation → informational.

Reporting

Severity table as in steps. Fixes are short: EncryptedSharedPreferences create-call, bind-args execSQL, FLAG_IMMUTABLE, Keystore keygen. Manifest findings → ../mobile-security/SKILL.md; Flutter equivalents → ../flutter-security/SKILL.md; dep CVEs → ../dependency-vulns/SKILL.md.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.